【 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