美文网首页ITBOXAndroid ClassAndroid资料
超级简单RecycleViewAdapter的封装-支持多视图,

超级简单RecycleViewAdapter的封装-支持多视图,

作者: laogui | 来源:发表于2016-08-09 18:17 被阅读2214次

    其实RecycleView已经出来很长时间了,对RecycleView的用法网上也有很多教程了。本篇文章不讲解RecycleView的用法,不讲解LayoutManager的用法也不讲解ItemDecoration的用法,我们只关注Adapter的用法以及如何封装成一个通用的Adapter

    Adapter的正常使用方法

    其实很简单,只需要继承RecyclerView.Adapter<VH extends ViewHolder>传入范型类型为ViewHolder的子类就可以,代码演示如下:
    MainActivity.java

    public class MainActivity extends AppCompatActivity {
        ...
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            ...
            List<String> datas = new ArrayList<>();
            for (int i = 0; i < 20; ++i) {
                datas.add("item:" + (i + 1));
            }
            layoutManager = new LinearLayoutManager(this);
            recyclerView.setLayoutManager(layoutManager);
    
            MyAdapter myAdapter = new MyAdapter(datas);
            myAdapter.setItemClickListener(new MyAdapter.ItemClickListener() {
                @Override
                public void onItemClicked(View view, int position) {
                    Log.d(TAG,"root clicked..." + position);
                }
            });
            recyclerView.setAdapter(myAdapter);
        }
    
        private static class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder>{
    
            private List<String> dataList;
    
            private ItemClickListener itemClickListener;
    
            public MyAdapter(List<String> dataList){
                this.dataList = dataList;
            }
    
            public interface ItemClickListener {
                void onItemClicked(View view,int position);
            }
    
            //设置点击回调接口
            public void setItemClickListener(ItemClickListener itemClickListener) {
                this.itemClickListener = itemClickListener;
            }
    
            //生成ViewHolder
            @Override
            public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_view_main1, parent, false);
                return new ViewHolder(itemView);
            }
    
            private String getItem(int position){
                return dataList.get(position);
            }
    
            //更新列表Item视图(根据需要绑定click事件)
            @Override
            public void onBindViewHolder(ViewHolder holder, final int position) {
                String str = getItem(position);
    //            holder.icon.setImageDrawable(xxx);
                holder.name.setText(str);
                holder.root.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if(itemClickListener != null)
                            itemClickListener.onItemClicked(v,position);
                    }
                });
            }
    
            @Override
            public int getItemCount() {
                return dataList.size();
            }
            //ViewHolder保存每个item视图
            public class ViewHolder extends RecyclerView.ViewHolder{
                private ImageView icon;
                private TextView name;
                private View root;
                public ViewHolder(View itemView) {
                    super(itemView);
                    icon = (ImageView)itemView.findViewById(R.id.icon);
                    name = (TextView)itemView.findViewById(R.id.id_text);
                    root = itemView.findViewById(R.id.root);
                }
            }
        }
    }
    

    通过以上代码演示,我们可以得出结论:构造一个比较完整的Adapter至少需要完成以下三件事情

    1. onCreateViewHolder通过视图Id加载不同Item视图并生成ViewHolder用来保存每个列表Item视图
    2. onBindViewHolder更新列表Item视图(填充model数据)
    3. 新建ViewHolder类来存储Item视图及其子视图

    如果需要实现点击事件,需要在onBindViewHolder中适当绑定点击事件,比如在以上代码中绑定了点击列表视图中的Item根视图的点击事件如下:

    holder.root.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if(itemClickListener != null)
                itemClickListener.onItemClicked(v,position);
        }
    });
    

    在需要实现具体的点击事件时调用如下代码:

    myAdapter.setItemClickListener(new MyAdapter.ItemClickListener() {
        @Override
        public void onItemClicked(View view, int position) {
            Log.d(TAG,"root clicked..." + position);
        }
    });
    

    问题

    1. 如果需要实现在同一个Item视图中点击不同view要实现不同功能
      比如以上代码中处理了点击Item跟视图的点击事件,如果需要点击根视图中的icon子视图如何实现?
      是不是继续写setOnXXXListener,麻烦也不太现实
    2. 如果要实现列表中多视图展示如何实现?正常如果需要支持多视图咱们是在onCreateViewHolder方法中根据不同的ViewType来加载不同的Item视图代码演示如下:
    //重写getItemViewType函数,更具需要返回不同的viewType
    @Override
    public int getItemViewType(int position) {
        String model = getItem();
        if(...){
            return 1;
        }else if(...){
            return 2;
        }
        return super.getItemViewType(position);
    }
    
    //生成ViewHolder
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        int itemViewId = -1;
        if(viewType == 1){
            itemViewId = R.layout.item_view_main1;
        }else if(viewType == 2){
            itemViewId = R.layout.item_view_main2;
        }
        View itemView = LayoutInflater.from(parent.getContext()).inflate(itemViewId, parent, false);
        return new ViewHolder(itemView);
    }
    

    以上方案纯属针对某种列表来实现的,如果换了一个列表视图咱们就需要重现创建adapter并分别在onCreateViewHolder,onBindViewHolder方法中实现不同逻辑并重新创建xxxViewHolder继承ViewHolder来保存不同的Item视图

    解决方案

    基于以上问题,给出如下方案
    LGViewHolder.java用来设计通用的ViewHolder

    public class LGViewHolder extends RecyclerView.ViewHolder {
        private SparseArray<View> mViews;
        private View mConvertView;//缓存itemView内部的子View
    
        public LGViewHolder(View itemView) {
            super(itemView);
            mConvertView = itemView;
            mViews = new SparseArray<>();
        }
    
        /**
         * 加载layoutId视图并用LGViewHolder保存
         * @param parent
         * @param layoutId
         * @return
         */
        protected static LGViewHolder getViewHolder(ViewGroup parent, int layoutId) {
            View itemView = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
            return new LGViewHolder(itemView);
        }
    
        /**
         * 根据ItemView的id获取子视图View
         * @param viewId
         * @return
         */
        public View getView(int viewId) {
            View view = mViews.get(viewId);
            if (view == null) {
                view = mConvertView.findViewById(viewId);
                mViews.put(viewId, view);
            }
            return view;
        }
    }
    

    LGRecycleViewAdapter.java

    public abstract class LGRecycleViewAdapter<T> extends RecyclerView.Adapter<LGViewHolder> {
        private final String TAG = "LGRecycleViewAdapter";
        //存储监听回调
        private SparseArray<ItemClickListener> onClickListeners;
    
        private List<T> dataList;
    
        public interface ItemClickListener {
            void onItemClicked(View view,int position);
        }
    
        public LGRecycleViewAdapter(List<T> dataList) {
            this.dataList = dataList;
            onClickListeners = new SparseArray<>();
        }
    
        /**
         * 存储viewId对应的回调监听实例listener
         * @param viewId
         * @param listener
         */
        public void setOnItemClickListener(int viewId,ItemClickListener listener) {
            ItemClickListener listener_ = onClickListeners.get(viewId);
            if(listener_ == null){
                onClickListeners.put(viewId,listener);
            }
        }
    
        /**
         * 获取列表控件的视图id(由子类负责完成)
         * @param viewType
         * @return
         */
        public abstract int getLayoutId(int viewType);
    
        //更新itemView视图(由子类负责完成)
        public abstract void convert(LGViewHolder holder, T t, int position);
    
        public T getItem(final int position){
            if(dataList == null)
                return null;
            return dataList.get(position);
        }
    
        @Override
        public LGViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            int layoutId = getLayoutId(viewType);
            LGViewHolder viewHolder = LGViewHolder.getViewHolder(parent, layoutId);
            return viewHolder;
        }
    
        @Override
        public void onBindViewHolder(LGViewHolder holder, final int position) {
            T itemModel = dataList.get(position);
            convert(holder, itemModel, position);//更新itemView视图
            //设置点击监听
            for (int i = 0; i < onClickListeners.size(); ++i){
                int id = onClickListeners.keyAt(i);
                View view = holder.getView(id);
                if(view == null)
                    continue;
                final ItemClickListener listener = onClickListeners.get(id);
                view.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if(listener != null){
                            listener.onItemClicked(v,position);
                        }
                    }
                });
            }
        }
    
        @Override
        public int getItemCount() {
            if (dataList == null)
                return 0;
            return dataList.size();
        }
    
        public void destroyAdapter(){
            if(onClickListeners != null)
                onClickListeners.clear();
            onClickListeners = null;
    
            if(dataList != null)
                dataList.clear();
            dataList = null;
        }
    }
    

    使用方法如下:
    单视图方式:
    定义adapter

    private static class MainAdapter extends LGRecycleViewAdapter<String> {
        ...
    
        @Override
        public int getLayoutId(int viewType) {
            return R.layout.item_view_main1;
        }
    
        @Override
        public void convert(LGViewHolder holder, String s, final int position) {
            TextView textView = (TextView) holder.getView(R.id.id_text);
            textView.setText(s);
        }
    }
    

    在需要时使用:

    mainAdapter = new MainAdapter(datas);
    mainAdapter.setOnItemClickListener(R.id.root, new LGRecycleViewAdapter.ItemClickListener() {
        @Override
        public void onItemClicked(View view, int position) {
            Log.d(TAG,"root clicked..." + position);
        }
    });
    mainAdapter.setOnItemClickListener(R.id.icon, new LGRecycleViewAdapter.ItemClickListener() {
        @Override
        public void onItemClicked(View view, int position) {
            Log.d(TAG,"icon clicked..." + position);
        }
    });
    recyclerView.setAdapter(mainAdapter);
    

    可以看出我们轻松的实现了同一Item视图不同子视图的点击监听,并且在MainAdapter子类中只需通过getLayoutId告诉父类Item视图对应的视图id,并在convert方法中只更新视图即可
    如果需要支持多视图模式则只需在子类中重现getViewType即可,代码如下:
    MainAdapter.java

    @Override
    public int getLayoutId(int viewType) {
        if(viewType == 1)
            return R.layout.item_view_main1;
        return R.layout.item_view_main2;
    }
    
    //支持不同viewType视图
    @Override
    public int getItemViewType(int position) {
        String model = getItem(position);//实际开发中可以通过model的属性来决定返回viewType类型
        if(position % 2 == 0)
            return  1;
        return  2;
    }
    

    写在最后

    为了简单起见,本篇文章设计的adapter适合没有header和footer视图的RecycleView,至于这方面的功能打算在github上更新
    完整的代码及案例可以到我的github下载
    demo开源github地址如下:
    LGRecycleViewAdapter
    欢迎大家访问并star,如果有任何问题可以在评论中加以提问,谢谢~~

    相关文章

      网友评论

      • 简单就好_1897:写的很好,封装的很不错。只是getViewHolder(),这里应该对item的LayoutParam进行一些设置, 否则可能造成一些适配的问题。
        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        itemView.setLayoutParams(lp);

      本文标题:超级简单RecycleViewAdapter的封装-支持多视图,

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