Tangram的组织结构
Tangram结构图从Tangram结构图上看,主要分为页面(TangramView+TMlazyScrollView),布局(layout)和组件(element)三部分。页面是由继承自TMLazyScrollView的TangramView构成,TMLazyScrollView是一个独立的视图组件,本文主要内容为如何实现一个TMLazyScrollView。
模型
TMLazyItemModel
TMLazyItemModel是TMLazyScrollView中使用的基本模型,存储着TMLazyScrollView中的view对应的布局信息和唯一标识符,模型中包含的属性主要有:
- CGRect absRect; // view在LazyScrollView中相对于LazyScrollView的frame
- NSString *muiID; // view在LazyScrollView的唯一id
- 什么是muiID?
muiID的是由这几部分构成的:
layout.layoutType + model.itemType + model.reuseIdentifier + layout.layoutIdentifier + model.index(将lazyScrollView中的view视为同一层级之后的下标)
这里的model就是lazyItemModel,由于与view一一对应,所以model的index与view的index也是一样的。
TMLazyModelBucket
TMLazyModelBucket是基本模型TMLazyItemModel上的一层封装,是比TMLazyItemModel层级更高的一个模型。是将origin.y在i*bucketHeight~(i+1)bucketHeight范围内的TMLazyItemModel放在一个集合里,然后所有的集合构成的一个数组。所以需要包含的属性主要有:
- NSMutableArray<NSMutableSet *> _buckets; // 0 ~ bucketHeight中的model保存在index为0的set中。bucketHeight~2bucketHeight中的model保存在index为1的set中。
需要提供的方法应该包括:
- -(void)addModel:(TMLazyItemModel *)itemModel;
控制器
TMLazyScrollView
TMLazyScrollView就是控制器,管理着的模型包括TMLazyModelBucket,TMLazyScrollView上的视图view,以及为了view重用而衍生的view唯一标识符muiID,所以包含的主要属性有:
- TMLazyModelBucket *_modelBucket; // 通过TMLazyModelBucket,保存了所有的lazyItemModel。
- NSMutableSet<UIView *> *_visibleItems; // 这里边存放的是可见范围的view视图
- NSMutableSet<NSString *> _newVisibleMuiIDs; // 这个reloadData中在新的可视范围内的待处理(需要创建element或者重新刷新element)的model的MuiID的set,当generateItems一个 MuiID(创建element或者刷新element)之后,就会将这个MuiID从这个集合中删除掉。
- TMLazyReusePool *reusePool; // 保存复用的view
下面是一些次要属性,主要用来保存业务处理过程中临时保存的数据,可以先放着不看。
* NSMutableSet<NSString *> *_needReloadingMuiIDs; // 存储需要刷新内容的item对应的muiID
* NSSet<NSString *> *_lastInScreenVisibleMuiIDs; // 存储上次可视范围内的TLMLazyItemModel的MuiIDs。
* NSMutableSet<NSString *> *_inScreenVisibleMuiIDs; // 当前可视范围内的TMLazyItemModel的MuiIDs。
TMLazyScrollView还需要一个dataSource代理,代理需要实现一些方法用来提供一些必要的数据:
- id<TMLazyScrollViewDataSource> dataSource;
dataSource需要实现的方法有:
1> - (NSUInteger)numberOfItemsInScrollView:(TMLazyScrollView *)scrollView
返回有多少个model。
2> - (TMLazyItemModel *)scrollView:(TMLazyScrollView *)scrollView itemModelAtIndex:(NSUInteger)index
返回第index个model。
3> - (UIView *)scrollView:(TMLazyScrollView *)scrollView itemByMuiID:(NSString *)muiID
根据muiID返回view。
TMLazyScrollView对外提供的方法有:
- -reloadData() // 获取数据后或者数据更新后加载全部数据
- -setContentOffset: // 手动滑动lazyScrollView之后视图的变化
reloadData方法的实现流程
-
[self storeItemModelsFromIndex:0]
清理所有本地数据;将所有的view对应的lazyModel存放进_modelBucket中,需要dataSource的方法1>和2>来提供lazyModel。 -
-assembleSubviews:minY:maxY:
统计当前可见范围内的model的muiID集合newVisibleMuiIDs。
2.1 -recycleItems:newVisibleMuiIDs: (isReload参数为YES)
遍历当前的可视view数组,_visibleItems
2.1.1 如果其中的view的muiID在将要出现的muiIDs集合newVisibleMuiIDs中,那么这个view只需要刷新其中的数据即可,将其muiID加入_needReloadingMuiIDs中。
2.1.2 如果其中的view的muiID不在将要出现的muiIDs集合newVisibleMuiIDs中,那么这个view就需要离开屏幕
2.1.2.1 此时需要调用view的mui_didLeave()方法
2.1.2.2 如果此view的reuseIdentifier.length>0,也就是说这个view是需要被重用的,那么就将其隐藏,并且加入reusePool中,将此element从_visibleItems数组中移除掉。
2.1.2.3 如果此itemView不打算重用,将其muiID加入_needReloadingMuiIDs中。
2.2 -generateItems: (isReload参数为YES)
获取view
2.2.1 对于_newVisibleMuiIDs中的任一muiID
2.2.1.1 如果不在_visibleItems中,或者需要刷新,则需通过代理dataSource的方法3>获取view,获取到itemView之后,(如何通过代理dataSource获取view?)【TMLazyScrollView如何通过代理获取element?】
2.2.1.1.1 调用view的方法mui_afterGetView
2.2.1.1.2 设置view的muiID,并设置hidden为NO,就是要让其显示出来,
2.2.1.1.3 如果该view之前没有显示在屏幕上,那么这次需要将其加入_visibleItems数组中
2.2.1.1.3 将其重_needReloadingMuiIDs中删除
2.2.1.2 对于newVisibleMuiIDs中的任一muiID,如果在_visibleItems中并且不需要刷新,那么不需要做其他操作
2.2.2 将muiID从_newVisibleMuiIDs总移除,如果_newVisibleMuiIDs不为空,则继续调用generateItems
reloadData流程中的-recycleItems:newVisibleMuiIDs:的处理流程可以用下图来表示:
reloadData流程中的-recycleItems:newVisibleMuiIDs:reloadData流程中的-generateItems:的处理流程可以用下图来表示:
reloadData流程中的-generateItems:setConentOffset方法的实现流程
-
用_lastContentOffset记录了上次滑动的contentOffset,当当前contentOffse.y和_lastContentOffset.y的差值绝对值大于LazyBufferHeight时,更新_lastContentOffset为当前的contentOffset,并调用assembleSubviews
-
-assembleSubviews:minY:maxY:
剩余流程同reloadData。
setConentOffset流程中的-recycleItems:newVisibleMuiIDs:的处理流程可以用下图来表示:
setConentOffset流程中的-recycleItems:newVisibleMuiIDs:setConentOffset和reloadData的区别
区别在于recycleItem:newVisibleMuiIDs:中的第一个参数isReload,手动滑动isReload为NO,reloadData中的isReload为YES。
- 如果_visibleItems中的element还要呆在屏幕上:
1> 对于reloadData,仍然需要刷新其中内容 (加入_needReloadingMuiIDs数组中)
2> 对于手动滑动,不需要刷新 - 如果_visibleItems中的element不需要呆在屏幕上
1> 对于reloadData,仍然需要刷新其中内容 (加入_needReloadingMuiIDs数组中)
2> 对于手动滑动,不需要刷新
TMLazyScrollView通过self.dataSource获取view
具体调用流程为:
1 - (UIView *)scrollView:(TMLazyScrollView *)scrollView itemByMuiID:(NSString *)muiID
该方法是TangramView实现TMLazyScrollView的dataSource的3>代理方法,TangramView在实现中会调用VC实现的返回itemView代理方法
2 - (UIView *)itemInTangramView:(TangramView *)view withModel:(NSObject<TangramItemModelProtocol> *)model forLayout:(UIView<TangramLayoutProtocol> *)layout atIndex:(NSUInteger)index
这是VC需要实现的代理方法。在其中,可能会调用dequeueReusableItemWithIdentifier:方法, 调用的是TMLazyScrollView的方法dequeueReusableItemWithIdentifier:(muiID:)
2.1 - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier muiID:(NSString *)muiID
如果当前需要的(newVisibleMuiIDs中的muiID)view在屏幕上,则直接使用该view;
如果不在屏幕上,则从reusepool中拿一个,返回之;
如果什么也没有,则直接返回nil。
2.2 如果dequeueReusableItemWithIdentifier:返回结果非空,则用model去刷新该element。
reuseableView = [TangramDefaultDataSourceHelper refreshElement:reuseableView byModel:model layout:layout tangramBus:self.tangramBus];
2.3 如果dequeueReusableItemWithIdentifier:返回结果为空,则需要创建一个element,并用model数据去填充该element。
reuseableView = [TangramDefaultDataSourceHelper elementByModel:model layout:layout tangramBus:self.tangramBus];
从这个角度来看,TMLazyScrollView还需要实现一个重用view的方法,dequeueReusableItemWithIdentifier:(muiID:)
dequeueReusableItemWithIdentifier:(muiID:)方法实现流程
- 通过identifier从reusepool中查找view
1.1 如果找到,调用view的prepareForReuse - 返回找到的view或者nil
总结
模型:
- TMLazyItemModel
- TMLazyModelBucket
控制器:
- TMLazyScrollView
属性:
- TMLazyModelBucket *_modelBucket;
- NSMutableSet<UIView *> *_visibleItems;
- NSMutableSet<NSString *> _newVisibleMuiIDs;
- TMLazyReusePool *reusePool;
- id<TMLazyScrollViewDataSource> dataSource;
方法:
- reloadData
- setConentOffset
- dequeueReusableItemWithIdentifier
网友评论