一种优雅的方式实现RecyclerView条目多类型

2024-03-06 01:40

本文主要是介绍一种优雅的方式实现RecyclerView条目多类型,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

下面以一个故事来讲来说明这中方式是怎么实现的。

放弃vlayout
大家都了解一般首页是非常复杂的,去年初项目引入vlayout来解决首页复杂布局的问题,后来对vlayout和databinding结合进行了封装,使用起来更方便简单,不仅首页使用,很多页面都在用,还封装了单纯列表样式的Activity,刷新加载的Activity,这样很开心的过了很久。由于vlayout项目一直比较活跃,在满足各种各样的需求上一直在打补丁,我也是一直在把它更新为最新版本,直到又一次更新我的的列表不显示内容了,经过一上午的排查,找到了问题。是在合并一个同学的PR时引入的,当时我还提了个issue 升级后出现onBindViewHolder未分发的问题,并给作者提了建议,加强Code Review,其实这时候我就没有那么happy了。

在一次需求中,PM提出了可以删除列表中某一条目的需求,在之前封装的基础上很简单就实现了。这时想加一个移除的动画吧,让APP活泼点,不是那么生硬。这可难住了我,一上午硬是没搞出来,在别的同学的issue 怎么正确的使用notifyItemRemoved,正是这个问题,使得我有了放弃使用vlayout的想法。不禁问自己,我为什么要使用它,没错就是为了使复杂布局更方便管理,现在看来有悖于初衷。也许vlayout有删除动画的简单实现方式,而我没有找到,但是我决定不再使用它。

寻找轮子
放弃之后面临的另一个问题是需求还是要做,项目还是要按时上线,冒出了第一个想法是找找其他的轮子吧,MultiItem github上的介绍是一个优雅的实现多类型的RecyclerView类库,窃喜,这不正是我想要的。他的思想是给BaseItemAdapter(设置给RecyclerView)注册一系列的Adapter,然后根据需要处理的类来区分是要选择哪个被代理的Adapter。

// 初始化adapter
BaseItemAdapter adapter = new BaseItemAdapter();
// 为TextBean数据源注册ViewHolderManager管理类
adapter.register(TextBean.class, new TextViewManager());
// 为更多数据源注册ViewHolderManager管理类
adapter.register(ImageTextBean.class, new ImageAndTextManager());
adapter.register(ImageBean.class, new ImageViewManager());
// 为RecyclerView设置Adapter
recyclerView.setAdapter(adapter);


我要做的就是快速拿它匹配下我的场景,能不能满足我的需求。

是否支持多种类型条目?废话肯定支持;
是否支持不同条目不同数据类型?人家就是很久需要处理的数据类型来进行选择的,肯定没问题;
能不能支持一个数据类型对应多个样式?我看作者也是支持的,即通过数据实体中的标志来判断使用哪个Adapter;
能不能支持一个数据实体对应多个样式?由于是基于数据的类型进行选择代理Adapter的,这看来是无法实现。
我的微笑还没收场,就尴尬的定住了,5秒钟后,晃过神来,为什么我不按照这种思想自己封装一个。说实话我对作者的代理Adapter的管理还是不太满意的,这种思想很好,还是忍不住给作者点赞,那就自己来撸个轮子吧。

需求迭代


简单列表
PM在一次迭代过程中提出了要加一个新闻列表的需求,很简单,就是左边一个图片,右边一个标题、来源、发布时间,点击可以查看详情。

你一看,心中默念so easy,三下五除二,你就用RecyclerView很快实现了。

activity xml layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.kevin.myapplication.MainActivity"><android.support.v7.widget.RecyclerViewandroid:id="@+id/recycler_view"android:layout_width="match_parent"android:layout_height="match_parent"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /></android.support.constraint.ConstraintLayout>

实体对象


public class News {public String imgUrl = "";public String content = "";public String source = "";public String time = "";public String link = "";
}

adapter


public class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder> {private List<News> dataItems = new ArrayList<>();public void setDataItems(List<News> dataItems) {this.dataItems = dataItems;notifyDataSetChanged();}@Overridepublic ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news, parent, false);ViewHolder holder = new ViewHolder(view);return holder;}@Overridepublic void onBindViewHolder(ViewHolder holder, int position) {News news = dataItems.get(position);holder.tvContent.setText(news.content);holder.tvSource.setText(news.source);holder.tvTime.setText(news.time);Glide.with(holder.itemView.getContext()).load(news.imgUrl).into(holder.ivPic);}@Overridepublic int getItemCount() {return dataItems.size();}static class ViewHolder extends RecyclerView.ViewHolder {ImageView ivPic;TextView tvContent;TextView tvSource;TextView tvTime;public ViewHolder(View view) {super(view);ivPic = view.findViewById(R.id.iv_pic);tvContent = view.findViewById(R.id.tv_content);tvSource = view.findViewById(R.id.tv_source);tvTime = view.findViewById(R.id.tv_time);}}
}

adapter item layout


<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingLeft="10dp"android:paddingRight="10dp"android:paddingTop="10dp"><ImageViewandroid:id="@+id/iv_pic"android:layout_width="0dp"android:layout_height="80dp"android:scaleType="centerCrop"app:layout_constraintHorizontal_weight="1"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toLeftOf="@+id/tv_content"app:layout_constraintTop_toTopOf="parent"tools:src="@mipmap/ic_launcher" /><TextViewandroid:id="@+id/tv_content"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginLeft="10dp"android:ellipsize="end"android:maxLines="2"android:textColor="#333333"android:textSize="18sp"app:layout_constraintHorizontal_chainStyle="spread"app:layout_constraintHorizontal_weight="2"app:layout_constraintLeft_toRightOf="@+id/iv_pic"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent"tools:text="这是一条新闻,这是一条新闻,这是一条新闻" /><TextViewandroid:id="@+id/tv_source"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="10dp"android:textColor="#888888"android:textSize="12sp"app:layout_constraintBottom_toBottomOf="@+id/iv_pic"app:layout_constraintLeft_toRightOf="@+id/iv_pic"tools:text="澎湃新闻" /><TextViewandroid:id="@+id/tv_time"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="8dp"android:textColor="#888888"android:textSize="12sp"app:layout_constraintBottom_toBottomOf="@+id/iv_pic"app:layout_constraintLeft_toRightOf="@+id/tv_source"tools:text="07:33" /><Viewandroid:layout_width="0dp"android:layout_height="1px"android:layout_margin="10dp"android:background="#EEEEEE"app:layout_constraintTop_toBottomOf="@id/iv_pic" /></android.support.constraint.ConstraintLayout>


看一下实现,还不错的样子。

扩充样式
某天产品经理找到你说,只有一张图片的看着太单调了,能不能扩充出另外一种样式,一张图片的时候还是原来的样子,如果三张图片的时候上面是标题,下面是图片。你觉得没什么,也比较好实现,就没有做任何反抗去做了。

应该是这样,在之前一个图片的ViewHolder基础上扩展一个三个图片的ViewHoder,通过实体对象的图片数量进行区分是选择哪一个ViewHolder,不同的ViewHolder绑定不同的数据。

修改实体对象
把原来的String类型的图片数据改为List<String>的集合。

public class News {public List<String> imgUrls = null;public String content = "";public String source = "";public String time = "";public String link = "";
}

新样式的xml layout


<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingLeft="10dp"android:paddingRight="10dp"android:paddingTop="10dp"><TextViewandroid:id="@+id/tv_content"android:layout_width="0dp"android:layout_height="wrap_content"android:ellipsize="end"android:maxLines="2"android:textColor="#333333"android:textSize="18sp"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent"tools:text="这是一条新闻,这是一条新闻,这是一条新闻" /><ImageViewandroid:id="@+id/iv_pic1"android:layout_width="0dp"android:layout_height="80dp"android:layout_marginTop="10dp"android:scaleType="centerCrop"app:layout_constraintHorizontal_weight="1"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toLeftOf="@+id/iv_pic2"app:layout_constraintTop_toBottomOf="@+id/tv_content"tools:src="@mipmap/ic_launcher" /><ImageViewandroid:id="@+id/iv_pic2"android:layout_width="0dp"android:layout_height="80dp"android:layout_marginLeft="4dp"android:layout_marginTop="10dp"android:scaleType="centerCrop"app:layout_constraintHorizontal_weight="1"app:layout_constraintLeft_toRightOf="@+id/iv_pic1"app:layout_constraintRight_toLeftOf="@+id/iv_pic3"app:layout_constraintTop_toBottomOf="@+id/tv_content"tools:src="@mipmap/ic_launcher" /><ImageViewandroid:id="@+id/iv_pic3"android:layout_width="0dp"android:layout_height="80dp"android:layout_marginLeft="4dp"android:layout_marginTop="10dp"android:scaleType="centerCrop"app:layout_constraintHorizontal_weight="1"app:layout_constraintLeft_toRightOf="@+id/iv_pic2"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toBottomOf="@+id/tv_content"tools:src="@mipmap/ic_launcher" /><TextViewandroid:id="@+id/tv_source"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="10dp"android:textColor="#888888"android:textSize="12sp"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toBottomOf="@+id/iv_pic1"tools:text="澎湃新闻" /><TextViewandroid:id="@+id/tv_time"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="8dp"android:textColor="#888888"android:textSize="12sp"app:layout_constraintLeft_toRightOf="@+id/tv_source"app:layout_constraintTop_toTopOf="@+id/tv_source"tools:text="07:33" /><Viewandroid:layout_width="0dp"android:layout_height="1px"android:layout_margin="10dp"android:background="#EEEEEE"app:layout_constraintTop_toBottomOf="@id/tv_source" /></android.support.constraint.ConstraintLayout>

Adapter改造
复写getItemViewType方法,通过判断图片的个数是不是3个来区分样式。如果是则ViewType为1,如果不是则ViewType为0


@Override
public int getItemViewType(int position) {News news = dataItems.get(position);boolean isThreePic = news.imgUrls.size() == 3;int viewType = isThreePic ? 1 : 0;return viewType;
}

增加三张图片样式的ViewHolder


static class ThreePicViewHolder extends RecyclerView.ViewHolder {ImageView ivPic1;ImageView ivPic2;ImageView ivPic3;TextView tvContent;TextView tvSource;TextView tvTime;public ThreePicViewHolder(View view) {super(view);ivPic1 = view.findViewById(R.id.iv_pic1);ivPic2 = view.findViewById(R.id.iv_pic2);ivPic3 = view.findViewById(R.id.iv_pic3);tvContent = view.findViewById(R.id.tv_content);tvSource = view.findViewById(R.id.tv_source);tvTime = view.findViewById(R.id.tv_time);}
}


在onCreateViewHolder中,通过判断ViewType的类型,创建对应的ViewHolder。

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {if (viewType == 0) {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news, parent, false);OnePicViewHolder holder = new OnePicViewHolder(view);return holder;} else {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news_extend, parent, false);ThreePicViewHolder holder = new ThreePicViewHolder(view);return holder;}
}


在onBindingViewHolder中,通过判断ViewType的类型,绑定对应的数据到控件。

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {int viewType = holder.getItemViewType();if (viewType == 0) {OnePicViewHolder viewHolder = (OnePicViewHolder) holder;News news = dataItems.get(position);viewHolder.tvContent.setText(news.content);viewHolder.tvSource.setText(news.source);viewHolder.tvTime.setText(news.time);Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic);} else {ThreePicViewHolder viewHolder = (ThreePicViewHolder) holder;News news = dataItems.get(position);viewHolder.tvContent.setText(news.content);viewHolder.tvSource.setText(news.source);viewHolder.tvTime.setText(news.time);Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic1);Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(1)).into(viewHolder.ivPic2);Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(2)).into(viewHolder.ivPic3);}
}


OK,很快就完成了,这次的需求,看了下效果,还可以。但是没有第一次那么开心了,隐隐感觉这里的代码有点恶心。

又扩充样式
过了一段时间,产品又找到你,说想增加一种图集类型的样式,上面是标题,下面是一张大图,点开是可以左右滑动翻页的图集。说完一张呆萌脸问你是不是很简单,你想到又要加一种类型的ViewHolder,而之前的实现已经让你不爽。你说:倒是不复杂,但是你也不能无限制的加啊。抱怨完了,还是要做的。

添加xml布局

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingLeft="10dp"android:paddingRight="10dp"android:paddingTop="10dp"><TextViewandroid:id="@+id/tv_content"android:layout_width="0dp"android:layout_height="wrap_content"android:ellipsize="end"android:maxLines="2"android:textColor="#333333"android:textSize="18sp"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent"tools:text="这是一条新闻,这是一条新闻,这是一条新闻" /><ImageViewandroid:id="@+id/iv_pic"android:layout_width="0dp"android:layout_height="0dp"android:layout_marginTop="10dp"android:scaleType="centerCrop"app:layout_constraintDimensionRatio="5:3"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toBottomOf="@+id/tv_content"tools:src="@mipmap/ic_launcher" /><TextViewandroid:id="@+id/tv_count"android:layout_width="wrap_content"android:layout_height="wrap_content"android:drawableLeft="@mipmap/icon_img"android:drawablePadding="8dp"android:paddingBottom="6dp"android:paddingRight="10dp"android:textColor="@android:color/white"app:layout_constraintBottom_toBottomOf="@+id/iv_pic"app:layout_constraintRight_toRightOf="@+id/iv_pic"tools:text="图3" /><TextViewandroid:id="@+id/tv_source"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="10dp"android:textColor="#888888"android:textSize="12sp"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toBottomOf="@+id/iv_pic"tools:text="澎湃新闻" /><TextViewandroid:id="@+id/tv_time"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="8dp"android:textColor="#888888"android:textSize="12sp"app:layout_constraintLeft_toRightOf="@+id/tv_source"app:layout_constraintTop_toTopOf="@+id/tv_source"tools:text="07:33" /><Viewandroid:layout_width="0dp"android:layout_height="1px"android:layout_margin="10dp"android:background="#EEEEEE"app:layout_constraintTop_toBottomOf="@id/tv_source" /></android.support.constraint.ConstraintLayout>


又改造Adapter
复写getItemViewType方法,通过判断图片的个数区分样式。

@Override
public int getItemViewType(int position) {News news = dataItems.get(position);int imgSize = news.imgUrls.size();if (imgSize < 3) {return 0;} else if (imgSize == 3) {return 2;} else {return 3;}
}


增加图集样式的ViewHolder

static class MorePicViewHolder extends RecyclerView.ViewHolder {ImageView ivPic;TextView tvContent;TextView tvCount;TextView tvSource;TextView tvTime;public MorePicViewHolder(View view) {super(view);ivPic = view.findViewById(R.id.iv_pic);tvContent = view.findViewById(R.id.tv_content);tvCount = view.findViewById(R.id.tv_count);tvSource = view.findViewById(R.id.tv_source);tvTime = view.findViewById(R.id.tv_time);}
}


在onCreateViewHolder中,通过判断ViewType的类型,创建对应的ViewHolder。

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {if (viewType == 0) {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news_one_pic, parent, false);OnePicViewHolder holder = new OnePicViewHolder(view);return holder;} else if (viewType == 2) {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news_three_pic, parent, false);ThreePicViewHolder holder = new ThreePicViewHolder(view);return holder;} else if (viewType == 3) {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news_more_pic, parent, false);MorePicViewHolder holder = new MorePicViewHolder(view);return holder;} else {// Can't reach;return null;}
}


在onBindingViewHolder中,通过判断ViewType的类型,绑定对应的数据到控件。

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {int viewType = holder.getItemViewType();if (viewType == 0) {OnePicViewHolder viewHolder = (OnePicViewHolder) holder;News news = dataItems.get(position);viewHolder.tvContent.setText(news.content);viewHolder.tvSource.setText(news.source);viewHolder.tvTime.setText(news.time);Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic);} else if (viewType == 2) {ThreePicViewHolder viewHolder = (ThreePicViewHolder) holder;News news = dataItems.get(position);viewHolder.tvContent.setText(news.content);viewHolder.tvSource.setText(news.source);viewHolder.tvTime.setText(news.time);Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic1);Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(1)).into(viewHolder.ivPic2);Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(2)).into(viewHolder.ivPic3);} else if (viewType == 3) {MorePicViewHolder viewHolder = (MorePicViewHolder) holder;News news = dataItems.get(position);viewHolder.tvContent.setText(news.content);viewHolder.tvSource.setText(news.source);viewHolder.tvTime.setText(news.time);viewHolder.tvCount.setText(news.count + " 图");Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic);}
}


OK,很快又改完了,这次你的心情不太愉快,之前的太不利于扩展了,每次添加一种新的都要把Adapter改一遍。

反思
在上次需求上线之后,你开始反思,为什么我这么反感产品扩充样式,而不是我的代码有一定的业务兼容性,而是把自己搞的这么被动。算了,下楼喝杯咖啡冷静冷静。你顺便叫了下旁边的哥们,他说:“我比较忙,你给我带杯吧。”,“带什么?”你问,“摩卡小杯”。你心想,本来叫你一块儿去的,你小子这么懒叫我给你带。唉,对啊,我的Adapter不就可以这样嘛,自己不做处理,委托给其他的Adapter去做。

一开始的时候注册三个的委托Adapter对象,A委托Adapter可以处理一张图片的样式,B委托Adapter可以处理三张图片的样式,C委托可以处理多张图片的样式。根据数据里面图片的数量选择对应的委托Adapter去处理就可以啦。

委托


所有的委托都有相同的方法,为了方便定义一个接口。

public interface IDelegateAdapter {// 查找委托时调用的方法,返回自己能处理的类型即可。boolean isForViewType(News news);// 用于委托Adapter的onCreateViewHolder方法RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType);// 用于委托Adapter的onBindViewHolder方法void onBindViewHolder(RecyclerView.ViewHolder holder, int position, News news);}


然后是三个委托

public class OnePicDelegateAdapter implements IDelegateAdapter {@Overridepublic boolean isForViewType(News news) {// 我能处理一张图片return news.imgUrls.size() == 1;}public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news_one_pic, parent, false);OnePicViewHolder holder = new OnePicViewHolder(view);return holder;}public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, News news) {OnePicViewHolder viewHolder = (OnePicViewHolder) holder;viewHolder.tvContent.setText(news.content);viewHolder.tvSource.setText(news.source);viewHolder.tvTime.setText(news.time);Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic);}static class OnePicViewHolder extends RecyclerView.ViewHolder {ImageView ivPic;TextView tvContent;TextView tvSource;TextView tvTime;public OnePicViewHolder(View view) {super(view);ivPic = view.findViewById(R.id.iv_pic);tvContent = view.findViewById(R.id.tv_content);tvSource = view.findViewById(R.id.tv_source);tvTime = view.findViewById(R.id.tv_time);}}
}
public class ThreePicDelegateAdapter implements IDelegateAdapter {@Overridepublic boolean isForViewType(News news) {// 我能处理三张图片return news.imgUrls.size() == 3;}public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news_three_pic, parent, false);ThreePicViewHolder holder = new ThreePicViewHolder(view);return holder;}public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, News news) {ThreePicViewHolder viewHolder = (ThreePicViewHolder) holder;viewHolder.tvContent.setText(news.content);viewHolder.tvSource.setText(news.source);viewHolder.tvTime.setText(news.time);Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic1);Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(1)).into(viewHolder.ivPic2);Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(2)).into(viewHolder.ivPic3);}static class ThreePicViewHolder extends RecyclerView.ViewHolder {ImageView ivPic1;ImageView ivPic2;ImageView ivPic3;TextView tvContent;TextView tvSource;TextView tvTime;public ThreePicViewHolder(View view) {super(view);ivPic1 = view.findViewById(R.id.iv_pic1);ivPic2 = view.findViewById(R.id.iv_pic2);ivPic3 = view.findViewById(R.id.iv_pic3);tvContent = view.findViewById(R.id.tv_content);tvSource = view.findViewById(R.id.tv_source);tvTime = view.findViewById(R.id.tv_time);}}}
public class MorePicDelegateAdapter implements IDelegateAdapter {@Overridepublic boolean isForViewType(News news) {// 我能处理多张图片return news.imgUrls.size() > 3;}public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news_more_pic, parent, false);MorePicViewHolder holder = new MorePicViewHolder(view);return holder;}public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, News news) {MorePicViewHolder viewHolder = (MorePicViewHolder) holder;viewHolder.tvContent.setText(news.content);viewHolder.tvSource.setText(news.source);viewHolder.tvTime.setText(news.time);viewHolder.tvCount.setText(news.count + " 图");Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic);}static class MorePicViewHolder extends RecyclerView.ViewHolder {ImageView ivPic;TextView tvContent;TextView tvCount;TextView tvSource;TextView tvTime;public MorePicViewHolder(View view) {super(view);ivPic = view.findViewById(R.id.iv_pic);tvContent = view.findViewById(R.id.tv_content);tvCount = view.findViewById(R.id.tv_count);tvSource = view.findViewById(R.id.tv_source);tvTime = view.findViewById(R.id.tv_time);}}}


再看下之前冗余的Adapter,现在已经非常清瘦了

public class NewsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {private List<News> dataItems = new ArrayList<>();public void setDataItems(List<News> dataItems) {this.dataItems = dataItems;notifyDataSetChanged();}List<IDelegateAdapter> delegateAdapters = new ArrayList<>();public void addDelegate(IDelegateAdapter delegateAdapter) {delegateAdapters.add(delegateAdapter);}@Overridepublic int getItemViewType(int position) {}@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {}@Overridepublic void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {}@Overridepublic int getItemCount() {return dataItems.size();}}


有三个方法需要我们去填空,在之前的图中我们说过,getItemViewType通过委托在集合中的index去标识。

@Override
public int getItemViewType(int position) {// 找到当前位置的数据News news = dataItems.get(position);// 遍历所有的代理,问下他们谁能处理for (IDelegateAdapter delegateAdapter : delegateAdapters) {if (delegateAdapter.isForViewType(news)) {// 谁能处理返回他的indexreturn delegateAdapters.indexOf(delegateAdapter);}}throw new RuntimeException("没有找到可以处理的委托Adapter");
}


然后是onCreateViewHolder,既然是通过委托Adapter的在集合中的index去标记的ViewType,那么在onCreateViewHolder中就非常简单了。

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {// 找到对应的委托AdapterIDelegateAdapter delegateAdapter = delegateAdapters.get(viewType);// 把onCreateViewHolder交给委托Adapter去处理RecyclerView.ViewHolder viewHolder = delegateAdapter.onCreateViewHolder(parent, viewType);return viewHolder;
}


接下来是onBindViewHolder,类似onCreateViewHolder,这里也是找到委托Adapter,交给他去处理。

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {// 找到当前ViewHolder的ViewType,也就是委托Adapter在集合中的indexint viewType = holder.getItemViewType();// 找到对应的委托AdapterIDelegateAdapter delegateAdapter = delegateAdapters.get(viewType);// 把onBindViewHolder交给委托Adapter去处理delegateAdapter.onBindViewHolder(holder, position, dataItems.get(position));
}


使用的时候也非常简单,在之前的基础上注册委托Adapter就可以了。

RecyclerView recyclerView = findViewById(R.id.recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
NewsAdapter mAdapter = new NewsAdapter();
// 添加委托Adapter
mAdapter.addDelegate(new OnePicDelegateAdapter());
mAdapter.addDelegate(new ThreePicDelegateAdapter());
mAdapter.addDelegate(new MorePicDelegateAdapter());recyclerView.setAdapter(mAdapter);


双扩充样式
产品经理又来了,说又要添加一种视频类型的样式。你爽快地说:“没问题”。

之前一直是通过图片的个数来判断是那种形式,但这次明显不能这么干了,修改原来的实体,添加一个type来标识是那种类型。

public class News {public int type = 0; // 0:一张图片;1:三张图片;2:多张图片;3:视频类型public List<String> imgUrls = null;public String content = "";public String count = "";public String duration = "";public String source = "";public String time = "";public String link = "";
}


添加xml布局

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingLeft="10dp"android:paddingRight="10dp"android:paddingTop="10dp"><TextViewandroid:id="@+id/tv_content"android:layout_width="0dp"android:layout_height="wrap_content"android:ellipsize="end"android:maxLines="2"android:textColor="#333333"android:textSize="18sp"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent"tools:text="这是一条新闻,这是一条新闻,这是一条新闻" /><ImageViewandroid:id="@+id/iv_pic"android:layout_width="0dp"android:layout_height="0dp"android:layout_marginTop="10dp"android:scaleType="centerCrop"app:layout_constraintDimensionRatio="5:3"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toBottomOf="@+id/tv_content"tools:src="@mipmap/ic_launcher" /><ImageViewandroid:layout_width="48dp"android:layout_height="48dp"android:src="@mipmap/play90"app:layout_constraintBottom_toBottomOf="@+id/iv_pic"app:layout_constraintLeft_toLeftOf="@+id/iv_pic"app:layout_constraintRight_toRightOf="@+id/iv_pic"app:layout_constraintTop_toTopOf="@+id/iv_pic" /><TextViewandroid:id="@+id/tv_duration"android:layout_width="wrap_content"android:layout_height="wrap_content"android:paddingBottom="6dp"android:paddingRight="10dp"android:textColor="@android:color/white"app:layout_constraintBottom_toBottomOf="@+id/iv_pic"app:layout_constraintRight_toRightOf="@+id/iv_pic"tools:text="12:34" /><TextViewandroid:id="@+id/tv_source"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="10dp"android:textColor="#888888"android:textSize="12sp"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toBottomOf="@+id/iv_pic"tools:text="澎湃新闻" /><TextViewandroid:id="@+id/tv_time"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="8dp"android:textColor="#888888"android:textSize="12sp"app:layout_constraintLeft_toRightOf="@+id/tv_source"app:layout_constraintTop_toTopOf="@+id/tv_source"tools:text="07:33" /><Viewandroid:layout_width="0dp"android:layout_height="1px"android:layout_margin="10dp"android:background="#EEEEEE"app:layout_constraintTop_toBottomOf="@id/tv_source" /></android.support.constraint.ConstraintLayout>


添加委托Adapter

public class VideoDelegateAdapter implements IDelegateAdapter {@Overridepublic boolean isForViewType(News news) {// 我能处理视频类型return news.type == 3;}public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news_video, parent, false);VideoViewHolder holder = new VideoViewHolder(view);return holder;}public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, News news) {VideoViewHolder viewHolder = (VideoViewHolder) holder;viewHolder.tvContent.setText(news.content);viewHolder.tvSource.setText(news.source);viewHolder.tvTime.setText(news.time);viewHolder.tvDuration.setText(news.duration);Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic);}static class VideoViewHolder extends RecyclerView.ViewHolder {ImageView ivPic;TextView tvContent;TextView tvDuration;TextView tvSource;TextView tvTime;public VideoViewHolder(View view) {super(view);ivPic = view.findViewById(R.id.iv_pic);tvContent = view.findViewById(R.id.tv_content);tvDuration = view.findViewById(R.id.tv_duration);tvSource = view.findViewById(R.id.tv_source);tvTime = view.findViewById(R.id.tv_time);}}}


添加注册委托代理

// 添加委托Adapter

mAdapter.addDelegate(new OnePicDelegateAdapter());
mAdapter.addDelegate(new ThreePicDelegateAdapter());
mAdapter.addDelegate(new MorePicDelegateAdapter());
mAdapter.addDelegate(new VideoDelegateAdapter()); // 新添加的视频类型


经过非常简单的三步之后,就很清爽地完成了这次需求,大家都很开心。

叒扩充样式
产品经理再一次找到你,说我们的新闻很火爆,希望加入广告。每10条加入一个广告,是不是很好做。你心想,卧槽,这他么不是坑我么。没办法,产品经理的性子你也知道,这个肯定是要做的。

这么看来之前的封装还是不能满足的,之前的是要求所有的数据都有相同的类型。那能不能加入不同数据类型的支持呢?

看下之前的是通过一个List来保存托管Adapter的,这样是无法保存不同的类型信息的。

List<IDelegateAdapter> delegateAdapters = new ArrayList<>();
那就再增加一个保存类型信息的List吧,为了便于查找,这里使用Android提供的SparseArrayCompat。两个集合定义如下:

// 用于保存委托Adapter
private SparseArrayCompat<AdapterDelegate<Object>> delegates = new SparseArrayCompat();
// 用于保存委托Adapter能处理的类型
private SparseArray<String> dataTypes = new SparseArray<>();
public AdapterDelegatesManager addDelegate(AdapterDelegate<Object, VH> delegate) {Type superclass = delegate.getClass().getGenericSuperclass();if (superclass instanceof ParameterizedType) {Class<?> clazz = (Class<?>) ((ParameterizedType) superclass).getActualTypeArguments()[0];String typeWithTag = clazz.getName();int viewType = delegates.size();// Save the delegate to the collection;delegates.put(viewType, delegate);// Save the index of the delegate to the collection;dataTypes.put(viewType, typeWithTag);} else {// Has no generics.throw new IllegalArgumentException(String.format("Please set the correct generic parameters on %s.", delegate.getClass().getName()));}return this;
}


每向delegates中添加一个委托Adapter,则向dataTypes中添加该委托Adapter能处理的类型。比如:向delegates添加一个能处理String类型的委托 AdapterDelegate<String>位置是1,那么向dataType中添加一个java.lang.String位置也是1。那么在处理String类型的数据的时候在dataType中查找到对应的位置为1,就可以去delegate的1位置取对应的委托Adapter就可以啦。

这里就不再贴代码了,github地址DelegationAdapter。

叕来需求
经过以上封装,你开心地工作了相当长的一段时间。有一天产品又找到你,我们要做一个账单详情页,很简单,就是显示他的消费信息。

你一看,上面是固定的信息,下面是固定的信息,中间是一个列表。使用ListView的添加头部、尾部会非常简单,但是你又不想使用ListView。能不能使用封装的RecyclerView委托Adapter实现呢?

public class Bill {public String title = "";           // 标题public String waiter = "";          // 服务员public String cashier = "";         // 收银员public int ramadhin = 0;            // 桌号public int guests = 0;              // 客人数public String beginTime = "";       // 开台时间public String endTime = "";         // 结账时间public String duration = "";        // 用餐时长public List<Item> details = null;   // 用餐详情public String total = "";           // 合计public String discounts = "";       // 优惠public String receivable = "";      // 应收public String describe = "";        // 描述信息public static class Item {public String name = "";    // 名称public String count = "";   // 数量public String price = "";   // 单价public String subtotal = "";// 小计}}


通过分析,应该是三个委托Adapter,即上中下三部分。中间部分还好,类型是Bill.Item,但是上下两部分就无法区分了,都是Bill类型。

那怎样才能区分相同数据类型同一数据的委托Adapter呢?能不能给每个委托Adapter打一个Tag,在设置数据的时候也添加Tag,这样就可以通过Tag去区分了。修改设置数据类型标记的部分。

// 用于保存委托Adapter
private SparseArrayCompat<AdapterDelegate<Object, VH>> delegates = new SparseArrayCompat();
// 用于保存委托Adapter能处理的类型
private SparseArray<String> dataTypeWithTags = new SparseArray<>();
public AdapterDelegatesManager addDelegate(AdapterDelegate<Object, VH> delegate, String tag) {Type superclass = delegate.getClass().getGenericSuperclass();if (superclass instanceof ParameterizedType) {Class<?> clazz = (Class<?>) ((ParameterizedType) superclass).getActualTypeArguments()[0];String typeWithTag = clazz.getName() + ":" + tag;int viewType = delegates.size();// Save the delegate to the collection;delegates.put(viewType, delegate);// Save the index of the delegate to the collection;dataTypeWithTags.put(viewType, typeWithTag);} else {// Has no generics.throw new IllegalArgumentException(String.format("Please set the correct generic parameters on %s.", delegate.getClass().getName()));}return this;
}


通过一个动画来总结下实现方案,开始注册了四个委托Adapter,其中A、C类型都为String,通过tag标记来区分;添加数据之后,RecyclerView进行布局,首先处理带tag为A的字符串"ABCDEF",那么到委托集合中去找对应的Adapter去进行渲染。接着一个个都完成了渲染。

OK,很轻松就搞定了。

 

这篇关于一种优雅的方式实现RecyclerView条目多类型的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/778459

相关文章

使用Python实现在Word中添加或删除超链接

《使用Python实现在Word中添加或删除超链接》在Word文档中,超链接是一种将文本或图像连接到其他文档、网页或同一文档中不同部分的功能,本文将为大家介绍一下Python如何实现在Word中添加或... 在Word文档中,超链接是一种将文本或图像连接到其他文档、网页或同一文档中不同部分的功能。通过添加超

windos server2022里的DFS配置的实现

《windosserver2022里的DFS配置的实现》DFS是WindowsServer操作系统提供的一种功能,用于在多台服务器上集中管理共享文件夹和文件的分布式存储解决方案,本文就来介绍一下wi... 目录什么是DFS?优势:应用场景:DFS配置步骤什么是DFS?DFS指的是分布式文件系统(Distr

NFS实现多服务器文件的共享的方法步骤

《NFS实现多服务器文件的共享的方法步骤》NFS允许网络中的计算机之间共享资源,客户端可以透明地读写远端NFS服务器上的文件,本文就来介绍一下NFS实现多服务器文件的共享的方法步骤,感兴趣的可以了解一... 目录一、简介二、部署1、准备1、服务端和客户端:安装nfs-utils2、服务端:创建共享目录3、服

C#使用yield关键字实现提升迭代性能与效率

《C#使用yield关键字实现提升迭代性能与效率》yield关键字在C#中简化了数据迭代的方式,实现了按需生成数据,自动维护迭代状态,本文主要来聊聊如何使用yield关键字实现提升迭代性能与效率,感兴... 目录前言传统迭代和yield迭代方式对比yield延迟加载按需获取数据yield break显式示迭

Python实现高效地读写大型文件

《Python实现高效地读写大型文件》Python如何读写的是大型文件,有没有什么方法来提高效率呢,这篇文章就来和大家聊聊如何在Python中高效地读写大型文件,需要的可以了解下... 目录一、逐行读取大型文件二、分块读取大型文件三、使用 mmap 模块进行内存映射文件操作(适用于大文件)四、使用 pand

python实现pdf转word和excel的示例代码

《python实现pdf转word和excel的示例代码》本文主要介绍了python实现pdf转word和excel的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录一、引言二、python编程1,PDF转Word2,PDF转Excel三、前端页面效果展示总结一

IDEA如何将String类型转json格式

《IDEA如何将String类型转json格式》在Java中,字符串字面量中的转义字符会被自动转换,但通过网络获取的字符串可能不会自动转换,为了解决IDEA无法识别JSON字符串的问题,可以在本地对字... 目录问题描述问题原因解决方案总结问题描述最近做项目需要使用Ai生成json,可生成String类型

Python xmltodict实现简化XML数据处理

《Pythonxmltodict实现简化XML数据处理》Python社区为提供了xmltodict库,它专为简化XML与Python数据结构的转换而设计,本文主要来为大家介绍一下如何使用xmltod... 目录一、引言二、XMLtodict介绍设计理念适用场景三、功能参数与属性1、parse函数2、unpa

Mybatis官方生成器的使用方式

《Mybatis官方生成器的使用方式》本文详细介绍了MyBatisGenerator(MBG)的使用方法,通过实际代码示例展示了如何配置Maven插件来自动化生成MyBatis项目所需的实体类、Map... 目录1. MyBATis Generator 简介2. MyBatis Generator 的功能3

C#实现获得某个枚举的所有名称

《C#实现获得某个枚举的所有名称》这篇文章主要为大家详细介绍了C#如何实现获得某个枚举的所有名称,文中的示例代码讲解详细,具有一定的借鉴价值,有需要的小伙伴可以参考一下... C#中获得某个枚举的所有名称using System;using System.Collections.Generic;usi