企业微信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。