RecyclerView实现多type页面

栏目: IOS · Android · 发布时间: 6年前

目录介绍

  • 01.先看看实际需求
  • 02.adapter实现多个type
  • 03.这样写的弊端
  • 04.如何优雅实现adapter封装

好消息

  • 博客笔记大汇总【16年3月到至今】,包括 Java 基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢!
  • 链接地址: https://github.com/yangchong2...
  • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!

01.先看看实际需求

  • 比如一个APP的首页,包含Banner区、广告区、文本内容、图片内容、新闻内容等等。
  • RecyclerView 可以用ViewType来区分不同的item,也可以满足需求,但还是存在一些问题,比如:

    • 1,在item过多逻辑复杂列表界面,Adapter里面的代码量庞大,逻辑复杂,后期难以维护。
    • 2,每次增加一个列表都需要增加一个Adapter,重复搬砖,效率低下。
    • 3,无法复用adapter,假如有多个页面有多个type,那么就要写多个adapter。
    • 4,要是有局部刷新,那么就比较麻烦了,比如广告区也是一个九宫格的RecyclerView,点击局部刷新当前数据,比较麻烦。

02.adapter实现多个type

  • 通常写一个多Item列表的方法

    • 根据不同的ViewType 处理不同的item,如果逻辑复杂,这个类的代码量是很庞大的。如果版本迭代添加新的需求,修改代码很麻烦,后期维护困难。
  • 主要操作步骤

    • 在onCreateViewHolder中根据viewType参数,也就是getItemViewType的返回值来判断需要创建的ViewHolder类型
    • 在onBindViewHolder方法中对ViewHolder的具体类型进行判断,分别为不同类型的ViewHolder进行绑定数据与逻辑处理
  • 代码如下所示

    public class HomePageAdapter extends RecyclerView.Adapter {
        public static final int TYPE_BANNER = 0;
        public static final int TYPE_AD = 1;
    public static final int TYPE_TEXT = 2;
        public static final int TYPE_IMAGE = 3;
        public static final int TYPE_NEW = 4;
        private List<HomePageEntry> mData;
    
        public void setData(List<HomePageEntry> data) {
            mData = data;
        }
    
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            switch (viewType){
                case TYPE_BANNER:
                    return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_banner_layout,null));
                case TYPE_AD:
                    return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_ad_item_layout,null));
                case TYPE_TEXT:
                    return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_text_item_layout,null));
                case TYPE_IMAGE:
                    return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_image_item_layout,null));
                case TYPE_NEW:
                    return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_news_item_layout,null));
            }
            return null;
        }
    
        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            int type = getItemViewType(position);
            switch (type){
                case TYPE_BANNER:
                    // banner 逻辑处理
                    break;
                case TYPE_AD:
                    // 广告逻辑处理
                    break;
                case TYPE_TEXT:
                    // 文本逻辑处理
                    break;
                case TYPE_IMAGE:
                   //图片逻辑处理
                    break;
                case TYPE_NEW:
                    //视频逻辑处理
                    break;
                // ... 此处省去N行代码
            }
        }
    
        @Override
        public int getItemViewType(int position) {
            if(position == 0){
                return TYPE_BANNER;//banner在开头
            }else {
                return mData.get(position).type;//type 的值为TYPE_AD,TYPE_IMAGE,TYPE_AD,等其中一个
            }
    
        }
    
        @Override
        public int getItemCount() {
            return mData == null ? 0:mData.size();
        }
    
    
    
        public static class BannerViewHolder extends RecyclerView.ViewHolder{
    
            public BannerViewHolder(View itemView) {
                super(itemView);
                //绑定控件
            }
        }
    
        public static class NewViewHolder extends RecyclerView.ViewHolder{
    
            public VideoViewHolder(View itemView) {
                super(itemView);
                //绑定控件
            }
        }
        public static class AdViewHolder extends RecyclerView.ViewHolder{
    
            public AdViewHolder(View itemView) {
                super(itemView);
                //绑定控件
            }
        }
        public static class TextViewHolder extends RecyclerView.ViewHolder{
    
            public TextViewHolder(View itemView) {
                super(itemView);
                //绑定控件
            }
        }
        public static class ImageViewHolder extends RecyclerView.ViewHolder{
    
            public ImageViewHolder(View itemView) {
                super(itemView);
                //绑定控件
            }
        }
    }

03.这样写的弊端

  • 上面那样写的弊端

    • 类型检查与类型转型,由于在onCreateViewHolder根据不同类型创建了不同的ViewHolder,所以在onBindViewHolder需要针对不同类型的ViewHolder进行数据绑定与逻辑处理,这导致需要通过instanceof对ViewHolder进行类型检查与类型转型。
    • 不利于扩展,目前的需求是列表中存在5种布局类类型,那么如果需求变动,极端一点的情况就是数据源是从服务器获取的,数据中的model决定列表中的布局类型。这种情况下,每当model改变或model类型增加,我们都要去改变adapter中很多的代码,同时Adapter还必须知道特定的model在列表中的位置(position)除非跟服务端约定好,model(位置)不变,很显然,这是不现实的。
    • 不利于维护,这点应该是上一点的延伸,随着列表中布局类型的增加与变更,getItemViewType、onCreateViewHolder、onBindViewHolder中的代码都需要变更或增加,Adapter 中的代码会变得臃肿与混乱,增加了代码的维护成本。

04.如何优雅实现adapter封装

  • 核心目的就是三个

    • 避免类的类型检查与类型转型
    • 增强Adapter的扩展性
    • 增强Adapter的可维护性
  • 当列表中类型增加或减少时Adapter中主要改动的就是getItemViewType、onCreateViewHolder、onBindViewHolder这三个方法,因此,我们就从这三个方法中开始着手。
  • 既然可能存在多个type类型的view,那么能不能把这些比如banner,广告,文本,视频,新闻等当做一个HeaderView来操作。
  • 在getItemViewType方法中。

    • 减少if之类的逻辑判断简化代码,可以简单粗暴的用hashCode作为增加type标识。
    • 通过创建列表的布局类型,同时返回的不再是简单的布局类型标识,而是布局的hashCode值
    private ArrayList<InterItemView> headers = new ArrayList<>();
        
    public interface InterItemView {
    
        /**
         * 创建view
         * @param parent            parent
         * @return                  view
         */
        View onCreateView(ViewGroup parent);
        
        /**
         * 绑定view
         * @param headerView        headerView
         */
        void onBindView(View headerView);
    }
        
    /**
     * 获取类型,主要作用是用来获取当前项Item(position参数)是哪种类型的布局
     * @param position                      索引
     * @return                              int
     */
    @Deprecated
    @Override
    public final int getItemViewType(int position) {
        if (headers.size()!=0){
            if (position<headers.size()) {
                return headers.get(position).hashCode();
            }
        }
        if (footers.size()!=0){
            int i = position - headers.size() - mObjects.size();
            if (i >= 0){
                return footers.get(i).hashCode();
            }
        }
        return getViewType(position-headers.size());
    }
  • onCreateViewHolder

    • getItemViewType返回的是布局hashCode值,也就是onCreateViewHolder(ViewGroup parent, int viewType)参数中的viewType
    /**
     * 创建viewHolder,主要作用是创建Item视图,并返回相应的ViewHolder
     * @param parent                        parent
     * @param viewType                      type类型
     * @return                              返回viewHolder
     */
    @NonNull
    @Override
    public final BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = createViewByType(parent, viewType);
        if (view!=null){
            return new BaseViewHolder(view);
        }
        final BaseViewHolder viewHolder = OnCreateViewHolder(parent, viewType);
        setOnClickListener(viewHolder);
        return viewHolder;
    }
    
    private View createViewByType(ViewGroup parent, int viewType){
        for (InterItemView headerView : headers){
            if (headerView.hashCode() == viewType){
                View view = headerView.onCreateView(parent);
                StaggeredGridLayoutManager.LayoutParams layoutParams;
                if (view.getLayoutParams()!=null) {
                    layoutParams = new StaggeredGridLayoutManager.LayoutParams(view.getLayoutParams());
                } else {
                    layoutParams = new StaggeredGridLayoutManager.LayoutParams(
                            ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                }
                layoutParams.setFullSpan(true);
                view.setLayoutParams(layoutParams);
                return view;
            }
        }
        for (InterItemView footerView : footers){
            if (footerView.hashCode() == viewType){
                View view = footerView.onCreateView(parent);
                StaggeredGridLayoutManager.LayoutParams layoutParams;
                if (view.getLayoutParams()!=null) {
                    layoutParams = new StaggeredGridLayoutManager.LayoutParams(view.getLayoutParams());
                } else {
                    layoutParams = new StaggeredGridLayoutManager.LayoutParams(
                            ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                }
                layoutParams.setFullSpan(true);
                view.setLayoutParams(layoutParams);
                return view;
            }
        }
        return null;
    }
  • 在onBindViewHolder方法中。可以看到,在此方法中,添加一种header类型的view,则通过onBindView进行数据绑定。

    /**
     * 绑定viewHolder,主要作用是绑定数据到正确的Item视图上。当视图从不可见到可见的时候,会调用这个方法。
     * @param holder                        holder
*/
@Override
public final void onBindViewHolder(BaseViewHolder holder, int position) {
    holder.itemView.setId(position);
    if (headers.size()!=0 && position<headers.size()){
        headers.get(position).onBindView(holder.itemView);
        return ;
    }

    int i = position - headers.size() - mObjects.size();
    if (footers.size()!=0 && i>=0){
        footers.get(i).onBindView(holder.itemView);
        return ;
    }
    OnBindViewHolder(holder,position-headers.size());
}
```
  • 如何使用,如下所示,这个就是banner类型,可以说是解耦了之前adapter中复杂的操作

    InterItemView interItemView = new InterItemView() {
        @Override
        public View onCreateView(ViewGroup parent) {
            BannerView header = new BannerView(HeaderFooterActivity.this);
            header.setHintView(new ColorPointHintView(HeaderFooterActivity.this,
                    Color.YELLOW, Color.GRAY));
            header.setHintPadding(0, 0, 0, (int) AppUtils.convertDpToPixel(
                    8, HeaderFooterActivity.this));
            header.setPlayDelay(2000);
            header.setLayoutParams(new RecyclerView.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    (int) AppUtils.convertDpToPixel(200, HeaderFooterActivity.this)));
            header.setAdapter(new BannerAdapter(HeaderFooterActivity.this));
            return header;
        }
    
        @Override
        public void onBindView(View headerView) {
    
        }
    };
    adapter.addHeader(interItemView);
  • 封装后好处

    • 拓展性——Adapter并不关心不同的列表类型在列表中的位置,因此对于Adapter来说列表类型可以随意增加或减少。十分方便,同时设置类型view的布局和数据绑定都不需要在adapter中处理。充分解耦。
    • 可维护性——不同的列表类型由adapter添加headerView处理,哪怕添加多个headerView,相互之间互不干扰,代码简洁,维护成本低。

其他介绍

01.关于博客汇总链接


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Beautiful Code

Beautiful Code

Greg Wilson、Andy Oram / O'Reilly Media / 2007-7-6 / GBP 35.99

In this unique work, leading computer scientists discuss how they found unusual, carefully designed solutions to difficult problems. This book lets the reader look over the shoulder of major coding an......一起来看看 《Beautiful Code》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器