Android RecyclerView多类型布局卡片解决方案

作者:luokanghui 时间:2022-06-18 17:39:09 

背景

随着公司业务越来越复杂,在同一个列表中需要展示各种类型的数据。

总体结构

Android RecyclerView多类型布局卡片解决方案

  • ItemViewAdapter: 每种类型的卡片分别都是不同的ItemViewAdapter

  • ItemViewAdapterFactory: 使用ItemViewAdapterFactory根据不同数据对应不同的ItemViewAdapter

  • MultiRecyclerViewAdapter: MultiRecyclerViewAdapter就是RecylerView.Adapter,并是个ItemViewAdapterFactory。

  • 具体只要继承MultiRecyclerViewAdapter即可,实现ItemViewAdapterFactory中getViewType、onCreateItemViewAdapter两个方法

  • ContextMap: 整个Adapter共用一个ContextMap数据上下文,用于外部(例Fragment等)与ItemAdapter交互、ItemAdapter之间交互等一系列数据传递,可以解决参数层层传递的问题

  • RecyclerViewHolder: 通用RecyclerView.ViewHolder,封装根据id获取view方法getView(viewId)、获取数据上下文方法getContextMap()

使用方法

每种类型卡片Item都实现ItemViewAdapter


package com.lkh.multiadapter;

import android.support.annotation.LayoutRes;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.List;

/**
* 列表单项布局与数据绑定
* Created by luokanghui on 2017/5/24.
*/
public abstract class ItemViewAdapter<E>{

/**
  * 返回列表单项View,如果View由资源layout加载而来,直接重写{@link #onGetLayoutId()}即可
  * @param parent 父view,一般为RecyclerView
  * @return 列表单项View
  */
 public View onCreateView(ViewGroup parent){
   return LayoutInflater.from(parent.getContext()).inflate(onGetLayoutId()
       , parent, false);
 }

/**
  * 当RecyclerViewHolder创建成功后调用,只会调用一次
  * @param viewHolder 单项view集合
  */
 public void onCreate(RecyclerViewHolder viewHolder){

}

/**
  * 返回单项布局的资源id,如果重写了{@link #onCreateView(ViewGroup)},则此方法可能失效
  * @return 单项布局layout id
  */
 @LayoutRes
 protected abstract int onGetLayoutId();

/**
  * 把数据与view进行绑定,滑动时都会调用
  * @param viewHolder 单项view集合
  * @param data 具体数据
  * @param position 在列表中的位置
  */
 public abstract void bindData(RecyclerViewHolder viewHolder, E data, int position);

/**
  * 局部更新时调用
  * @param viewHolder 单项view集合
  * @param data 具体数据
  * @param position 在列表中的位置
  * @param payloads 局部更新标志,不会为空(isEmpty()==false)
  */
 public void bindData(RecyclerViewHolder viewHolder, E data, int position, List<Object> payloads){

}
}

卡片1:


package com.lkh.multiadapter.sample;

import android.widget.TextView;

import com.lkh.multiadapter.ItemViewAdapter;
import com.lkh.multiadapter.R;
import com.lkh.multiadapter.RecyclerViewHolder;

/**
* 卡片1实现
* Created by luokanghui on 2019/3/18
*/
public class SampleOneItemViewAdapter extends ItemViewAdapter<DataOne> {
 @Override
 protected int onGetLayoutId() {
   //布局layout资源id
   return R.layout.item_one;
 }

@Override
 public void bindData(RecyclerViewHolder viewHolder, DataOne data, int position) {
   //根据id获取view
   TextView tvContent = viewHolder.getView(R.id.tv_content);
   //数据绑定
   tvContent.setText(data.getContent());
 }
}


package com.lkh.multiadapter.sample;

/**
* 卡片1数据
* Created by luokanghui on 2019/3/18
*/
public class DataOne {
 private String content;

public String getContent() {
   return content;
 }

public void setContent(String content) {
   this.content = content;
 }
}

item_one.xml:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:background="#eeeeee"
 android:orientation="vertical">

<TextView
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="卡片1"
   android:textColor="#000000" />

<TextView
   android:id="@+id/tv_content"
   android:layout_width="wrap_content"
   android:layout_height="50dp"
   android:gravity="center"
   android:textColor="#000000" />

</LinearLayout>

卡片2


package com.lkh.multiadapter.sample;

import android.widget.TextView;

import com.lkh.multiadapter.ItemViewAdapter;
import com.lkh.multiadapter.R;
import com.lkh.multiadapter.RecyclerViewHolder;

/**
* 卡片2实现
* Created by luokanghui on 2019/3/18
*/
public class SampleTwoItemViewAdapter extends ItemViewAdapter<DataTwo> {
 @Override
 protected int onGetLayoutId() {
   //布局layout资源id
   return R.layout.item_two;
 }

@Override
 public void bindData(RecyclerViewHolder viewHolder, DataTwo data, int position) {
   //根据id获取view
   TextView tvNum = viewHolder.getView(R.id.tv_num);
   //数据绑定
   tvNum.setText("num="+data.getNum());
 }
}

package com.lkh.multiadapter.sample;

/**
* 卡片2数据
* Created by luokanghui on 2019/3/18
*/
public class DataTwo {
 private int num;

public int getNum() {
   return num;
 }

public void setNum(int num) {
   this.num = num;
 }
}

item_two.xml:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:background="#999999"
 android:orientation="vertical">

<TextView
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="卡片2"
   android:textColor="#0000ff" />

<TextView
   android:id="@+id/tv_num"
   android:layout_width="wrap_content"
   android:layout_height="100dp"
   android:gravity="center"
   android:textColor="#0000ff" />

</LinearLayout>

总Adapter,继承MultiRecyclerViewAdapter


package com.lkh.multiadapter.sample;

import com.lkh.multiadapter.ItemViewAdapter;
import com.lkh.multiadapter.MultiRecyclerViewAdapter;

/**
* 多布局adapter,根据不同data及position,使用不同ItemViewAdapter卡片
* Created by luokanghui on 2019/3/18
*/
public class SampleMultiAdapter extends MultiRecyclerViewAdapter<Object> {
 private static final int TYPE_EMPTY = 0;//空item
 private static final int TYPE_ONE = 1;//卡片1
 private static final int TYPE_TWO = 2;//卡片2

@Override
 public int getViewType(Object data, int position) {
   if (data instanceof DataOne){//卡片1
     return TYPE_ONE;
   }

if (data instanceof DataTwo){//卡片2
     return TYPE_TWO;
   }

return TYPE_EMPTY;//空item
 }

@Override
 public ItemViewAdapter onCreateItemViewAdapter(int viewType) {
   switch (viewType){
     case TYPE_ONE://卡片1
       return new SampleOneItemViewAdapter();
     case TYPE_TWO://卡片2
       return new SampleTwoItemViewAdapter();
     default://空item
       return new EmptyItemViewAdapter();
   }
 }
}

RecyclerView中使用


package com.lkh.multiadapter;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

import com.lkh.multiadapter.sample.DataOne;
import com.lkh.multiadapter.sample.DataTwo;
import com.lkh.multiadapter.sample.SampleMultiAdapter;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

@Override
 protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);

RecyclerView recyclerView = findViewById(R.id.recycler_view);
   recyclerView.setLayoutManager(new LinearLayoutManager(this));

SampleMultiAdapter adapter = new SampleMultiAdapter();

//设置数据
   adapter.setData(generateData());

//设置adapter
   recyclerView.setAdapter(adapter);

}

//造测试数据
 private List<Object> generateData(){
   List<Object> list = new ArrayList<>();
   for (int i=0; i<20; i++){
     DataOne dataOne = new DataOne();
     dataOne.setContent("这是卡片1数据:"+i);
     list.add(dataOne);

DataTwo dataTwo = new DataTwo();
     dataTwo.setNum(i);
     list.add(dataTwo);
   }
   return list;
 }
}

activity_main.xml:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="vertical">

<android.support.v7.widget.RecyclerView
   android:id="@+id/recycler_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"/>

</LinearLayout>

运行效果如下:

Android RecyclerView多类型布局卡片解决方案

总的来说,实现一个多类型布局列表只需要写多个不同卡片ItemViewAdapter、继承MultiRecyclerViewAdapter用来控制不同数据使用不同ItemViewAdapter,新增一个卡片只需要新增一个ItemViewAdapter,在MultiRecyclerViewAdapter新加一项即可,不会影响其它卡片使用,而且ItemViewAdapter完全独立,可以很好的复用。

核心代码


package com.lkh.multiadapter;

import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;

import java.util.List;

/**
* 多种布局adapter
* Created by luokanghui on 2017/5/24.
*/

public abstract class MultiRecyclerViewAdapter<E> extends RecyclerView.Adapter<RecyclerViewHolder> implements ItemViewAdapterFactory<E> {
 public static final int NO_TYPE = -1;

private List<E> dataList;

protected final MapData mMapData = new MapData();

public MultiRecyclerViewAdapter setData(List<E> list) {
   this.dataList = list;
   return this;
 }

@Override
 public RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
   ItemViewAdapter itemViewModule = onCreateItemViewAdapter(viewType);
   RecyclerViewHolder recyclerViewHolder = new RecyclerViewHolder(itemViewModule.onCreateView(parent), itemViewModule, this, getContextMap());
   itemViewModule.onCreate(recyclerViewHolder);
   return recyclerViewHolder;
 }

@Override
 public void onBindViewHolder(RecyclerViewHolder holder, int position) {
   if (checkItems(position)) {
     return;
   }
   holder.itemViewAdapter.bindData(holder, dataList.get(position), position);
 }

@Override
 public void onBindViewHolder(RecyclerViewHolder holder, int position, List<Object> payloads) {
   if (checkItems(position)) {
     return;
   }
   if (payloads.isEmpty()) {
     super.onBindViewHolder(holder, position, payloads);
   } else {
     holder.itemViewAdapter.bindData(holder, dataList.get(position), position, payloads);
   }
 }

@Override
 public int getItemViewType(int position) {
   if (checkItems(position)) {
     return NO_TYPE;
   }
   return getViewType(dataList.get(position), position);
 }

@Override
 public int getItemCount() {
   return dataList == null ? 0 : dataList.size();
 }

/**
  * true表示没通过
  */
 private boolean checkItems(int position) {
   return dataList == null || position < 0 || position >= dataList.size();
 }

@Override
 public MapData getContextMap() {
   return mMapData;
 }
}

package com.lkh.multiadapter;
/**
* 多布局ItemViewAdapter创建者
* Created by luokanghui on 2017/5/24.
*/
public interface ItemViewAdapterFactory<E> {

/**
  * 返回ItemViewAdapter的类型
  * 建议根据data的数据类型判断不同的viewType
  * @param data 具体数据
  * @param position 在列表中的位置
  * @return 类型
  */
 int getViewType(E data, int position);

/**
  * 根据不同的viewType返回不同的ItemViewAdapter
  * @param viewType 类型
  * @return ItemViewAdapter
  */
 ItemViewAdapter<? extends E> onCreateItemViewAdapter(int viewType);

/**
  * 上下文数据
  * @return
  */
 MapData getContextMap();
}

package com.lkh.multiadapter;

import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import android.view.View;

/**
* ViewHolder基类
*/
public final class RecyclerViewHolder extends RecyclerView.ViewHolder {

private final SparseArray<View> views;
 ItemViewAdapter itemViewAdapter;
 private final RecyclerView.Adapter adapter;
 private final MapData mMapData ;

public RecyclerViewHolder(View itemView, ItemViewAdapter itemViewAdapter, RecyclerView.Adapter adapter, MapData mapData) {
   super(itemView);
   this.views = new SparseArray<>();
   this.itemViewAdapter = itemViewAdapter;
   this.adapter = adapter;
   this.mMapData = mapData;
 }

/**
  * 根据id获取view,如果缓存中存在,直接使用缓存中的,避免重复执行findViewById
  */
 @SuppressWarnings("unchecked")
 public <T extends View> T getView(int viewId) {
   View view = views.get(viewId);
   if (view == null) {
     view = itemView.findViewById(viewId);
     views.put(viewId, view);
   }
   return (T) view;
 }

public RecyclerView.Adapter getAdapter(){
   return adapter;
 }

/**
  * 获取数据上下文
  */
 public MapData getContextMap(){
   return mMapData;
 }
}

package com.lkh.multiadapter;

import android.support.annotation.LayoutRes;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import java.util.List;

/**
* 列表单项布局与数据绑定
* Created by luokanghui on 2017/5/24.
*/

public abstract class ItemViewAdapter<E>{

/**
  * 返回列表单项View,如果View由资源layout加载而来,直接重写{@link #onGetLayoutId()}即可
  * @param parent 父view,一般为RecyclerView
  * @return 列表单项View
  */
 public View onCreateView(ViewGroup parent){
   return LayoutInflater.from(parent.getContext()).inflate(onGetLayoutId()
       , parent, false);
 }

/**
  * 当RecyclerViewHolder创建成功后调用,只会调用一次
  * @param viewHolder 单项view集合
  */
 public void onCreate(RecyclerViewHolder viewHolder){

}

/**
  * 返回单项布局的资源id,如果重写了{@link #onCreateView(ViewGroup)},则此方法可能失效
  * @return 单项布局layout id
  */
 @LayoutRes
 protected abstract int onGetLayoutId();

/**
  * 把数据与view进行绑定,滑动时都会调用
  * @param viewHolder 单项view集合
  * @param data 具体数据
  * @param position 在列表中的位置
  */
 public abstract void bindData(RecyclerViewHolder viewHolder, E data, int position);

/**
  * 局部更新时调用
  * @param viewHolder 单项view集合
  * @param data 具体数据
  * @param position 在列表中的位置
  * @param payloads 局部更新标志,不会为空(isEmpty()==false)
  */
 public void bindData(RecyclerViewHolder viewHolder, E data, int position, List<Object> payloads){

}
}

来源:https://juejin.im/post/5c8f01f8e51d456bcb58d3af

标签:Android,RecyclerView,多布局
0
投稿

猜你喜欢

  • 聊聊SpringMVC项目依赖和静态资源导出问题

    2023-03-26 13:32:20
  • Tomcat内存溢出分析及解决方法

    2023-11-12 23:24:47
  • Java自定义注解用法实例小结

    2023-03-26 09:13:51
  • java中匿名内部类详解

    2022-10-06 14:56:56
  • java实现订餐系统

    2023-08-12 04:39:06
  • android中强制更新app实例代码

    2023-05-23 18:42:34
  • SQL Server中的数据复制到的Access中的函数

    2021-10-05 16:06:42
  • c#反射表达式树模糊搜索示例

    2022-01-09 07:03:31
  • Springboot WebFlux集成Spring Security实现JWT认证的示例

    2021-06-02 03:24:50
  • C#实现实体类和XML相互转换

    2023-06-16 04:37:32
  • 使用Enumeration和Iterator遍历集合类详解

    2023-01-05 11:57:51
  • springboot 如何配置多个jndi数据源

    2023-03-13 16:28:07
  • 一文带你深入了解Java泛型

    2022-02-10 05:38:02
  • Idea中SpringBoot多模块项目的建立实现

    2023-11-08 07:52:34
  • C#取得Web程序和非Web程序的根目录的N种取法总结

    2023-07-16 07:37:32
  • Android ListView UI组件使用说明

    2022-06-14 16:04:09
  • Java上传视频实例代码

    2023-06-24 04:17:45
  • 基于Java Springboot + Vue + MyBatis实现音乐播放系统

    2023-07-09 16:01:41
  • java自定义封装StringUtils常用工具类

    2022-09-01 05:11:13
  • java启动参数之谜的排查过程

    2023-02-18 19:47:50
  • asp之家 软件编程 m.aspxhome.com