最近在研究设计模式的时候看到了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. Centos和Redhat的区别和联系
  2. Java Review - 并发编程_并发List_CopyOnWriteArrayList源码剖析
  3. 站在巨人的肩膀上眺望未来
  4. win7卡在正在启动windows界面_win7如何重装ie8
  5. mysql5.7 glibcxx_3.4.15_Requires: libstdc++.so.6(GLIBCXX_3.4.15)(64bit)
  6. 程序显示文本框_【教程】TestComplete测试桌面应用程序教程(二)
  7. openresty—实现缓存前移
  8. AngularJS自定义指令–隔离范围教程
  9. 一个女孩为什么要努力
  10. 成人高考自考资讯网源码 织梦dedecms模板
  11. 基于R语言的地理探测器实现与问题研究
  12. termux android api,Termux API
  13. PHP字符串解析函数
  14. uni-app小程序与app端的兼容问题
  15. 4-9 Python对象的自省机制
  16. 程序员为什么要转行项目经理
  17. 电容在计算机运用原理,隔直电容的作用及原理 - 全文
  18. html特殊符号对照表
  19. sl4a+android截屏,在Android桌面上使用SL4A Python显示数据(example)
  20. 一键登陆网易163邮箱

热门文章

  1. 华盛顿大学西雅图 计算机科学 申请条件,华盛顿大学西雅图分校申请有哪些条件...
  2. 信息论与编码-python实现三种编码(香农编码,费诺编码,赫夫曼编码)
  3. Element2 el-tooltip 滚动时 不消失
  4. 论文阅读: Disentangled lmage Colorization via Global Anchors
  5. 多维数组变成一维数组
  6. Jetpack Compose——Text(文本)的使用
  7. Component属性
  8. wps表格日期计算天数_如何计算Google表格中两个日期之间的天数
  9. 通达信指标转python_通达信转python
  10. PostgreSQL索引(一)