最近在研究设计模式的时候看到了Adapter模式,第一时间就想到了RecyclerView用到的Adapter,简单地走了一遍ReyclerView相关的源码,不得不感叹:设计得真的漂亮。
本文算不上源码分析,只能算是理解设计模式的初级内容。

1.整体把握

平时使用RecyclerView的时候大只可分为三个部分:
1.Adapter
2.LayoutManager
3.RcyclerView
将这三个部分组合在一起就构成了一个漂亮的「多视图展示」的View,作为这么一个优秀的控件,整体的显示结构可以用下图表示:

从右往左看,假设对与不同类型的Layout以及不同格式的数据,通过一个Adapter适配成为一个个ViewHolder,ViewHolder中缓存有每个用于显示的ItemView。ItemView的布局操作则交给了LanyoutManager来管理,ItemView可以根据LayoutManager中的布局策略,完成自己的布局操作,如果不想用系统提供的那三种LayoutManager,完全可以自己根据需求来定制一个,通过Adapter和LayoutManger,整个RecyclerView的功能变得十分强大,可定制性超级高。

2.跟进源码

说到底,RcyclerView终究只是一个ViewGroup,就从它的的onMeasure方法开始简单跟进一下,捋一捋LayoutManager和Adapter的使用时机,这样在以后的定制过程中会有更深的理解。
定位到onMeasure方法,先把主线拎出来,如下图:

从整个方法的调用链可以看出:在onMeasure开始执行的时候,就将measure操作委托给了LayoutManager。

@Override
protected void onMeasure(int widthSpec, int heightSpec) {if (mLayout == null) {// 如果没有设置LayoutManager,执行默认的测量操作defaultOnMeasure(widthSpec, heightSpec);return;}//如果设置了LayoutManager,先判断是否开启了自动测量if (mLayout.mAutoMeasure) {//开启了自动测量,根据ItemView所占大小,设置RecyclerView//将测量的操作委托给LayoutManager(mLayout就是LayoutManager的实例)mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);……} else {//没有开启自动测量//如果设置了固定的大小则直接将测量的操作委托给LayoutManagerif (mHasFixedSize) {mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);return;}//如果没有设置固定大小,执行自定义的测量流程……}
}

系统提供的几种layoutManager都有开启自动测量的功能,即LayoutManager会更具ItemView的大小,自动测量RecyclerView的宽高,具体算法我就没去深入了。
测绘完了后,LayoutManger就会给ItemView进行布局操作了,跳到onLayoutChildren方法中,这个方法的代码比较多,有200行左右,开头的注释描述了具体的布局算法,忽略掉相关的判断操作,最终都调用了一个fill()方法来实现来填充ItemView。

    @Overridepublic void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {// layout algorithm:// 1) by checking children and other variables, find an anchor coordinate and an anchor//  item position.// 2) fill towards start, stacking from bottom// 3) fill towards end, stacking from top// 4) scroll to fulfill requirements like stack from bottom.····if (mAnchorInfo.mLayoutFromEnd) {// fill towards start···fill(recycler, mLayoutState, state, false);····// fill towards end····            fill(recycler, mLayoutState, state, false);····} else {····        }····}

由于我真是抱着学习RecyclerView工作大致流程的心态去分析,也就没深入各种逻辑细节了,直接看到fill()方法。

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable) {···while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {···//迭代布局ItemViewlayoutChunk(recycler, state, layoutState, layoutChunkResult);···        }···}

看到循环了,顿时就松了一口气,大概也知道具体布局的ItemView的工作多半都是在循环中迭代完成的。

 void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,LayoutState layoutState, LayoutChunkResult result) {View view = layoutState.next(recycler);···// We calculate everything with View's bounding box (which includes decor and margins)// To calculate correct layout position, we subtract margins.layoutDecoratedWithMargins(view, left, top, right, bottom);···}

走到这一步了,注释也说得过去很明白了,计算得到的数据最终会传入layoutDecoratedWithMargins()方法来完成布局,再点进这个方法:

  public void layoutDecoratedWithMargins(View child, int left, int top, int right,int bottom) {final LayoutParams lp = (LayoutParams) child.getLayoutParams();final Rect insets = lp.mDecorInsets;child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,right - insets.right - lp.rightMargin,bottom - insets.bottom - lp.bottomMargin);
}

终于看到了朝思暮想的layout方法,也走到了ItemView布局的终点。
到此为止,LayoutManager的主线已经被拎出来了,下面就回退到layoutChunk() 中,不可以忽略第一句话View view = layoutState.next(recycler);, 这里获取View的方法尤为重要,应为View的来源是ViewHolder,而Adapter又管理者ViewHolder,自然而然Adapter的调用时机就在这里:

View next(RecyclerView.Recycler recycler) {if (mScrapList != null) {return nextViewFromScrapList();}final View view = recycler.getViewForPosition(mCurrentPosition);mCurrentPosition += mItemDirection;return view;
}

走到这,recycler出现了,他是Recycler的实例,也是负责复用与管理ItemView的类。在这个类里面可以轻松找到三个缓存ViewHolder的集合:

final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();

具体的复用缓存机制就不做深入了,点进getViewForPosition():

View getViewForPosition(int position, boolean dryRun) {return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}

发现调用的其实是:tryGetViewHolderForPositionByDeadline():

ViewHolder tryGetViewHolderForPositionByDeadline(int position,boolean dryRun, long deadlineNs) {···holder = mAdapter.createViewHolder(RecyclerView.this, type);···return holder;
}

终于看到了熟悉的createViewHolder()方法,这不正是我们实现Adapter时重写的几个方法之一吗?走到这里,Adapter的使用时机大概也知道了,也就完成了分析RecyclerView的目的,就不往下继续走了。很显然ViewHolder的构建需要上层使用者去具体完成,在RecyclerView#Adapter中定义的仅仅是一个抽象的Adapter。
最后总结一下,从设计模式的角度来将,ReyclerView的设计确实十分漂亮,LayoutManager和Adapter各司其职,协同合作,共同实现ReyclerView的功能,并且下层抽象的ViewHodler和Adapter也定义了相关的实现规范,使得上层用户在使用的时候学习成本非常低,并且无需关注优化的细节,不得不说“适配器模式”在RecyclerView的设计中运用的十分合适。

参考资料:RecyclerView源码分析(二)–测量流程

漂亮的Adapter模式-体会RecyclerView的设计实现相关推荐

  1. 也说说“从Adapter模式到Decorator模式”

    为什么80%的码农都做不了架构师?>>>    终于有时间写点什么了,可以前酝酿好的东西似乎一下子都忘记了.这几天看了wayfarer的<<让僵冷的翅膀飞起来>系列 ...

  2. java设计模式adapter_Java设计模式--适配器(Adapter)模式

    适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作. 适配器模式的用途 用电器做例子,笔记本电脑的插头一般都是三相的,即除了阳极.阴极 ...

  3. 用Adapter模式重构以前系统的登录权限验证

    Adapter模式概述 Adapter模式有两种形式,一种是类的形式,一种则是对象的形式.目标就是用Adapter将原本不兼容的几个接口可以一起工作,简单的说,就是将引用的东西转变成我们自己系统需要的 ...

  4. 面向模式的分析和设计(POAD)

    <?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /> 设计模式的作用 ...

  5. 小例子背后的大道理——Adapter模式详解

    上回问题回顾 前文说到一位用户拿着业界标准开关(一个标准的StandardSwitcher,它依赖IStandardSwitchable接口才能工作,然而目前我们的灯并不支持这个接口)出现在我面前,叫 ...

  6. Design Pattern: Adapter 模式 - Object Adapter

    您的电脑是个旧电脑,新的滑鼠都在使用USB接口了,而您的电脑上并没有USB,而只有一个PS2接口,这时您可以使用一个USB转PS2的接头作为转换,这样您的电脑就可以使用新滑鼠了(当然您也可以使用USB ...

  7. Facade与Adapter模式应用

    前言 作为设计模式第一篇随笔,首先以个人粗浅了解谈一谈何为设计模式. 简单来说,对于某一类新问题,可以使用前人为旧问题设计过的解决方案.将前人设计的模式应用到新问题上,不仅避免了许多可能碰壁的尝试,同 ...

  8. 设计模式【7】——适配器模式(Adapter 模式)

    文章目录 前言 一.适配器模式(Adapter 模式) 二.具体源码 1.Adapter.h 2.Adapter.cpp 3.main.cpp 三.运行结果 总结 前言 实际上在软件系统设计和开发中, ...

  9. 结构型模式之Adapter模式

    1.意图 将一个类的接口转换成客户希望的另外一个接口.Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作. 2.适用性 以下情况使用Adapter模式 (1)你想使用一个已经存 ...

最新文章

  1. 前端CSS预处理器Sass
  2. Bioinformatics: Assembling Genomes (week 1-2)
  3. 支持向量机SVM(四)
  4. 计算机专业是否限制语种,高考日语选什么专业(如果高考选日语,大学选专业有什么限制)...
  5. Android游戏开发系统控件-CheckBox
  6. Linux内核驱动之GPIO子系统(一)GPIO的使用
  7. 机器学习面试——逻辑回归和线性回归
  8. 第八篇: 消息总线(Spring Cloud Bus)(Finchley版本)V2.0_dev
  9. 关于SQLite.org网站给黑...
  10. Python 商务统计学 lesson 01
  11. Unity3D游戏开发之路:一月工作总结
  12. 人工神经网络评价法案例_人工神经网络评价法.
  13. linux系统浏览器没有图片不显示,网页不显示图片怎么回事【解决方法】
  14. Testflight上架丨苹果官方内测渠道详解
  15. 目标检测难题 | 小目标检测策略汇总
  16. epub格式电子书剖析之一:文档构成
  17. 特斯拉自家电池厂浮出水面!占地18万平米,就在加州超级工厂隔壁
  18. 读书笔记:《编程之美》
  19. “晓白”学python-科普篇(2)-人们都用python做什么?
  20. 证券市场基础知识(二)——股票、债券、基金

热门文章

  1. 常用的函数:atoi,itoa,atof,_ttoi等
  2. 用django实现一个资产管理的系统
  3. 转行智能控制的这些年(月无声智控事业部的前世今生)
  4. 刘鹏飞_2022_reStructured Pre-training
  5. 自定义微信小程序tabBar组件上边框的颜色
  6. Design Compiler初体验
  7. c++ sin\cos函数引用
  8. 期刊列表及发表文章的相关事项
  9. 多级联动(三级联动)
  10. Excel实现给加单引号,以及加逗号