企业微信Duilib奇怪的RecyclerView
背景
之前分析了个人微信Duilib的RecyclerView:[[原创]微信Duilib的List优化和代价]( 当时的企业微信还没有RecyclerView,最近看了下,发现已经有类似的RecyclerView。 只是看起来有点奇怪,所以简单梳理一下背后的设计思路。
布局
类似spy++,写了一个Duilib的界面元素查看工具,列表List相关的布局如下:
class mmListView
class mmTreeView
class xxxItemView
一般来说,Tree 类似 List的复杂结构。设计上一般两种方案:1、像原生的Duilib是Tree继承List;2、像QT是Tree和List并列,但继承同一个抽象类。但从界面布局上可以看出mmListView里面包含了mmTreeView,奇怪的点就在这里。
关系
界面布局的关系不一定映射底层代码实现的关系,于是进一步看看这两个类:
mmListView继承:CContainerUI、mmTreeViewDataSource
mmTreeView继承:CVerticalLayoutUI(继承CContainerUI)
从继承关系可以看出,mmListView和mmTreeView算是并列的,但又不像QT一样继承同一个抽象类。反而是mmListView继承mmTreeViewDataSource,提供数据给mmTreeView使用,也就是说,mmListView提供了列表的所有元素,而mmTreeView从中拿到部分元素做显示。这样子就可以进行Data和View的分离,从而实现RecyclerView。
数据
类的关系理清,进一步就是数据的结构,看看最底层的数据是怎么组织起来的。 mmTreeView继承CContainerUI,没有重写AddAt,也就是说mmTreeView的元素数据结构和CContainerUI一样都是数组。
DuiLib::CContainerUI::AddAt
DuiLib::CStdPtrArray::InsertAt
滚动列表,包含元素的数组会整体移动,可以看出确实有回收复用:

换句话说,mmTreeView就是RecyclerView,但是底层并不是概念上的树形结构。
逻辑
有了数据结构,再看看列表滚动对应的逻辑代码:
滚动修改控件位置:
int __thiscall mmTreeView::SetScrollPos(int this, struct tagSIZE a2)
offset = curScrollPos - *(this + 0x588);
*(this + 0x588) = curScrollPos;
if ( offset >= 0 )
sub_1B67AD(this, v6); // ⬇向下滚动
else
sub_1B66EE(this, v6); // ⬆向上滚动
向下滚动:
int __thiscall sub_1B67AD(int this, int a2)
1、尾部加元素
if ( TailAddItem(this, v5, rcDst.top, rcDst.bottom - rcDst.top) )
2、头部删元素
HeadDelItem(this);
向上滚动:
int __thiscall sub_1B66EE(int this, int a2)
1、头部加元素
if ( HeadAddItem(v5, rcDst.bottom, rcDst.bottom - rcDst.top) )
2、尾部删元素
TailDelItem(this);
特殊的是,元素只是被移除,没有真正的删除,方便后续复用:
DuiLib::CContainerUI::RemoveNoDelete
树形
List是一维结构,Tree是二维结构,理论上也可以先实现Tree,然后List基于Tree特殊处理,让二维结构降成一维结构。
好奇Tree本身又是怎么实现的,树形Tree相关的布局如下:
class xxxTreeView
class mmTreeView3
class xxxTreeItem
再看看继承关系:
mmTreeView3继承:CVerticalLayoutUI(继承CContainerUI)
这就意味着mmTreeView3和mmTreeView一样底层都是数组。
总结
Data和View确实是分离了,也实现了回收和复用。但不管是一维的List还是二维的Tree,在界面上显示都是一个一维的列表(二维的Tree通过缩进元素表达层次结构)。至于mmTreeView和mmTreeView3实际上都是RecyclerView。