RecyclerView实现复杂布局

来源:互联网 发布:上海行知教育教学网点 编辑:程序博客网 时间:2024/05/18 17:23

在上一篇中我们简单的说了一下RecyclerView的基本使用方法,这一篇我们来说说如何去利用RecyclerView来实现复杂布局。为什么要实现复杂布局呢?因为我们在实际的项目开发中肯定不可能像之前写的Demo那样,Item里面就一个TextView,产品经理肯定会给我们提出各种各样的神奇需求(工作过的哥们都知道),所以实现复杂布局很有必要。先来看几个基本的知识点,如下:

1.先来看一下RecyclerView的使用场景:多种多样的列表、宫格和列表同时存在、分类列表比如通讯录

2.多种布局的保存:Type<---getItemViewType(int position)、RecyclerView.Holder(类比ListView的内部类Holder)、RecyclerView.Recycler(保存了一些缓存的机制,类比之前ListView中的convertView)

3.getItemViewType的作用:ItemType会保存在Holder内部类中、Holder根据position被缓存在cache中(当我们需要复用的时候,系统会在cache中拿holder,进而实现使用流畅)、

遍历缓存中的Holder,如果Type一致就返回当前的Holder

4.RecyclerView.Holder的简介:在RV中保存View的单位(它是包装了一个View,因为View是在Holder中的,ListView里面保存的是View,RV中保存的是Holder,这就是它们的区别)、记录在RV中的基本信息、需要一个是否被缓存的Flag标志

5.RecyclerView.Recycler的简介:RV中会缓存一些Holder,这些Holder会保存在一个map里面,这个map就是放在Recycler这个内部类中,多个RV会公用一个RecycleredPool,这个缓存池是一个静态内部类,Recycler中会配置一些缓存Size,里面有一个默认的缓存Size和一个公用的Pool的Size

6.和ListView的区别:Type已经是Holder的成员了、RV的缓存单位是Holder而不再是View了、RecycleredPool的缓存key是Type(如果Holder被缓存了,则是直接根据Type去寻找)

下面我们通过代码来看一下如何具体的实现多种Item的列表展示,首先来看列表Item的布局文件:

item_type_one.xml文件中的代码如下,里面放有一个ImageView和一个TextView:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="60dp"    android:background="@android:color/white"    android:orientation="horizontal"    android:gravity="center_vertical">    <ImageView        android:id="@+id/avatar"        android:layout_width="40dp"        android:layout_height="40dp"        android:layout_marginLeft="20dp"/>    <TextView        android:id="@+id/name"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        tools:text="Archie"        android:layout_marginLeft="20dp"/></LinearLayout>
item_type_two.xml文件中的代码如下,里面放有一个ImageView和两个TextView:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="60dp"    android:background="@android:color/white"    android:orientation="horizontal"    android:gravity="center_vertical">    <ImageView        android:id="@+id/avatar"        android:layout_width="40dp"        android:layout_height="40dp"        android:layout_marginLeft="20dp"/>    <LinearLayout        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:orientation="vertical">        <TextView            android:id="@+id/name"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            tools:text="Archie"            android:layout_marginLeft="20dp"/>        <TextView            android:id="@+id/content"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            tools:text="内容"            android:layout_marginLeft="20dp"            android:layout_marginTop="5dp"/>    </LinearLayout></LinearLayout>
item_type_three.xml文件中的代码如下,里面有两个ImageView和两个TextView:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="60dp"    android:background="@android:color/white">    <ImageView        android:id="@+id/avatar"        android:layout_width="40dp"        android:layout_height="40dp"        android:layout_centerVertical="true"        android:layout_marginLeft="20dp" />    <LinearLayout        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_centerVertical="true"        android:layout_toRightOf="@+id/avatar"        android:orientation="vertical">        <TextView            android:id="@+id/name"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_marginLeft="20dp"            tools:text="Archie" />        <TextView            android:id="@+id/content"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_marginLeft="20dp"            android:layout_marginTop="5dp"            tools:text="内容" />    </LinearLayout>    <ImageView        android:id="@+id/contentImage"        android:layout_width="80dp"        android:layout_height="45dp"        android:layout_alignParentRight="true"        android:layout_centerVertical="true"        android:layout_marginRight="20dp" /></RelativeLayout>
接着我们去创建我们的数据实体,大多数情况下数据返回的都是有多种Type的,意思就是服务器返回给我们的数据是多种列表的(比如说数据源有list1,list2,list3,并且每一个集合中的字段还都不一样),这里我在代码中也给出了基于一个ViewModel去做类型的分发的实现方式,这里我就不讲这种实现方式了(最基本的实现方式),我在这里来讲讲基于多个Model的实现方式,这种方式的Model里面我们不做Type的处理,只给出数据字段,具体代码如下:

DataModelOne.java的代码,对应着第一种布局:

public class DataModelOne {    public int avatarColor;    public String name;}
DataModelTwo.java的代码,对应着第二种布局:

public class DataModelTwo {    public int avatarColor;    public String name;    public String content;}
DataModelThree.java的代码,对应着第三种布局:

public class DataModelThree {    public int avatarColor;    public String name;    public String content;    public int contentColor;}
好了,接下来我们要进行的是我们的重中之重,就是Adapter中的处理,首先我们在我们的Adapter中创建了一个addListByType的方法,在方法里面我们传递两个参数,一个是type,一个是list,作用就是把List根据Type去分配它的position,在实现这个方法前我们还需要创建两个数据集合,代码如下:

private List<Integer> types = new ArrayList<>();private Map<Integer,Integer> mPos = new HashMap<>();
第一个types就相当于每个位置的type,这样的话get到每一个position都能拿到一个type,getItemViewType里面每一个position上的位置都能记录的很清楚,具体代码为:

@Overridepublic int getItemViewType(int position) {    return types.get(position);}
@Overridepublic int getItemCount() {    return types.size();}
第二个mPos用来记录当前每一个List在types的起始位置,这样就可以来实现我们的addListByType方法了,具体实现为:

private void addListByType(int type,List list){    mPos.put(type,types.size());    for (int i =0;i<list.size();i++){        types.add(type);    }}
然后我们在Adapter中开始创建我们的类型,这里定义为静态常量(实际项目中也是这样),具体代码为:

public static final int TYPE_ONE = 1;public static final int TYPE_TWO = 2;public static final int TYPE_THREE = 3;
然后我们来定义一个方法去调用addListByType进行数据的添加,这里创建方法addList,参数为不同类型的数据集合,代码为:

public void addList(List<DataModelOne> list1,                     List<DataModelTwo> list2,                     List<DataModelThree> list3){    addListByType(TYPE_ONE,list1);    addListByType(TYPE_TWO,list2);    addListByType(TYPE_THREE,list3);    this.list1 = list1;    this.list2 = list2;    this.list3 = list3;}

好了做完这些以后,下面我们来看一下我们的ViewHolder该如何实现呢?这里我们定义一个抽象的父类,类中定义两个方法,一个是构造方法,一个是抽象的bindHolder方法,这里用到了泛型(泛型擦除),这样便于子类在继承之后可以指定它自己的泛型类型,这里分别为三种类型的Item创建三个ViewHolder类,分别为TypeOneViewHolder、TypeTwoViewHolder、TypeThreeViewHolder,这里实现的原理都是一样的,我给出其中一个的代码,其它的大家自己可以下载源码后自行查看:

public class TypeOneViewHolder extends         TypeAbstractViewHolder<DataModelOne>{    public ImageView avatar;    public TextView name;    public TypeOneViewHolder(View itemView) {        super(itemView);        avatar = (ImageView) itemView.findViewById(R.id.avatar);        name = (TextView) itemView.findViewById(R.id.name);        itemView.setBackgroundColor(Color.YELLOW);    }    @Override    public void bindHolder(DataModelOne model) {        avatar.setBackgroundResource(model.avatarColor);        name.setText(model.name);    }}

好,ViewHolder准备好了之后,下面我们来看适配器中的onCreateViewHolder和onBindViewHolder这两个方法的实现,onCreateViewHolder中很简单,就是加载我们的View,具体实现为:

@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent,                                                  int viewType) {    switch (viewType) {        case TYPE_ONE:            return new TypeOneViewHolder(mInflater.inflate(                    R.layout.item_type_one, parent, false));        case TYPE_TWO:            return new TypeTwoViewHolder(mInflater.inflate(                    R.layout.item_type_two, parent, false));        case TYPE_THREE:            return new TypeThreeViewHolder(mInflater.inflate(                    R.layout.item_type_three, parent, false));    }    return null;}

重点是来看我们的onBindViewHolder,这个方法是进行数据的绑定,这里我们先去获取viewType,拿到每一个position的Type,然后再去拿一个realPosition,这个值就是传进来的position减去我们拿到的起始位置的position,这样计算出来的realPosition就是对应每个列表中的position,最后通过不同的类型去绑定数据,具体实现为:

@Overridepublic void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {    int viewType = getItemViewType(position);    int realPosition = position - mPos.get(viewType);    switch (viewType) {        case TYPE_ONE:            ((TypeOneViewHolder) holder)                    .bindHolder(list1.get(realPosition));            break;        case TYPE_TWO:            ((TypeTwoViewHolder) holder)                    .bindHolder(list2.get(realPosition));            break;        case TYPE_THREE:            ((TypeThreeViewHolder) holder)                    .bindHolder(list3.get(realPosition));            break;    }}
这样我们就完成了利用RecyclerView去实现多种Item布局的操作,这里给出项目案例的源码地址,本篇的实现参见工程中的ComplexRVDemo这个model,地址为:https://github.com/JArchie/RecyclerViewDemo,谢谢大家,欢迎各位的指正!大笑
0 0