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

转载自:https://blog.csdn.net/xuehuayous/article/details/80021325

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

放弃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。

 
  1. // 初始化adapter

  2. BaseItemAdapter adapter = new BaseItemAdapter();

  3. // 为TextBean数据源注册ViewHolderManager管理类

  4. adapter.register(TextBean.class, new TextViewManager());

  5. // 为更多数据源注册ViewHolderManager管理类

  6. adapter.register(ImageTextBean.class, new ImageAndTextManager());

  7. adapter.register(ImageBean.class, new ImageViewManager());

  8. // 为RecyclerView设置Adapter

  9. recyclerView.setAdapter(adapter);

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

  1. 是否支持多种类型条目?废话肯定支持;
  2. 是否支持不同条目不同数据类型?人家就是很久需要处理的数据类型来进行选择的,肯定没问题;
  3. 能不能支持一个数据类型对应多个样式?我看作者也是支持的,即通过数据实体中的标志来判断使用哪个Adapter;
  4. 能不能支持一个数据实体对应多个样式?由于是基于数据的类型进行选择代理Adapter的,这看来是无法实现。

我的微笑还没收场,就尴尬的定住了,5秒钟后,晃过神来,为什么我不按照这种思想自己封装一个。说实话我对作者的代理Adapter的管理还是不太满意的,这种思想很好,还是忍不住给作者点赞,那就自己来撸个轮子吧。

需求迭代

 

简单列表

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

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

activity xml layout

 
  1. <?xml version="1.0" encoding="utf-8"?>

  2. <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"

  3. xmlns:app="http://schemas.android.com/apk/res-auto"

  4. xmlns:tools="http://schemas.android.com/tools"

  5. android:layout_width="match_parent"

  6. android:layout_height="match_parent"

  7. tools:context="com.kevin.myapplication.MainActivity">

  8.  
  9. <android.support.v7.widget.RecyclerView

  10. android:id="@+id/recycler_view"

  11. android:layout_width="match_parent"

  12. android:layout_height="match_parent"

  13. app:layout_constraintBottom_toBottomOf="parent"

  14. app:layout_constraintLeft_toLeftOf="parent"

  15. app:layout_constraintRight_toRightOf="parent"

  16. app:layout_constraintTop_toTopOf="parent" />

  17.  
  18. </android.support.constraint.ConstraintLayout>

实体对象

 
  1. public class News {

  2. public String imgUrl = "";

  3. public String content = "";

  4. public String source = "";

  5. public String time = "";

  6. public String link = "";

  7. }

adapter

 
  1. public class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder> {

  2.  
  3. private List<News> dataItems = new ArrayList<>();

  4.  
  5. public void setDataItems(List<News> dataItems) {

  6. this.dataItems = dataItems;

  7. notifyDataSetChanged();

  8. }

  9.  
  10. @Override

  11. public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

  12. View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news, parent, false);

  13. ViewHolder holder = new ViewHolder(view);

  14. return holder;

  15. }

  16.  
  17. @Override

  18. public void onBindViewHolder(ViewHolder holder, int position) {

  19. News news = dataItems.get(position);

  20. holder.tvContent.setText(news.content);

  21. holder.tvSource.setText(news.source);

  22. holder.tvTime.setText(news.time);

  23. Glide.with(holder.itemView.getContext()).load(news.imgUrl).into(holder.ivPic);

  24. }

  25.  
  26. @Override

  27. public int getItemCount() {

  28. return dataItems.size();

  29. }

  30.  
  31. static class ViewHolder extends RecyclerView.ViewHolder {

  32. ImageView ivPic;

  33. TextView tvContent;

  34. TextView tvSource;

  35. TextView tvTime;

  36.  
  37. public ViewHolder(View view) {

  38. super(view);

  39. ivPic = view.findViewById(R.id.iv_pic);

  40. tvContent = view.findViewById(R.id.tv_content);

  41. tvSource = view.findViewById(R.id.tv_source);

  42. tvTime = view.findViewById(R.id.tv_time);

  43. }

  44. }

  45. }

adapter item layout

 
  1. <?xml version="1.0" encoding="utf-8"?>

  2. <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"

  3. xmlns:app="http://schemas.android.com/apk/res-auto"

  4. xmlns:tools="http://schemas.android.com/tools"

  5. android:layout_width="match_parent"

  6. android:layout_height="wrap_content"

  7. android:paddingLeft="10dp"

  8. android:paddingRight="10dp"

  9. android:paddingTop="10dp">

  10.  
  11. <ImageView

  12. android:id="@+id/iv_pic"

  13. android:layout_width="0dp"

  14. android:layout_height="80dp"

  15. android:scaleType="centerCrop"

  16. app:layout_constraintHorizontal_weight="1"

  17. app:layout_constraintLeft_toLeftOf="parent"

  18. app:layout_constraintRight_toLeftOf="@+id/tv_content"

  19. app:layout_constraintTop_toTopOf="parent"

  20. tools:src="@mipmap/ic_launcher" />

  21.  
  22. <TextView

  23. android:id="@+id/tv_content"

  24. android:layout_width="0dp"

  25. android:layout_height="wrap_content"

  26. android:layout_marginLeft="10dp"

  27. android:ellipsize="end"

  28. android:maxLines="2"

  29. android:textColor="#333333"

  30. android:textSize="18sp"

  31. app:layout_constraintHorizontal_chainStyle="spread"

  32. app:layout_constraintHorizontal_weight="2"

  33. app:layout_constraintLeft_toRightOf="@+id/iv_pic"

  34. app:layout_constraintRight_toRightOf="parent"

  35. app:layout_constraintTop_toTopOf="parent"

  36. tools:text="这是一条新闻,这是一条新闻,这是一条新闻" />

  37.  
  38. <TextView

  39. android:id="@+id/tv_source"

  40. android:layout_width="wrap_content"

  41. android:layout_height="wrap_content"

  42. android:layout_marginLeft="10dp"

  43. android:textColor="#888888"

  44. android:textSize="12sp"

  45. app:layout_constraintBottom_toBottomOf="@+id/iv_pic"

  46. app:layout_constraintLeft_toRightOf="@+id/iv_pic"

  47. tools:text="澎湃新闻" />

  48.  
  49. <TextView

  50. android:id="@+id/tv_time"

  51. android:layout_width="wrap_content"

  52. android:layout_height="wrap_content"

  53. android:layout_marginLeft="8dp"

  54. android:textColor="#888888"

  55. android:textSize="12sp"

  56. app:layout_constraintBottom_toBottomOf="@+id/iv_pic"

  57. app:layout_constraintLeft_toRightOf="@+id/tv_source"

  58. tools:text="07:33" />

  59.  
  60. <View

  61. android:layout_width="0dp"

  62. android:layout_height="1px"

  63. android:layout_margin="10dp"

  64. android:background="#EEEEEE"

  65. app:layout_constraintTop_toBottomOf="@id/iv_pic" />

  66.  
  67. </android.support.constraint.ConstraintLayout>

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

扩充样式

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

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

修改实体对象

把原来的String类型的图片数据改为List<String>的集合。

 
  1. public class News {

  2. public List<String> imgUrls = null;

  3. public String content = "";

  4. public String source = "";

  5. public String time = "";

  6. public String link = "";

  7. }

新样式的xml layout

 
  1. <?xml version="1.0" encoding="utf-8"?>

  2. <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"

  3. xmlns:app="http://schemas.android.com/apk/res-auto"

  4. xmlns:tools="http://schemas.android.com/tools"

  5. android:layout_width="match_parent"

  6. android:layout_height="wrap_content"

  7. android:paddingLeft="10dp"

  8. android:paddingRight="10dp"

  9. android:paddingTop="10dp">

  10.  
  11. <TextView

  12. android:id="@+id/tv_content"

  13. android:layout_width="0dp"

  14. android:layout_height="wrap_content"

  15. android:ellipsize="end"

  16. android:maxLines="2"

  17. android:textColor="#333333"

  18. android:textSize="18sp"

  19. app:layout_constraintLeft_toLeftOf="parent"

  20. app:layout_constraintRight_toRightOf="parent"

  21. app:layout_constraintTop_toTopOf="parent"

  22. tools:text="这是一条新闻,这是一条新闻,这是一条新闻" />

  23.  
  24. <ImageView

  25. android:id="@+id/iv_pic1"

  26. android:layout_width="0dp"

  27. android:layout_height="80dp"

  28. android:layout_marginTop="10dp"

  29. android:scaleType="centerCrop"

  30. app:layout_constraintHorizontal_weight="1"

  31. app:layout_constraintLeft_toLeftOf="parent"

  32. app:layout_constraintRight_toLeftOf="@+id/iv_pic2"

  33. app:layout_constraintTop_toBottomOf="@+id/tv_content"

  34. tools:src="@mipmap/ic_launcher" />

  35.  
  36. <ImageView

  37. android:id="@+id/iv_pic2"

  38. android:layout_width="0dp"

  39. android:layout_height="80dp"

  40. android:layout_marginLeft="4dp"

  41. android:layout_marginTop="10dp"

  42. android:scaleType="centerCrop"

  43. app:layout_constraintHorizontal_weight="1"

  44. app:layout_constraintLeft_toRightOf="@+id/iv_pic1"

  45. app:layout_constraintRight_toLeftOf="@+id/iv_pic3"

  46. app:layout_constraintTop_toBottomOf="@+id/tv_content"

  47. tools:src="@mipmap/ic_launcher" />

  48.  
  49. <ImageView

  50. android:id="@+id/iv_pic3"

  51. android:layout_width="0dp"

  52. android:layout_height="80dp"

  53. android:layout_marginLeft="4dp"

  54. android:layout_marginTop="10dp"

  55. android:scaleType="centerCrop"

  56. app:layout_constraintHorizontal_weight="1"

  57. app:layout_constraintLeft_toRightOf="@+id/iv_pic2"

  58. app:layout_constraintRight_toRightOf="parent"

  59. app:layout_constraintTop_toBottomOf="@+id/tv_content"

  60. tools:src="@mipmap/ic_launcher" />

  61.  
  62. <TextView

  63. android:id="@+id/tv_source"

  64. android:layout_width="wrap_content"

  65. android:layout_height="wrap_content"

  66. android:layout_marginTop="10dp"

  67. android:textColor="#888888"

  68. android:textSize="12sp"

  69. app:layout_constraintLeft_toLeftOf="parent"

  70. app:layout_constraintTop_toBottomOf="@+id/iv_pic1"

  71. tools:text="澎湃新闻" />

  72.  
  73. <TextView

  74. android:id="@+id/tv_time"

  75. android:layout_width="wrap_content"

  76. android:layout_height="wrap_content"

  77. android:layout_marginLeft="8dp"

  78. android:textColor="#888888"

  79. android:textSize="12sp"

  80. app:layout_constraintLeft_toRightOf="@+id/tv_source"

  81. app:layout_constraintTop_toTopOf="@+id/tv_source"

  82. tools:text="07:33" />

  83.  
  84. <View

  85. android:layout_width="0dp"

  86. android:layout_height="1px"

  87. android:layout_margin="10dp"

  88. android:background="#EEEEEE"

  89. app:layout_constraintTop_toBottomOf="@id/tv_source" />

  90.  
  91. </android.support.constraint.ConstraintLayout>

Adapter改造

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

 
  1. @Override

  2. public int getItemViewType(int position) {

  3. News news = dataItems.get(position);

  4. boolean isThreePic = news.imgUrls.size() == 3;

  5. int viewType = isThreePic ? 1 : 0;

  6. return viewType;

  7. }

增加三张图片样式的ViewHolder

 
  1. static class ThreePicViewHolder extends RecyclerView.ViewHolder {

  2. ImageView ivPic1;

  3. ImageView ivPic2;

  4. ImageView ivPic3;

  5. TextView tvContent;

  6. TextView tvSource;

  7. TextView tvTime;

  8.  
  9. public ThreePicViewHolder(View view) {

  10. super(view);

  11. ivPic1 = view.findViewById(R.id.iv_pic1);

  12. ivPic2 = view.findViewById(R.id.iv_pic2);

  13. ivPic3 = view.findViewById(R.id.iv_pic3);

  14. tvContent = view.findViewById(R.id.tv_content);

  15. tvSource = view.findViewById(R.id.tv_source);

  16. tvTime = view.findViewById(R.id.tv_time);

  17. }

  18. }

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

 
  1. @Override

  2. public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

  3. if (viewType == 0) {

  4. View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news, parent, false);

  5. OnePicViewHolder holder = new OnePicViewHolder(view);

  6. return holder;

  7. } else {

  8. View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news_extend, parent, false);

  9. ThreePicViewHolder holder = new ThreePicViewHolder(view);

  10. return holder;

  11. }

  12. }

 

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

 
  1. @Override

  2. public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

  3. int viewType = holder.getItemViewType();

  4. if (viewType == 0) {

  5. OnePicViewHolder viewHolder = (OnePicViewHolder) holder;

  6. News news = dataItems.get(position);

  7. viewHolder.tvContent.setText(news.content);

  8. viewHolder.tvSource.setText(news.source);

  9. viewHolder.tvTime.setText(news.time);

  10. Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic);

  11. } else {

  12. ThreePicViewHolder viewHolder = (ThreePicViewHolder) holder;

  13. News news = dataItems.get(position);

  14. viewHolder.tvContent.setText(news.content);

  15. viewHolder.tvSource.setText(news.source);

  16. viewHolder.tvTime.setText(news.time);

  17. Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic1);

  18. Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(1)).into(viewHolder.ivPic2);

  19. Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(2)).into(viewHolder.ivPic3);

  20. }

  21. }

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

又扩充样式

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

添加xml布局

 
  1. <?xml version="1.0" encoding="utf-8"?>

  2. <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"

  3. xmlns:app="http://schemas.android.com/apk/res-auto"

  4. xmlns:tools="http://schemas.android.com/tools"

  5. android:layout_width="match_parent"

  6. android:layout_height="wrap_content"

  7. android:paddingLeft="10dp"

  8. android:paddingRight="10dp"

  9. android:paddingTop="10dp">

  10.  
  11. <TextView

  12. android:id="@+id/tv_content"

  13. android:layout_width="0dp"

  14. android:layout_height="wrap_content"

  15. android:ellipsize="end"

  16. android:maxLines="2"

  17. android:textColor="#333333"

  18. android:textSize="18sp"

  19. app:layout_constraintLeft_toLeftOf="parent"

  20. app:layout_constraintRight_toRightOf="parent"

  21. app:layout_constraintTop_toTopOf="parent"

  22. tools:text="这是一条新闻,这是一条新闻,这是一条新闻" />

  23.  
  24. <ImageView

  25. android:id="@+id/iv_pic"

  26. android:layout_width="0dp"

  27. android:layout_height="0dp"

  28. android:layout_marginTop="10dp"

  29. android:scaleType="centerCrop"

  30. app:layout_constraintDimensionRatio="5:3"

  31. app:layout_constraintLeft_toLeftOf="parent"

  32. app:layout_constraintRight_toRightOf="parent"

  33. app:layout_constraintTop_toBottomOf="@+id/tv_content"

  34. tools:src="@mipmap/ic_launcher" />

  35.  
  36. <TextView

  37. android:id="@+id/tv_count"

  38. android:layout_width="wrap_content"

  39. android:layout_height="wrap_content"

  40. android:drawableLeft="@mipmap/icon_img"

  41. android:drawablePadding="8dp"

  42. android:paddingBottom="6dp"

  43. android:paddingRight="10dp"

  44. android:textColor="@android:color/white"

  45. app:layout_constraintBottom_toBottomOf="@+id/iv_pic"

  46. app:layout_constraintRight_toRightOf="@+id/iv_pic"

  47. tools:text="图3" />

  48.  
  49. <TextView

  50. android:id="@+id/tv_source"

  51. android:layout_width="wrap_content"

  52. android:layout_height="wrap_content"

  53. android:layout_marginTop="10dp"

  54. android:textColor="#888888"

  55. android:textSize="12sp"

  56. app:layout_constraintLeft_toLeftOf="parent"

  57. app:layout_constraintTop_toBottomOf="@+id/iv_pic"

  58. tools:text="澎湃新闻" />

  59.  
  60. <TextView

  61. android:id="@+id/tv_time"

  62. android:layout_width="wrap_content"

  63. android:layout_height="wrap_content"

  64. android:layout_marginLeft="8dp"

  65. android:textColor="#888888"

  66. android:textSize="12sp"

  67. app:layout_constraintLeft_toRightOf="@+id/tv_source"

  68. app:layout_constraintTop_toTopOf="@+id/tv_source"

  69. tools:text="07:33" />

  70.  
  71. <View

  72. android:layout_width="0dp"

  73. android:layout_height="1px"

  74. android:layout_margin="10dp"

  75. android:background="#EEEEEE"

  76. app:layout_constraintTop_toBottomOf="@id/tv_source" />

  77.  
  78. </android.support.constraint.ConstraintLayout>

又改造Adapter

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

 
  1. @Override

  2. public int getItemViewType(int position) {

  3. News news = dataItems.get(position);

  4. int imgSize = news.imgUrls.size();

  5. if (imgSize < 3) {

  6. return 0;

  7. } else if (imgSize == 3) {

  8. return 2;

  9. } else {

  10. return 3;

  11. }

  12. }

增加图集样式的ViewHolder

 
  1. static class MorePicViewHolder extends RecyclerView.ViewHolder {

  2. ImageView ivPic;

  3. TextView tvContent;

  4. TextView tvCount;

  5. TextView tvSource;

  6. TextView tvTime;

  7.  
  8. public MorePicViewHolder(View view) {

  9. super(view);

  10. ivPic = view.findViewById(R.id.iv_pic);

  11. tvContent = view.findViewById(R.id.tv_content);

  12. tvCount = view.findViewById(R.id.tv_count);

  13. tvSource = view.findViewById(R.id.tv_source);

  14. tvTime = view.findViewById(R.id.tv_time);

  15. }

  16. }

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

 
  1. @Override

  2. public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

  3. if (viewType == 0) {

  4. View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news_one_pic, parent, false);

  5. OnePicViewHolder holder = new OnePicViewHolder(view);

  6. return holder;

  7. } else if (viewType == 2) {

  8. View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news_three_pic, parent, false);

  9. ThreePicViewHolder holder = new ThreePicViewHolder(view);

  10. return holder;

  11. } else if (viewType == 3) {

  12. View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news_more_pic, parent, false);

  13. MorePicViewHolder holder = new MorePicViewHolder(view);

  14. return holder;

  15. } else {

  16. // Can't reach;

  17. return null;

  18. }

  19. }

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

 
  1. @Override

  2. public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

  3. int viewType = holder.getItemViewType();

  4. if (viewType == 0) {

  5. OnePicViewHolder viewHolder = (OnePicViewHolder) holder;

  6. News news = dataItems.get(position);

  7. viewHolder.tvContent.setText(news.content);

  8. viewHolder.tvSource.setText(news.source);

  9. viewHolder.tvTime.setText(news.time);

  10. Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic);

  11. } else if (viewType == 2) {

  12. ThreePicViewHolder viewHolder = (ThreePicViewHolder) holder;

  13. News news = dataItems.get(position);

  14. viewHolder.tvContent.setText(news.content);

  15. viewHolder.tvSource.setText(news.source);

  16. viewHolder.tvTime.setText(news.time);

  17. Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic1);

  18. Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(1)).into(viewHolder.ivPic2);

  19. Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(2)).into(viewHolder.ivPic3);

  20. } else if (viewType == 3) {

  21. MorePicViewHolder viewHolder = (MorePicViewHolder) holder;

  22. News news = dataItems.get(position);

  23. viewHolder.tvContent.setText(news.content);

  24. viewHolder.tvSource.setText(news.source);

  25. viewHolder.tvTime.setText(news.time);

  26. viewHolder.tvCount.setText(news.count + " 图");

  27. Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic);

  28. }

  29. }

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

反思

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

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

委托

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

 
  1. public interface IDelegateAdapter {

  2.  
  3. // 查找委托时调用的方法,返回自己能处理的类型即可。

  4. boolean isForViewType(News news);

  5.  
  6. // 用于委托Adapter的onCreateViewHolder方法

  7. RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType);

  8.  
  9. // 用于委托Adapter的onBindViewHolder方法

  10. void onBindViewHolder(RecyclerView.ViewHolder holder, int position, News news);

  11.  
  12. }

然后是三个委托

 
  1. public class OnePicDelegateAdapter implements IDelegateAdapter {

  2.  
  3. @Override

  4. public boolean isForViewType(News news) {

  5. // 我能处理一张图片

  6. return news.imgUrls.size() == 1;

  7. }

  8.  
  9. public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

  10. View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news_one_pic, parent, false);

  11. OnePicViewHolder holder = new OnePicViewHolder(view);

  12. return holder;

  13. }

  14.  
  15. public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, News news) {

  16. OnePicViewHolder viewHolder = (OnePicViewHolder) holder;

  17. viewHolder.tvContent.setText(news.content);

  18. viewHolder.tvSource.setText(news.source);

  19. viewHolder.tvTime.setText(news.time);

  20. Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic);

  21. }

  22.  
  23. static class OnePicViewHolder extends RecyclerView.ViewHolder {

  24. ImageView ivPic;

  25. TextView tvContent;

  26. TextView tvSource;

  27. TextView tvTime;

  28.  
  29. public OnePicViewHolder(View view) {

  30. super(view);

  31. ivPic = view.findViewById(R.id.iv_pic);

  32. tvContent = view.findViewById(R.id.tv_content);

  33. tvSource = view.findViewById(R.id.tv_source);

  34. tvTime = view.findViewById(R.id.tv_time);

  35. }

  36. }

  37. }

 
  1. public class ThreePicDelegateAdapter implements IDelegateAdapter {

  2.  
  3. @Override

  4. public boolean isForViewType(News news) {

  5. // 我能处理三张图片

  6. return news.imgUrls.size() == 3;

  7. }

  8.  
  9. public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

  10. View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news_three_pic, parent, false);

  11. ThreePicViewHolder holder = new ThreePicViewHolder(view);

  12. return holder;

  13. }

  14.  
  15. public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, News news) {

  16. ThreePicViewHolder viewHolder = (ThreePicViewHolder) holder;

  17. viewHolder.tvContent.setText(news.content);

  18. viewHolder.tvSource.setText(news.source);

  19. viewHolder.tvTime.setText(news.time);

  20. Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic1);

  21. Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(1)).into(viewHolder.ivPic2);

  22. Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(2)).into(viewHolder.ivPic3);

  23. }

  24.  
  25. static class ThreePicViewHolder extends RecyclerView.ViewHolder {

  26. ImageView ivPic1;

  27. ImageView ivPic2;

  28. ImageView ivPic3;

  29. TextView tvContent;

  30. TextView tvSource;

  31. TextView tvTime;

  32.  
  33. public ThreePicViewHolder(View view) {

  34. super(view);

  35. ivPic1 = view.findViewById(R.id.iv_pic1);

  36. ivPic2 = view.findViewById(R.id.iv_pic2);

  37. ivPic3 = view.findViewById(R.id.iv_pic3);

  38. tvContent = view.findViewById(R.id.tv_content);

  39. tvSource = view.findViewById(R.id.tv_source);

  40. tvTime = view.findViewById(R.id.tv_time);

  41. }

  42. }

  43.  
  44. }

 
  1. public class MorePicDelegateAdapter implements IDelegateAdapter {

  2.  
  3. @Override

  4. public boolean isForViewType(News news) {

  5. // 我能处理多张图片

  6. return news.imgUrls.size() > 3;

  7. }

  8.  
  9. public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

  10. View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news_more_pic, parent, false);

  11. MorePicViewHolder holder = new MorePicViewHolder(view);

  12. return holder;

  13. }

  14.  
  15. public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, News news) {

  16. MorePicViewHolder viewHolder = (MorePicViewHolder) holder;

  17. viewHolder.tvContent.setText(news.content);

  18. viewHolder.tvSource.setText(news.source);

  19. viewHolder.tvTime.setText(news.time);

  20. viewHolder.tvCount.setText(news.count + " 图");

  21. Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic);

  22. }

  23.  
  24. static class MorePicViewHolder extends RecyclerView.ViewHolder {

  25. ImageView ivPic;

  26. TextView tvContent;

  27. TextView tvCount;

  28. TextView tvSource;

  29. TextView tvTime;

  30.  
  31. public MorePicViewHolder(View view) {

  32. super(view);

  33. ivPic = view.findViewById(R.id.iv_pic);

  34. tvContent = view.findViewById(R.id.tv_content);

  35. tvCount = view.findViewById(R.id.tv_count);

  36. tvSource = view.findViewById(R.id.tv_source);

  37. tvTime = view.findViewById(R.id.tv_time);

  38. }

  39. }

  40.  
  41. }

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

 
  1. public class NewsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

  2.  
  3. private List<News> dataItems = new ArrayList<>();

  4.  
  5. public void setDataItems(List<News> dataItems) {

  6. this.dataItems = dataItems;

  7. notifyDataSetChanged();

  8. }

  9.  
  10. List<IDelegateAdapter> delegateAdapters = new ArrayList<>();

  11.  
  12. public void addDelegate(IDelegateAdapter delegateAdapter) {

  13. delegateAdapters.add(delegateAdapter);

  14. }

  15.  
  16. @Override

  17. public int getItemViewType(int position) {

  18.  
  19. }

  20.  
  21. @Override

  22. public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

  23.  
  24. }

  25.  
  26. @Override

  27. public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

  28.  
  29. }

  30.  
  31. @Override

  32. public int getItemCount() {

  33. return dataItems.size();

  34. }

  35.  
  36. }

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

 
  1. @Override

  2. public int getItemViewType(int position) {

  3. // 找到当前位置的数据

  4. News news = dataItems.get(position);

  5.  
  6. // 遍历所有的代理,问下他们谁能处理

  7. for (IDelegateAdapter delegateAdapter : delegateAdapters) {

  8. if (delegateAdapter.isForViewType(news)) {

  9. // 谁能处理返回他的index

  10. return delegateAdapters.indexOf(delegateAdapter);

  11. }

  12. }

  13.  
  14. throw new RuntimeException("没有找到可以处理的委托Adapter");

  15. }

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

 
  1. @Override

  2. public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

  3. // 找到对应的委托Adapter

  4. IDelegateAdapter delegateAdapter = delegateAdapters.get(viewType);

  5. // 把onCreateViewHolder交给委托Adapter去处理

  6. RecyclerView.ViewHolder viewHolder = delegateAdapter.onCreateViewHolder(parent, viewType);

  7. return viewHolder;

  8. }

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

 
  1. @Override

  2. public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

  3. // 找到当前ViewHolder的ViewType,也就是委托Adapter在集合中的index

  4. int viewType = holder.getItemViewType();

  5. // 找到对应的委托Adapter

  6. IDelegateAdapter delegateAdapter = delegateAdapters.get(viewType);

  7. // 把onBindViewHolder交给委托Adapter去处理

  8. delegateAdapter.onBindViewHolder(holder, position, dataItems.get(position));

  9. }

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

 
  1. RecyclerView recyclerView = findViewById(R.id.recycler_view);

  2. LinearLayoutManager layoutManager = new LinearLayoutManager(this);

  3. recyclerView.setLayoutManager(layoutManager);

  4. NewsAdapter mAdapter = new NewsAdapter();

  5. // 添加委托Adapter

  6. mAdapter.addDelegate(new OnePicDelegateAdapter());

  7. mAdapter.addDelegate(new ThreePicDelegateAdapter());

  8. mAdapter.addDelegate(new MorePicDelegateAdapter());

  9.  
  10. recyclerView.setAdapter(mAdapter);

双扩充样式

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

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

 
  1. public class News {

  2. public int type = 0; // 0:一张图片;1:三张图片;2:多张图片;3:视频类型

  3. public List<String> imgUrls = null;

  4. public String content = "";

  5. public String count = "";

  6. public String duration = "";

  7. public String source = "";

  8. public String time = "";

  9. public String link = "";

  10. }

添加xml布局

 
  1. <?xml version="1.0" encoding="utf-8"?>

  2. <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"

  3. xmlns:app="http://schemas.android.com/apk/res-auto"

  4. xmlns:tools="http://schemas.android.com/tools"

  5. android:layout_width="match_parent"

  6. android:layout_height="wrap_content"

  7. android:paddingLeft="10dp"

  8. android:paddingRight="10dp"

  9. android:paddingTop="10dp">

  10.  
  11. <TextView

  12. android:id="@+id/tv_content"

  13. android:layout_width="0dp"

  14. android:layout_height="wrap_content"

  15. android:ellipsize="end"

  16. android:maxLines="2"

  17. android:textColor="#333333"

  18. android:textSize="18sp"

  19. app:layout_constraintLeft_toLeftOf="parent"

  20. app:layout_constraintRight_toRightOf="parent"

  21. app:layout_constraintTop_toTopOf="parent"

  22. tools:text="这是一条新闻,这是一条新闻,这是一条新闻" />

  23.  
  24. <ImageView

  25. android:id="@+id/iv_pic"

  26. android:layout_width="0dp"

  27. android:layout_height="0dp"

  28. android:layout_marginTop="10dp"

  29. android:scaleType="centerCrop"

  30. app:layout_constraintDimensionRatio="5:3"

  31. app:layout_constraintLeft_toLeftOf="parent"

  32. app:layout_constraintRight_toRightOf="parent"

  33. app:layout_constraintTop_toBottomOf="@+id/tv_content"

  34. tools:src="@mipmap/ic_launcher" />

  35.  
  36. <ImageView

  37. android:layout_width="48dp"

  38. android:layout_height="48dp"

  39. android:src="@mipmap/play90"

  40. app:layout_constraintBottom_toBottomOf="@+id/iv_pic"

  41. app:layout_constraintLeft_toLeftOf="@+id/iv_pic"

  42. app:layout_constraintRight_toRightOf="@+id/iv_pic"

  43. app:layout_constraintTop_toTopOf="@+id/iv_pic" />

  44.  
  45. <TextView

  46. android:id="@+id/tv_duration"

  47. android:layout_width="wrap_content"

  48. android:layout_height="wrap_content"

  49. android:paddingBottom="6dp"

  50. android:paddingRight="10dp"

  51. android:textColor="@android:color/white"

  52. app:layout_constraintBottom_toBottomOf="@+id/iv_pic"

  53. app:layout_constraintRight_toRightOf="@+id/iv_pic"

  54. tools:text="12:34" />

  55.  
  56. <TextView

  57. android:id="@+id/tv_source"

  58. android:layout_width="wrap_content"

  59. android:layout_height="wrap_content"

  60. android:layout_marginTop="10dp"

  61. android:textColor="#888888"

  62. android:textSize="12sp"

  63. app:layout_constraintLeft_toLeftOf="parent"

  64. app:layout_constraintTop_toBottomOf="@+id/iv_pic"

  65. tools:text="澎湃新闻" />

  66.  
  67. <TextView

  68. android:id="@+id/tv_time"

  69. android:layout_width="wrap_content"

  70. android:layout_height="wrap_content"

  71. android:layout_marginLeft="8dp"

  72. android:textColor="#888888"

  73. android:textSize="12sp"

  74. app:layout_constraintLeft_toRightOf="@+id/tv_source"

  75. app:layout_constraintTop_toTopOf="@+id/tv_source"

  76. tools:text="07:33" />

  77.  
  78. <View

  79. android:layout_width="0dp"

  80. android:layout_height="1px"

  81. android:layout_margin="10dp"

  82. android:background="#EEEEEE"

  83. app:layout_constraintTop_toBottomOf="@id/tv_source" />

  84.  
  85. </android.support.constraint.ConstraintLayout>

添加委托Adapter

 
  1. public class VideoDelegateAdapter implements IDelegateAdapter {

  2.  
  3. @Override

  4. public boolean isForViewType(News news) {

  5. // 我能处理视频类型

  6. return news.type == 3;

  7. }

  8.  
  9. public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

  10. View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news_video, parent, false);

  11. VideoViewHolder holder = new VideoViewHolder(view);

  12. return holder;

  13. }

  14.  
  15. public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, News news) {

  16. VideoViewHolder viewHolder = (VideoViewHolder) holder;

  17. viewHolder.tvContent.setText(news.content);

  18. viewHolder.tvSource.setText(news.source);

  19. viewHolder.tvTime.setText(news.time);

  20. viewHolder.tvDuration.setText(news.duration);

  21. Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic);

  22. }

  23.  
  24. static class VideoViewHolder extends RecyclerView.ViewHolder {

  25. ImageView ivPic;

  26. TextView tvContent;

  27. TextView tvDuration;

  28. TextView tvSource;

  29. TextView tvTime;

  30.  
  31. public VideoViewHolder(View view) {

  32. super(view);

  33. ivPic = view.findViewById(R.id.iv_pic);

  34. tvContent = view.findViewById(R.id.tv_content);

  35. tvDuration = view.findViewById(R.id.tv_duration);

  36. tvSource = view.findViewById(R.id.tv_source);

  37. tvTime = view.findViewById(R.id.tv_time);

  38. }

  39. }

  40.  
  41. }

添加注册委托代理

 
  1. // 添加委托Adapter

  2. mAdapter.addDelegate(new OnePicDelegateAdapter());

  3. mAdapter.addDelegate(new ThreePicDelegateAdapter());

  4. mAdapter.addDelegate(new MorePicDelegateAdapter());

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

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

叒扩充样式

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

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

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

List<IDelegateAdapter> delegateAdapters = new ArrayList<>();

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

 
  1. // 用于保存委托Adapter

  2. private SparseArrayCompat<AdapterDelegate<Object>> delegates = new SparseArrayCompat();

  3. // 用于保存委托Adapter能处理的类型

  4. private SparseArray<String> dataTypes = new SparseArray<>();

 
  1. public AdapterDelegatesManager addDelegate(AdapterDelegate<Object, VH> delegate) {

  2. Type superclass = delegate.getClass().getGenericSuperclass();

  3. if (superclass instanceof ParameterizedType) {

  4. Class<?> clazz = (Class<?>) ((ParameterizedType) superclass).getActualTypeArguments()[0];

  5. String typeWithTag = clazz.getName();

  6.  
  7. int viewType = delegates.size();

  8. // Save the delegate to the collection;

  9. delegates.put(viewType, delegate);

  10. // Save the index of the delegate to the collection;

  11. dataTypes.put(viewType, typeWithTag);

  12. } else {

  13. // Has no generics.

  14. throw new IllegalArgumentException(

  15. String.format("Please set the correct generic parameters on %s.", delegate.getClass().getName()));

  16. }

  17. return this;

  18. }

每向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实现呢?

 
  1. public class Bill {

  2.  
  3. public String title = ""; // 标题

  4. public String waiter = ""; // 服务员

  5. public String cashier = ""; // 收银员

  6. public int ramadhin = 0; // 桌号

  7. public int guests = 0; // 客人数

  8. public String beginTime = ""; // 开台时间

  9. public String endTime = ""; // 结账时间

  10. public String duration = ""; // 用餐时长

  11. public List<Item> details = null; // 用餐详情

  12. public String total = ""; // 合计

  13. public String discounts = ""; // 优惠

  14. public String receivable = ""; // 应收

  15. public String describe = ""; // 描述信息

  16.  
  17. public static class Item {

  18. public String name = ""; // 名称

  19. public String count = ""; // 数量

  20. public String price = ""; // 单价

  21. public String subtotal = "";// 小计

  22. }

  23.  
  24. }

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

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

 
  1. // 用于保存委托Adapter

  2. private SparseArrayCompat<AdapterDelegate<Object, VH>> delegates = new SparseArrayCompat();

  3. // 用于保存委托Adapter能处理的类型

  4. private SparseArray<String> dataTypeWithTags = new SparseArray<>();

 
  1. public AdapterDelegatesManager addDelegate(AdapterDelegate<Object, VH> delegate, String tag) {

  2. Type superclass = delegate.getClass().getGenericSuperclass();

  3. if (superclass instanceof ParameterizedType) {

  4. Class<?> clazz = (Class<?>) ((ParameterizedType) superclass).getActualTypeArguments()[0];

  5. String typeWithTag = clazz.getName() + ":" + tag;

  6.  
  7. int viewType = delegates.size();

  8. // Save the delegate to the collection;

  9. delegates.put(viewType, delegate);

  10. // Save the index of the delegate to the collection;

  11. dataTypeWithTags.put(viewType, typeWithTag);

  12. } else {

  13. // Has no generics.

  14. throw new IllegalArgumentException(

  15. String.format("Please set the correct generic parameters on %s.", delegate.getClass().getName()));

  16. }

  17. return this;

  18. }

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

OK,很轻松就搞定了。剩余代码及详细demo请移步github DelegationAdapter

 

以后终于能开心地撸码了。

--------------------- 本文来自 周文凯 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/xuehuayous/article/details/80021325?utm_source=copy

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值