【 Android 】RecyclerView 从理论到实践

作者: Tyhoo_Wu | 来源:发表于2017-08-04 09:58 被阅读182次

示例项目采用:
MD + MVP + Retrofit2 + Gson + RxJava2 + Realm

示例项目 GIF:

示例 GIF.gif

示例项目已同步到 GitHub:
https://github.com/cnwutianhao/RecyclerViewSample

欢迎交流,我会持续更新,不断完善自己对 RecyclerView 的认识。

这个示例项目没有依赖,基本上照着学会了 RecyclerView 也就掌握了。

五部分 + 彩蛋

  • 列表 item
  • 列表到详情 item -> detail
  • 搜索 search
  • 搜索到详情 search -> detail
  • 彩蛋(一些新特性)

项目遵循 MVP 架构样式(参考 Google Sample 的 TODO-MVP),项目整体是 一个 Activity + N 个 Fragment 构成。

开始我的 FreeStyle !

导入依赖库:

compile 'com.android.support:recyclerview-v7:26.0.0-alpha1'

列表 item

RecyclerView 也是一个 View , 那么就要在布局里面先定义好:

<android.support.v7.widget.RecyclerView
    android:id="@+id/recyclerViewCompaniesList"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

在 Fragment 里面初始化 RecyclerView,并使用 LayoutManager 来确定每一个 item 的排列方式

private RecyclerView mRecyclerView;
mRecyclerView = view.findViewById(R.id.recyclerViewCompaniesList);
mRecyclerView.setLayoutManager(layoutManager);

重头戏:自定义 Adapter

继承父类 RecyclerView.Adapter<RecyclerView.ViewHolder>

得到 onCreateViewHolder 、onBindViewHolder 和 getItemCount

onCreateViewHolder 用来创建新 view,被 LayoutManager 所调用。
onBindViewHolder 将数据与界面进行绑定的操作。
getItemCount 获取数据的数量。

除此之外,我们还需要自定义 ViewHolder,持有每个 Item 的所有界面元素。

彩蛋:公开一个 interface ,用于对 RecyclerView 的 item 做点击事件监听。

public interface OnRecyclerViewItemClickListener {

    void OnItemClick(View v, int position);
}

自定义 ViewHolder

@Nullable
private OnRecyclerViewItemClickListener mListener;

public class ExpressCompaniesViewHolder extends RecyclerView.ViewHolder
        implements View.OnClickListener {

    ImageView avatar;
    AppCompatTextView textViewCompanyName;
    AppCompatTextView textViewAvatar;
    AppCompatTextView textViewCompanyTel;

    private OnRecyclerViewItemClickListener listener;

    public ExpressCompaniesViewHolder(View itemView, OnRecyclerViewItemClickListener listener) {
        super(itemView);
        avatar = itemView.findViewById(R.id.imageViewAvatar);
        textViewAvatar = itemView.findViewById(R.id.textViewAvatar);
        textViewCompanyName = itemView.findViewById(R.id.textViewCompanyName);
        textViewCompanyTel = itemView.findViewById(R.id.textViewCompanyTel);

        this.listener = listener;
        itemView.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (this.listener != null) {
            listener.OnItemClick(v, getLayoutPosition());
        }
    }
}

重写 onCreateViewHolder

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    return new ExpressCompaniesViewHolder(mInflater.inflate(R.layout.item_company, parent, false), mListener);
}

重写 onBindViewHolder

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    ExpressCompany company = mList.get(position);
    ExpressCompaniesViewHolder cvh = (ExpressCompaniesViewHolder) holder;
    cvh.textViewAvatar.setText(company.getName().substring(0, 1).toUpperCase());
    cvh.textViewCompanyTel.setText(company.getTel());
    cvh.textViewCompanyName.setText(company.getName());
    cvh.avatar.setColorFilter(Color.parseColor(company.getAvatarColor()));
}

重写 getItemCount

@Override
public int getItemCount() {
    return mList.size();
}

列表到详情 item -> detail

公开一个方法暴露给 Fragment,使得当点击 Item 的时候可以准确的对应到。

public void setOnRecyclerViewItemClickListener(OnRecyclerViewItemClickListener listener) {
    this.mListener = listener;
}

在 Fragment 里面初始化自定义的 Adapter,并做点击事件。

private ExpressCompaniesAdapter mAdapter;

mAdapter = new ExpressCompaniesAdapter(getContext(), list);
mAdapter.setOnRecyclerViewItemClickListener(new OnRecyclerViewItemClickListener() {
    @Override
    public void OnItemClick(View v, int position) {
        Intent intent = new Intent(getContext(), ExpressCompanyDetailActivity.class);
        intent.putExtra(ExpressCompanyDetailActivity.COMPANY_ID, list.get(position).getId());
        startActivity(intent);
    }
});
mRecyclerView.setAdapter(mAdapter);

将 Key:唯一的 ID , Value:对应的 Position 传给详情页。
这样我们就完成了从 item 到 detail 的第一步。
在详情页拿到唯一的 ID 之后,根据调取的 JSON 数据,我们就可以拿到整个字段,并将字段里面的信息,逐一填到对应的 View 上。

搜索 search

搜索一般会显示两种情况:有数据、没有数据
继承父类

RecyclerView.Adapter<RecyclerView.ViewHolder> 

就会有 onCreateViewHolder 、 onBindViewHolder 、 getItemCount 和 自定义 ViewHolder 。

自定义没有数据时的 ViewHolder

private class EmptyHolder extends RecyclerView.ViewHolder {
    AppCompatTextView textView;
    EmptyHolder(View itemView) {
        super(itemView);
        textView = itemView.findViewById(R.id.tv_title);
    }
}

自定义有数据时的 ViewHolder

private class CompanyHolder extends RecyclerView.ViewHolder
        implements View.OnClickListener {

    ImageView avatar;
    AppCompatTextView textViewCompanyName, textViewAvatar, textViewCompanyTel;

    private OnRecyclerViewItemClickListener listener;

    CompanyHolder(View itemView, OnRecyclerViewItemClickListener listener) {
        super(itemView);
        avatar = itemView.findViewById(R.id.imageViewAvatar);
        textViewAvatar = itemView.findViewById(R.id.textViewAvatar);
        textViewCompanyName = itemView.findViewById(R.id.textViewCompanyName);
        textViewCompanyTel = itemView.findViewById(R.id.textViewCompanyTel);
        this.listener = listener;
        itemView.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (listener != null) {
            listener.OnItemClick(v, getLayoutPosition());
        }
    }
}

重写 onCreateViewHolder

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    RecyclerView.ViewHolder viewHolder = null;
    switch (viewType) {
        case ItemWrapper.TYPE_EMPTY:
            viewHolder = new EmptyHolder(mInflater.inflate(R.layout.item_search_result_empty, parent, false));
            break;
        case ItemWrapper.TYPE_COMPANY:
            viewHolder = new CompanyHolder(mInflater.inflate(R.layout.item_company, parent, false), mListener);
            break;
    }
    return viewHolder;
}

重写 onBindViewHolder

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    ItemWrapper iw = mList.get(position);
    switch (iw.viewType) {
        case ItemWrapper.TYPE_EMPTY:
            EmptyHolder emptyHolder = (EmptyHolder) holder;
            emptyHolder.textView.setText(mCompanies == null ?
                    R.string.item_loading : R.string.no_result);
            break;
        case ItemWrapper.TYPE_COMPANY:
            ExpressCompany company = mCompanies.get(iw.index);
            CompanyHolder companyHolder = (CompanyHolder) holder;
            companyHolder.textViewAvatar.setText(company.getName().substring(0, 1).toUpperCase());
            companyHolder.textViewCompanyTel.setText(company.getTel());
            companyHolder.textViewCompanyName.setText(company.getName());
            companyHolder.avatar.setColorFilter(Color.parseColor(company.getAvatarColor()));
            break;
    }
}

重写 getItemCount

@Override
public int getItemCount() {
    return mList != null ? mList.size() : 0;
}

搜索到详情 search -> detail

公开一个方法暴露给 Fragment,使得当点击 Item 的时候可以准确的对应到。

public void setOnItemClickListener(OnRecyclerViewItemClickListener listener) {
    this.mListener = listener;
}

在 Fragment 里面初始化自定义的 Adapter,并做点击事件。

private SearchAdapter mSearchAdapter;

if (mSearchAdapter == null) {
    mSearchAdapter = new SearchAdapter(getContext(), companies);
    mSearchAdapter.setOnItemClickListener(new OnRecyclerViewItemClickListener() {
        @Override
        public void OnItemClick(View v, int position) {
            Intent intent = new Intent(getContext(), ExpressCompanyDetailActivity.class);
            intent.putExtra(ExpressCompanyDetailActivity.COMPANY_ID, companies.get(mSearchAdapter.getOriginalIndex(position)).getId());
            startActivity(intent);
        }
    });
    mRecyclerView.setAdapter(mSearchAdapter);
}

将 Key:唯一的 ID , Value:对应的 Position 传给详情页。
这样我们就完成了从 item 到 detail 的第一步。
在详情页拿到唯一的 ID 之后,根据调取的 JSON 数据,我们就可以拿到整个字段,并将字段里面的信息,逐一填到对应的 View 上。

彩蛋

留心的同学会发现我们这里有一个弹出网页页面的动作。
示例项目没有添加 webview ,而是使用了更有好的,Google 推荐的方式实现网页显示,给用户一种感觉就是好像我们的 App 里面嵌套了浏览器一样。
导入官方库:

compile 'com.android.support:customtabs:26.0.0-alpha1'

没接触过此库的,第一感觉一定是这是一个自定义的 Tab,但并非如此,后续我会针对 Google 新推出的几个库做一个小结。

自定义 CustomTabsHelper 类

public class CustomTabsHelper {

    public static void openUrl(Context context, String url) {
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);

        if (sharedPreferences.getBoolean(SettingsUtil.KEY_CUSTOM_TABS, true)) {
            CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
            builder.setToolbarColor(ContextCompat.getColor(context, R.color.colorPrimaryDark));
            builder.build().launchUrl(context, Uri.parse(url));
        } else {
            try {
                context.startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse(url)));
            } catch (ActivityNotFoundException e) {
                Toast.makeText(context, R.string.error_no_browser, Toast.LENGTH_SHORT).show();
            }
        }
    }
}

这样当我们点击 URL 的时候,直接调用 openUrl 方法即可。

注:如果你的 Android 手机或模拟器没有 Chrome ,会看不到效果。

至此,我们的 RecyclerView 从理论到实践就到此结束。

相关文章

网友评论

    本文标题:【 Android 】RecyclerView 从理论到实践

    本文链接:https://www.haomeiwen.com/subject/rgwmlxtx.html