本篇文章主要介绍iOS 滚动视图的复用问题解决方案,具有一定的参考价值,有兴趣的可以了解一下。
LazyScroll是什么
LazyScrollView 继承自ScrollView,目标是解决异构(与TableView的同构对比)滚动视图的复用回收问题。它可以支持跨View层的复用,用易用方式来生成一个高性能的滚动视图。
为什么要用LazyScrollView
我们在做首页的时候,往往展示的东西会很多,随着View数量逐渐膨胀,没有一套复用回收机制的ScrollView已经影响到性能了,迫切需要处理对ScrollView中View的复用和回收。使用TableView只能用来解决同类Cell的展示,然而在实际的场景中在ScrollView里面,View的种类往往会比较多,所以使用TableView不适合我们的场景。
而UICollectionView本身的布局和复用回收机制不够灵活,用起来也较为繁琐。所以诞生了LazyScrollView去解决这个问题。这也是天猫iOS客户端的首页落地方案。
LazyScroll使用
LazyScrollView的使用和TableView很像,不过多了一个需要实现的方法:返回对应index的View 相对LazyScrollView的绝对坐标。
实现LazyScrollViewDatasource
类似TableView的用法,我们需要使用方实现LazyScrollViewDatasource的Delegate。
@protocol TMMuiLazyScrollViewDataSource <NSObject>
@required
//ScrollView展示item个数
- (NSUInteger)numberOfItemInScrollView:(TMMuiLazyScrollView *)scrollView;
//要求根据index直接返回RectModel
- (TMMuiRectModel *)scrollView:(TMMuiLazyScrollView *)scrollView rectModelAtIndex:(NSUInteger)index;
//返回下标所对应的view
- (UIView *)scrollView:(TMMuiLazyScrollView *)scrollView itemByMuiID:(NSString *)muiID;
LazyScrollView的核心是在初始状态就得知所有View应该显示的位置。第一个方法很简单,获取LazyScrollView中item的个数。第二个方法需要按照Index返回TMMuiRectModel ,它会携带对应index的View 相对LazyScrollView的绝对坐标。
这里出现了一个TMMuiRectModel ,这是个什么东西呢?我们看一下代码:
@interface TMMuiRectModel:NSObject
//转换后的绝对值rect
@property (nonatomic,assign) CGRect absRect;
//业务下标
@property (nonatomic,copy) NSString *muiID;
这里有两个属性,absRect是LazyScroll中的View相对LazyScrollView的绝对坐标,muiID是这个View在LazyScrollView中唯一的标识符,可赋值也可不赋值。
第三个方法,返回View。
@interface UIView(TMMui)
//索引过的标识,在LazyScrollView范围内唯一
@property (nonatomic, copy) NSString *muiID;
//重用的ID
@property (nonatomic, copy) NSString *reuseIdentifier;
首先,我们在UIView之外加了一个Category,这个category可以让View携带muiID和reuseIdentifier,对于返回的View来说,只需要在乎对View的reuseIdentifier赋值,muiID的赋值会在lazyScrollView中处理掉。reuseIdentifier相同的View会被复用,如果这个View的reuseIdentifier是nil或者空字符串,则不会被复用。
LazyScrollView内部原理分析
首先来看一个简单的案例:
根据DataSource获取所有的TMMuiRectModel
根据DataSource的Delegate,拿到所有的View应该被显示的位置。这一步,核心是拿到的位置是确定的。根据Demo,我们观察从 0/1 - 2/3 之间这些View,这个时候LazyScrollView拿到的Rect如下:
| Index | 标号(MUIID) | Rect |
|---|---|---|
| 0 | 0/0 | origin = (x = 25, y = 15), size = (width = 156, height = 150 |
| 1 | 0/1 | origin = (x = 194, y = 15), size = (width = 156, height = 150) |
| 2 | 0/2 | origin = (x = 25, y = 180), size = (width = 156, height = 150) |
| 3 | 0/3 | origin = (x = 194, y = 180), size = (width = 156, height = 150 |
| 4 | 1/0 | origin = (x = 5, y = 360), size = (width = 177.5, height = 150) |
| 5 | 1/1 | origin = (x = 192.5, y = 426), size = (width = 84, height = 84) |
| 6 | 1/2 | origin = (x = 192.5, y = 360), size = (width = 177.5, height = 56) |
| 7 | 1/3 | origin = (x = 286.5, y = 426), size = (width = 83.5, height = 84) |
| 8 | 2/0 | origin = (x = 25, y = 530), size = (width = 325, height = 150) |
| 9 | 2/1 | origin = (x = 25, y = 695), size = (width = 325, height = 150) |
| 10 | 2/2 | origin = (x = 25, y = 860), size = (width = 325, height = 150) |
排序
拿到了这些位置之后,接下来做的事情就是排序。排序生成的索引会有两个:根据顶边(y)升序排序的索引和根据底边(y+height)降序排序的索引。
根据顶边(y)升序排序的索引
| Index | 标号(MUIID) | Rect |
|---|---|---|
| 0 | 0/0 | origin = (x = 25, y = 15), size = (width = 156, height = 150 |
| 1 | 0/1 | origin = (x = 194, y = 15), size = (width = 156, height = 150) |
| 2 | 0/2 | origin = (x = 25, y = 180), size = (width = 156, height = 150) |
| 3 | 0/3 | origin = (x = 194, y = 180), size = (width = 156, height = 150 |
| 4 | 1/0 | origin = (x = 5, y = 360), size = (width = 177.5, height = 150) |
| 5 | 1/1 | origin = (x = 192.5, y = 360), size = (width = 177.5, height = 56) |
| 6 | 1/2 | origin = (x = 192.5, y = 360), size = (width = 177.5, height = 56) |
| 7 | 1/3 | origin = (x = 286.5, y = 426), size = (width = 83.5, height = 84) |
| 8 | 2/0 | origin = (x = 25, y = 530), size = (width = 325, height = 150) |
| 9 | 2/1 | origin = (x = 25, y = 695), size = (width = 325, height = 150) |
| 10 | 2/2 | origin = (x = 25, y = 860), size = (width = 325, height = 150) |
根据底边(y+height)降序排序的索引
| Index | 标号(MUIID) | Rect |
|---|---|---|
| 0 | 2/2 | origin = (x = 25, y = 860), size = (width = 325, height = 150) |
| 1 | 2/1 | origin = (x = 25, y = 695), size = (width = 325, height = 150) |
| 2 | 2/0 | origin = (x = 25, y = 530), size = (width = 325, height = 150) |
| 3 | 1/0 | origin = (x = 5, y = 360), size = (width = 177.5, height = 150) |
| 4 | 1/2 | origin = (x = 192.5, y = 360), size = (width = 177.5, height = 56) |
| 5 | 1/3 | origin = (x = 286.5, y = 426), size = (width = 83.5, height = 84) |
| 6 | 1/1 | origin = (x = 192.5, y = 426), size = (width = 84, height = 84) |
| 7 | 0/2 | origin = (x = 25, y = 180), size = (width = 156, height = 150) |
| 8 | 0/3 | origin = (x = 194, y = 180), size = (width = 156, height = 150 |
| 9 | 0/0 | origin = (x = 25, y = 15), size = (width = 156, height = 150 |
| 10 | 0/1 | origin = (x = 194, y = 15), size = (width = 156, height = 150) |
查找
前两步是在执行完reload,在视图还没有生成的时候就开始做了,而接下来的步骤在要生成视图(初始化或滚动的时候)才会去做。
我们设定了Buffer为上下各20,滚动超过20个像素后才会指定查找视图并显示的动作。举个例子,如下图,红圈是应该显示的区域。
- (UIView *)scrollView:(TMMuiLazyScrollView *)scrollView itemByMuiID:(NSString *)muiID;
复用还是不复用,是由datasource决定的。如果要复用,需要datasource方法内调用,即:
- (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier
获取复用的View,这个方法取出来的View就是在上一段所说的Dictionary中拿的。
最后我们看一下LazyScrollView的使用流程:找到所有View将要显示的位置 – 排序 – 查找应该显示的View – 回收 – 创建/复用。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程学习网。
本文标题为:详解iOS 滚动视图的复用问题解决方案
- Android实现监听音量的变化 2023-03-30
- Android实现轮询的三种方式 2023-02-17
- Android studio实现动态背景页面 2023-05-23
- Android MaterialButton使用实例详解(告别shape、selector) 2023-06-16
- 详解flutter engine 那些没被释放的东西 2022-12-04
- 最好用的ios数据恢复软件:PhoneRescue for Mac 2023-09-14
- SurfaceView播放视频发送弹幕并实现滚动歌词 2023-01-02
- iOS 对当前webView进行截屏的方法 2023-03-01
- Flutter实现底部和顶部导航栏 2022-08-31
- 作为iOS开发,这道面试题你能答出来,说明你基础很OK! 2023-09-14
