理解RecyclerView(二)—不同类型条目item、头尾布局

前言: 世人总是恐惧失败,但失败了也大不从头再来。

一、概述

  我们在上一篇文章RecyclerView(一)中对RecyclerView使用详细介绍了,在项目中,我们常遇到一个列表中有不同类型的item,RecyclerView.Adapter中还有一个很重要的方法getItemViewType(),它的作用是获取item的类型,在onCreateViewHolder()方法中携带过来的viewType是区分item不同类型的参数,核心方法就在这里。原理就是根据viewType来创建不同的ViewHolder,从而生成不同类型的item。

  • getItemViewType(int position)   根据位置获取itemView的视图类型,默认返回0,position表示item的位置。可以根据不同的唯一标识来区分不同的ViewType。

二、不同类型条目item

2.1 添加ViewHolder

我们这里就只演示两种不同的类型,一种普通的item类型,另一种为广告位类型,那么我们根据这两种类型创建两个ViewHolder,同时需要两个Item的布局View:(源码地址在最后给出)
普通的item类型:item_normal.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout ......
    android:id="@+id/cl_root">
    <ImageView
        ......
        android:id="@+id/iv_head"
        android:src="@mipmap/ic_head"/>
    <TextView
        ......
        android:id="@+id/tv_name"
        android:text="球队名称:"/>
    <TextView
        ......
        android:id="@+id/tv_price"
        android:text="$:30000000"/>
    <TextView
        ......
        android:id="@+id/tv_des"
        android:text="球队描述:灌篮高手灌篮高手灌篮高手"/>
</androidx.constraintlayout.widget.ConstraintLayout>

广告位类型:item_section.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout ......>
    <ImageView 
        ......
        android:id="@+id/iv_bg"
        android:src="@mipmap/flower" />
</LinearLayout>

在adapter中分别创建普通类型和广告位类型的ViewHolder

//普通类型ViewHolder
public class NormalHolder extends RecyclerView.ViewHolder {
    ImageView mIv_head;
    TextView mTv_name;
   
    NormalHolder(@NonNull View itemView) {
        super(itemView);
        mIv_head = itemView.findViewById(R.id.iv_head);
        mTv_name = itemView.findViewById(R.id.tv_name);
    }
}
//广告位类型ViewHolder
public class SectionHolder extends RecyclerView.ViewHolder {
    ImageView mIv_bg;

    SectionHolder(@NonNull View itemView) {
        super(itemView);
        mIv_bg = itemView.findViewById(R.id.iv_bg);
    }
}

ViewHolder需要继承RecyclerView.ViewHolder,然后构造方法传入布局View并需要实现super(itemView),将itemView通过父类方法传给RecyclerView.ViewHolder,通过itemView将控件查找出来,以变量的形式保存到ViewHolder中,这样就可以通过holder实例使用到查找出来的控件。

2.2绑定ViewHolder

首先我们在getItemViewType()中根据item的位置返回不同的类型,这里模拟在position能被4整除的数为特殊类型,其他为普通类型:

    private static final int ITEM_TYPE_NORMAL= 0;//普通类型
    private static final int ITEM_TYPE_SECTION = 1;//特殊类型

	//返回每个数据中的itemType
    @Override
    public int getItemViewType(int position) {
        Goods goods = mData.get(position);
        return goods.getViewType();
    }

定义了两个常量:ITEM_TYPE_NORMAL表示普通类型,ITEM_TYPE_SECTION 表示广告位类型,常量值可以自己设置,能区分不同类型就可以了,最好规范起来,能被4整除的position就显示特殊类型,其他则显示普通类型。

然后在创建ViewHolder的方法onCreateViewHolder(ViewGroup parent, int viewType)中根据viewType不同类型创建不同类型的ViewHolder:

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(mContext);
        //根据不同类型来获取不同的ViewHolder,里面装载不同的布局
        if (viewType == ITEM_TYPE_SECTION) {
            return new SectionHolder(inflater.inflate(R.layout.item_section, parent, false));
        } else {
            return new NormalHolder(inflater.inflate(R.layout.item_normal, parent, false));
        }
    }

如果是普通类型就创建NormalHolder,特殊类型就创建SectionHolder。最后在绑定ViewHolder方法onBindViewHolder()将数据与ViewHolder绑定起来:

 	@Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) {
        if (holder instanceof NormalHolder) {//普通类型ViewHolder
            NormalHolder viewHolder = (NormalHolder) holder;
            viewHolder.mTv_name.setText(mData.get(position));
            viewHolder.mTv_price.setText("$:" + position * 100);
        } else if (holder instanceof SectionHolder) {//特殊类型ViewHolder
            SectionHolder sectionHolder = (SectionHolder) holder;
            sectionHolder.mIv_bg.setImageResource(R.mipmap.flower);
        }
    }

这里可以holder instanceof NormalHolder来判断ViewHolder的类型来处理相关的逻辑数据。那么这里显示不同类型item的例子就完成了,效果如下:

核心原理就是根据viewType来创建不同的ViewHolder,从而生成不同类型的item,这里给出adapter的全部代码:TypeViewAdapter.java

public class TypeViewAdapter extends RecyclerView.Adapter {
    private Context mContext;
    private List<String> mData;
    private static final int ITEM_TYPE_NORMAL = 0;//普通类型
    private static final int ITEM_TYPE_SECTION = 1;//特殊类型

    public TypeViewAdapter(Context context, List<String> stringList) {
        this.mContext = context;
        this.mData = stringList;
    }

    @Override
    public int getItemViewType(int position) {
        Goods goods = mData.get(position);
        return goods.getViewType();
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(mContext);
        //根据不同类型来获取不同的ViewHolder,里面装载不同的布局
        if (viewType == ITEM_TYPE_SECTION) {
            return new SectionHolder(inflater.inflate(R.layout.item_section, parent, false));
        } else {
            return new NormalHolder(inflater.inflate(R.layout.item_normal, parent, false));
        }
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) {
        if (holder instanceof NormalHolder) {//普通类型ViewHolder
            NormalHolder viewHolder = (NormalHolder) holder;
            viewHolder.mTv_name.setText(mData.get(position).getName());
            viewHolder.mTv_price.setText("$:" + position * 100);
            viewHolder.mCl_root.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(mContext, "NormalHolder == " + position, Toast.LENGTH_SHORT).show();
                }
            });
        } else if (holder instanceof SectionHolder) {//特殊类型ViewHolder
            SectionHolder sectionHolder = (SectionHolder) holder;
            sectionHolder.mIv_bg.setImageResource(R.mipmap.flower);
            sectionHolder.mIv_bg.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(mContext, "SectionHolder == " + position, Toast.LENGTH_SHORT).show();
                }
            });
        }
    }

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

    //普通类型ViewHolder
    public class NormalHolder extends RecyclerView.ViewHolder {
        ConstraintLayout mCl_root;
        TextView mTv_name;
        TextView mTv_price;

        NormalHolder(@NonNull View itemView) {
            super(itemView);
            mCl_root = itemView.findViewById(R.id.cl_root);
            mTv_name = itemView.findViewById(R.id.tv_name);
            mTv_price = itemView.findViewById(R.id.tv_price);
        }
    }

    //特殊类型ViewHolder
    public class SectionHolder extends RecyclerView.ViewHolder {
        ImageView mIv_bg;

        SectionHolder(@NonNull View itemView) {
            super(itemView);
            mIv_bg = itemView.findViewById(R.id.iv_bg);
        }
    }
}

TypeViewActivity.java

public class TypeViewActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recyclerview);
       
        RecyclerView recyclerView = findViewById(R.id.recyclerView);
        LinearLayoutManager manager = new LinearLayoutManager(this);
        manager.setOrientation(RecyclerView.VERTICAL);
        recyclerView.setLayoutManager(manager);

        List<Goods> list = new ArrayList<>();
        for (int i = 0; i < 40; i++) {
            Goods goods = new Goods();
            goods.setName("球队名称: " + i);
            //每第四个数据为广告类型,其他为普通类型
            goods.setViewType(i % 4 == 0 ? TypeViewAdapter.ITEM_TYPE_SECTION : TypeViewAdapter.ITEM_TYPE_NORMAL);
            list.add(goods);
        }
        
        TypeViewAdapter adapter = new TypeViewAdapter(this, list);
        recyclerView.setAdapter(adapter);
    }
}

三、添加头尾布局

  在上面根据类型创建不同类型的item,主要原理是根据viewType来创建不同的ViewHolder,从而生成不同类型的item。RecyclerView默认没有像ListView那样提供类似addHeaderView()addFooterView()添加头尾布局的API,那么我们也可以根据这个原理来为RecyclerView添加头布局和尾布局。(源码在文章最后给出)

(1) 首先创建一个adapter,并继承RecyclerView.Adapter,并重写相关方法,首先设置三个item_type,分别是头布局,尾布局,普通类型,在getItemViewType()方法中根据position判断第一个数据返回头布局类型,最后一个数据返回尾布局类型,其他item返回普通类型:

private static final int ITEM_TYPE_NORMAL = 0;//普通类型
private static final int ITEM_TYPE_HEAD = 1;//头布局类型
private static final int ITEM_TYPE_FOOT = 2;//尾部局类型

@Override
public int getItemViewType(int position) {
    if (mHeadView != null && position == 0) {//头布局
        return ITEM_TYPE_HEAD;
    } else if (mFootView != null && position == mData.size() - 1) {//尾部局
        return ITEM_TYPE_FOOT;
    }
    return ITEM_TYPE_NORMAL;//普通类型
}

(2)然后在onCreateViewHolder()方法中根据获取的itemType创建不同类型的 ViewHolder:

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(mContext);
        //根据不同类型来获取不同的ViewHolder,里面装载不同的布局
        if (viewType == ITEM_TYPE_HEAD) {//头布局
            return new RecyclerView.ViewHolder(mHeadView) {};
        } else if (viewType == ITEM_TYPE_FOOT) {//尾部局
            return new RecyclerView.ViewHolder(mFootView) {};
        } else {
            return new NormalHolder(inflater.inflate(R.layout.item_linear, parent, false));
        }
    }

(3) 最后,在onBindViewHolder()实现控件与数据逻辑的绑定,adapter还提供了set()方法提供头布局和尾部局实例:

	@Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) {
        if (holder instanceof NormalHolder) {//普通类型ViewHolder
            NormalHolder viewHolder = (NormalHolder) holder;
            viewHolder.mTv_name.setText(mData.get(position));
        }
    }
    
   public void setHeadView(View headView) {//设置头布局
        mHeadView = headView;
    }
    public void setFootView(View footView) {//设置尾布局
        mFootView = footView;
    }

效果如下:

下面给出代码,在Activity中设置数据并添加头部尾部:HeadFootViewActivity.java

public class HeadFootViewActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recyclerview);
        RecyclerView recyclerView = findViewById(R.id.recyclerView);

        //创建布局管理器-线性布局
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        //设置数据
        List<String> stringList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            stringList.add("第 " + i + " 个item");
        }

        //数据适配器
        HeadFootViewAdapter adapter = new HeadFootViewAdapter(this, stringList);

        //头布局
        View headView = LayoutInflater.from(this).inflate(R.layout.head_banner, recyclerView, false);
        //尾部局
        View footView = LayoutInflater.from(this).inflate(R.layout.foot_loadmore, recyclerView, false);
        
        adapter.setHeadView(headView);//添加头布局
        adapter.setFootView(footView);//添加尾布局

        recyclerView.setAdapter(adapter);
    }
}

adapter的全部代码:HeadFootViewAdapter.java

public class HeadFootViewAdapter extends RecyclerView.Adapter {
    private Context mContext;
    private List<String> mData;

    private View mHeadView;//头布局
    private View mFootView;//尾部局

    private static final int ITEM_TYPE_NORMAL = 0;//普通类型
    private static final int ITEM_TYPE_HEAD = 1;//头布局类型
    private static final int ITEM_TYPE_FOOT = 2;//尾部局类型

    public HeadFootViewAdapter(Context context, List<String> stringList) {
        this.mContext = context;
        this.mData = stringList;
    }

    @Override
    public int getItemViewType(int position) {
        if (mHeadView != null && position == 0) {//头布局
            return ITEM_TYPE_HEAD;
        } else if (mFootView != null && position == mData.size() - 1) {//尾部局
            return ITEM_TYPE_FOOT;
        }
        return ITEM_TYPE_NORMAL;//普通类型
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(mContext);
        //根据不同类型来获取不同的ViewHolder,里面装载不同的布局
        if (viewType == ITEM_TYPE_HEAD) {//头布局
            return new RecyclerView.ViewHolder(mHeadView) {};
        } else if (viewType == ITEM_TYPE_FOOT) {//尾部局
            return new RecyclerView.ViewHolder(mFootView) {};
        } else {
            return new NormalHolder(inflater.inflate(R.layout.item_linear, parent, false));
        }
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) {
        if (holder instanceof NormalHolder) {//普通类型ViewHolder
            NormalHolder viewHolder = (NormalHolder) holder;
            viewHolder.mTv_name.setText(mData.get(position));
        }
    }

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

    //普通类型ViewHolder
    public class NormalHolder extends RecyclerView.ViewHolder {
        TextView mTv_name;

        NormalHolder(@NonNull View itemView) {
            super(itemView);
            mTv_name = itemView.findViewById(R.id.tv_name);
        }
    }

    public void setHeadView(View headView) {
        mHeadView = headView;
        if (mData == null) return;
        mData.add(0, "头布局");
    }

    public void setFootView(View footView) {
        mFootView = footView;
        if (mData == null) return;
        mData.add(mData.size(), "尾布局");
    }
}

头尾布局的布局文件就不给出了,比较简单,如果需要可以在源码中查看。

源码地址

点关注,不迷路


好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是人才

我是suming,感谢各位的支持和认可,您的点赞、评论、收藏【一键三连】就是我创作的最大动力,我们下篇文章见!

如果本篇博客有任何错误,请批评指教,不胜感激 !

要想成为一个优秀的安卓开发者,这里有必须要掌握的知识架构,一步一步朝着自己的梦想前进!Keep Moving!


相关文章:

理解RecyclerView(一)

 ● RecyclerView的基础使用、网格布局、瀑布流布局

理解RecyclerView(二)

 ● RecyclerView的ItemType(不同条目类型)

理解RecyclerView(三)

 ● RecyclerView的ItemDecoration分割线、增删item动画效果、拖拽和侧滑删除功能

理解RecyclerView(四)

 ● RecyclerView的自定义点击事件、万能ViewHolder和Adapter简单封装

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值