RecyclerView 里的自定义 LayoutManager 的一种设计与实现

原文连接html

好久好久之前,我分享过一篇文章,介绍了团队推出的一种异构的自定义 LayoutManger 的实现,它是基于 LinearLayoutManager 扩展实现的,这个项目的名字叫 vlayout,也许你之前据说过,或者在 github 上看到过,虽然还存在很多 bug 和不足,但能获得很多同窗的支持,真是感到欣慰。java

关于它的设计思路,其实在文章《Tangram 的基础 —— vlayout》里已经有过一些介绍,还有一些关于它的使用、功能介绍:vlayout使用说明(一)vlayout使用说明(二)。其实它不少细节能够展开介绍,其中可能涉及到 RecyclerView 自身的源码解读之类的。这里我想分享 vlayout 里其中一种 LayoutHelperLayoutHelper 负责具体的布局逻辑,是 vlayout 里抽象出的一个层次,能够参考前文连接详细了解)的设计与实现。android

说到这里,这篇文章的标题其实应该叫作:vlayout 里一种自定义 LayoutHelper 的设计与实现,考虑到可能有读者不明白,因此用『自定义 LayoutManager 的一种设计与实现』代替了一下。git

好,下面开始进入主题。github

需求场景

在 vlayout 里,提供了多种类型的 LayoutHelper 来负责布局逻辑,将不一样类型的 LayoutHelper 组合到一个 RecyclerView 里,实现了在同一个页面异构的、扁平化的布局能力。在考虑到一种布局结构须要对应实现一个 LayoutHelper 的时候,老是要考虑到将 item 扁平化地布局,这样才能最大程度发挥 RecyclerView 的回收复用能力。缓存

如今若是有这样一种需求场景:在组件 A 以两列布局模式的数据里流,以 4 个一组为单位,插入一块其余布局类型的组件,好比说是 3 列布局的组件 B。按照原先的作法,可能须要按照视觉样式,将 4 个一组的组件 A,包装到一个 GridLayoutHelper 里,而后将中插的每一块组件 B 区域,包装到另外一个 GridLayoutHerlper 里,这两种 GridLayoutHerlper 的主要区别在于列数不一样。bash

这样子作有一个小问题在于,从产生数据列表到 UI 展现列表的链路里,总有一个环节须要按照视觉样式来对数据进行切割分组操做。将这种数据切割的操做暴露给业务方,老是让人难受的,并且很容易出错。在更加复杂的业务场景下,数据来源方多是多种多样的,它只关心数据的吐出,而不是按照 UI 样式或者某一特定框架的协议来转换数据。架构

所以有必要侧重在端上进行设计,若是进一步考虑这个需求,能够将这种结构描述成一种树状结构。以上图为例,也就说处于根节点的的组件 A 列表,都是用 2 列结构的 GridLayoutHelper 来布局的,而根节点的组件列表里某些位置,插入一个组件 B 的列表,它们是用 3 列结构的 GridLayoutHelper 来布局的。这种描述可能有点抽象,以普通场景下、非 RecyclerView 里实现场景为例,也就是说假如要写一个自定义布局来绘制上述界面,其实就是写一个能进行 2 或 3 列布局的 ViewGroup,而后按照想要的结构自由组织就好了,而后最终咱们就能获得一个 View 的树。可是这种嵌套的结构 View 在 RecyclerView 只能做为一个总体来进行回收复用,还不够扁平化,回收复用的粒度就达不到咱们的要求,因此就提出了上述的逻辑上具有嵌套能力的树状结构。有了这样的逻辑结构来描述,就能够提供更加普适性的布局能力。解决这个问题的 LayoutHelper 就是本文要介绍的内容,它能够接收带逻辑上带嵌套结构的数据描述,同时又在最终布局的时候将每个 item 组件扁平化地、直接地挂载到 RecyclerView 下。app

实现思路与简介

有了描述布局的结构,接下来就是要按照设计来实现布局能力,若是是普通的自定义 ViewGroup,状况还比较容易,可是要结合到 RecyclerView 里,必须时时牢记扁平化实现,在 vlayout 的场景里,就是要新建一种 LayoutHelper 来实现。 以前有作过几回这样的尝试。第一种思路是像正常 View 层级同样写一个大的自定义 ViewGroup 做为总体的一个 RecyclerView 的组件,内部在作回收复用的分发处理,这样其实没有作到真正的扁平化,并且须要维护内部的子 View 布局高度消耗,以及与 RecyclerView 布局机制的协同,过程会比较麻烦,稍加尝试以后放弃。框架

第二种方式是实现一种 LayoutHelper,让它像系统 View 同样具有嵌套描述的能力。一开始将它想象的比较复杂,能够按照任意层次结构去嵌套、摆放,结果致使设计与实现都很是复杂。

尝试了前两种方案,实现成本和结果都不太理想,因而来从新审视最初的目标。并作了如下几点思考:1. 要在必定领域内解决问题,限定边界,不能单纯追求更大的灵活性而提高复杂度。2. 将问题简化为行级布局,由于自己 vlayout 里每一种 LayoutHelper 都是按行来布局的,LayoutHelper 内部每一次布局都是填满一整行的空间,而不一样 LayoutHelper 之间也都是按行划分的,不会出现同一行内两个不一样的 LayoutHelper 混搭。

因而,基于前面第二种方案进行简化,仍是实现一种自定义 LayoutHelper,在它引入了一种叫 RangeStyle 的结构来描述每一块区域的相对父节点起始位置以及它的样式,RangeStyle 能够按照设计上的逻辑嵌套结构来嵌套描述。这样最初设计上的逻辑树状结构就有了实体来承载。而在布局的时候,自定义 LayoutHelper 会获取到当前将要布局的 position,经过这个 position 来它所对应的 RangeStyle 节点信息,经过它提供的样式,好比 margin、padding、spanCount 等来控制当前 LayoutHelper 的行为。这样每次布局的组件就像在其余 LayoutHelepr 里的同样是直接挂载到 RecyclerView 下的,也达到了嵌套的描述、扁平化的实现的预设目标。

基于这样的思路,思考起来就很是清晰,与总体的 vlayout 设计自己就契合的很是好,实现起来也比较顺利。固然实现起来仍是有一些细节要调测,好比计算总体的 margin、padding 须要累加 RangeStyle 树里节点下的相同位置的边距;每一块区域的背景色也要像真的一层嵌套结构那样按照预期的层级堆叠排放。

我将它称之为 RangeGridLayoutHelper,主要是目由于前支持用来作这种嵌套的流式布局的实现。它的详细源码能够参考:RangeGridLayoutHelper

若是直接使用 vlayout,RangeGridLayoutHelper 的使用代码看起来多是这样的:

RangeGridLayoutHelper layoutHelper = new RangeGridLayoutHelper(4);
layoutHelper.setBgColor(Color.GREEN);
layoutHelper.setWeights(new float[]{20f, 26.665f});
layoutHelper.setPadding(15, 15, 15, 15);
layoutHelper.setMargin(15, 15, 15, 15);
layoutHelper.setHGap(10);
layoutHelper.setVGap(10);
GridRangeStyle rangeStyle = new GridRangeStyle();
rangeStyle.setBgColor(Color.RED);
rangeStyle.setSpanCount(2);
rangeStyle.setWeights(new float[]{46.665f});
rangeStyle.setPadding(15, 15, 15, 15);
rangeStyle.setMargin(15, 15, 15, 15);
rangeStyle.setHGap(5);
rangeStyle.setVGap(5);
layoutHelper.addRangeStyle(4, 7, rangeStyle);
GridRangeStyle rangeStyle1 = new GridRangeStyle();
rangeStyle1.setBgColor(Color.YELLOW);
rangeStyle1.setSpanCount(2);
rangeStyle1.setWeights(new float[]{46.665f});
rangeStyle1.setPadding(15, 15, 15, 15);
rangeStyle1.setMargin(15, 15, 15, 15);
rangeStyle1.setHGap(5);
rangeStyle1.setVGap(5);
layoutHelper.addRangeStyle(8, 11, rangeStyle1);
adapters.add(new SubAdapter(this, layoutHelper, 16));
复制代码

最佳实践

vlayout 虽然提供了异构布局的能力,可是我也认可,目前是接口(主要是 DelegateAdapter 以及各类 LayoutHelper 提供的接口)并不易用,开发者很难抛开那些具体的细节而后快速写出页面,在 Github 上也有同窗反馈过这个问题。之因此这样实际上是由于:咱们团队本身也并非直接使用 vlayout 进行开发,而是经过 Tangram 库来间接使用 vlayout,在 Tangram 主要是经过 JSON 数据来描述总体页面的结构,并封装了一个自定义的 Adater,它接收 Tangram 协议 JSON 数据,来自动建立、维护各类 LayoutHelper 的内部信息,这样就屏蔽了 vlayout 这些复杂的细节,而不是在使用 DelegateAdapter 的时候手动维护各个 LayoutHelper。建议到 Tangram 工程下进一步了解详细信息,对于原来使用 vlayout 开发的 app 来讲,理论上均可以迁移到 Tangram 架构,这样整个页面的渲染就能够由数据来驱动,提高页面的动态性。

那么说到动态性,Tangram 解决了页面结构的问题,至于每个 RecyclerView 里的 item,也能够称之为组件,它的动态性,咱们有另一个方案—— VirtualView,它是经过自定义 XML 来描述组件的布局结构,而后由自定义引擎解析 XML 数据并渲染出界面的方案。就比如在 Android 里写 XML 布局文件而后渲染展现,当动态下发 XML 数据的时候,组件样式也就能动态更新了。有兴趣的也能够进一步了解一下:

有了这两件利器,当下一次 PD 跑过来问你线上 XXX 能不能调整一下样式结构的时候,你就能够回答说『能够』,而不是等到下一次发版。并且咱们的重点功能、平常迭代,也主要是围绕 Tangram + VirtualView 来进行,这样能够更快用上最新特性。

更多关于 RecyclerView 的资料

最后,想说一点的是,整个 RecyclerView 体系的设计虽然很是强大、扩展性更好,但对于使用方来讲,想要扩展一个自定义的 LayoutManager 仍是比较麻烦的,这要求开发者深刻理解 RecyclerView 体系的设计及原理,这里收集了部分以前阅读过的资料,对于你们深刻理解 RecyclerView 或者 vlayout 都有好处:

相关文章
相关标签/搜索