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个像素后才会指定查找视图并显示的动作。举个例子,如下图,红圈是应该显示的区域。
如上图所示,现在已知的是红圈顶边y是242,底边y是949,加上缓冲区Buffer,应该是找222 - 969 之间的View。我们要做的是,找到底边y小于969的Model和顶边y大于222的Model,取交集,就是我们要显示的View。
采用的方法为二分查找,在根据顶边升序排序的索引中找949,找到的index为0(MUIID为2/2),我们使用一个Set,把根据顶边排序中index >= 0 的元素先放在这里。获取的Set中包含的muiID为 0/0,0/1,0/2,0/3,1/0,1/1,1/2,1/3,2/0,2/1,2/2。
根据底边排序的索引中找222,找到的index为2,我们把index >= 2的元素放在另一个Set,获取的Set中包含的muiID为0/2,0/3,1/0,1/1,1/2,1/3,2/0,2/1,2/2
两个Set取交集,得到的就是我们的ResultSet,这里面都是我们要显示View的Model,它们的muiID是0/2,0/3,1/0,1/1,1/2,1/3,2/0,2/1,2/2。
回收、复用、生成
我们知道了应该显示哪些View,但是我们之后做的第一步是把不需要显示的View加入到复用池中。LazyScroll可以取到当前显示了的View,拿当前显示的View的muiID和将要显示view的Model的muiID做对比,可以知道当前显示的View哪些应该被回收。
LazyScrollView中有一个Dictionary,key是reuseIdentifier,Value是对应reuseIdentifier被回收的View,当LazyScrollView得知这个View不该再出现了,会把View放在这里,并且把这个View hidden掉。
然后,用LazyScrollView会去调用datasource。
- (UIView *)scrollView:(TMMuiLazyScrollView *)scrollView itemByMuiID:(NSString *)muiID;
复用还是不复用,是由datasource决定的。如果要复用,需要datasource方法内调用,即:
- (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier
获取复用的View,这个方法取出来的View就是在上一段所说的Dictionary中拿的。
最后我们看一下LazyScrollView的使用流程:找到所有View将要显示的位置 – 排序 – 查找应该显示的View – 回收 – 创建/复用。
iOS 滚动视图的复用问题解决方案的更多相关文章
- ios 滚动视图响应touchesBegin,touchesEnd等方法
能够滚动的控件都不会响应touchesBegin,touchesEnd等方法,这就需要对这个类进行封装 以UITextView为例 1,创建CustomTextView类,继承与UITextView ...
- Xamarin iOS教程之进度条和滚动视图
Xamarin iOS教程之进度条和滚动视图 Xamarin iOS 进度条 进度条可以看到每一项任务现在的状态.例如在下载的应用程序中有进度条,用户可以很方便的看到当前程序下载了多少,还剩下多少.Q ...
- iOS开发之视差滚动视图
首先声明一点,由于自己iOS开发经验有限,这里给下面将要实现的效果起名叫视差滚动视图,自己也不知道是否严谨,等以后有经验了,再来更新吧. 一.需求 有的时候我们可能会有这样一种需求,在一个UITabl ...
- iOS学习笔记——滚动视图(scrollView)
滚动视图:在根视图中添加UIScrollViewDelegate协议,声明一些对象属性 @interface BoViewController : UIViewController<UIScro ...
- iOS关于菜单滚动视图实现
菜单滚动视图也是在项目开发过程中比较常用到的功能,先直接看效果图 实现的效果如下: 当菜单个数的总长度超过一个屏宽度就计算每一个的文字宽度,若没有则只进行一个屏平分,点击菜单项时,滚动的视图位置会随着 ...
- UIScrollView 滚动视图—IOS开发
转自:http://blog.csdn.net/iukey/article/details/7319314 UIScrollView 类负责所有基于 UIKit 的滚动操作. 一.创建 CGRect ...
- 弹层蒙版(mask),ios滚动穿透,我们项目的解决方案
问题描述 项目开发遇到一个ios独有的问题,在wkwebview中稳定复现 问题: 弹出一个蒙版,当在蒙版上面滑动的时候蒙版后面的内容滚动了 这当然是ios的bug,但是经过我们测试iphone7也会 ...
- iOS渐变视图&动画库、腰杆、音频水滴水波手势、多种对话框、四级展开效果等源码
iOS精选源码 用户行为追踪--无侵入埋点 .终端日志的打印 支持storyboard的渐变视图&动画库 支持圆形.竖直.横向的摇杆 纯swift实现的类似excel表格效果 swift实现自 ...
- 制作滚动视图(ScrollView)
怎样判断是否应当使用滚动视图 所谓的滚动视图,是指一个可以滑动的视窗,视窗大小和位置固定不变,视窗内的内容用户可以通过手指滑动或者拖动滚动天来进行滚动浏览. 滚动视图的目的是为了解决同类内容过多,一个 ...
随机推荐
- python九九乘法表
j = 1 while j <= 9: i = 1 while i <= j: print("%d*%d=%d\t" % (i, j, i*j), end=" ...
- 走在spring的路上。。。。
一些spring的概念理解: 1.为什么需要spring? spring与我们平时用的工厂模式最大的差别在于,工厂模式设计还需要单独去建一个工厂类并去维护它, 而spring可只通过配置文件便可创建并 ...
- [LeetCode] 2 Keys Keyboard 两键的键盘
Initially on a notepad only one character 'A' is present. You can perform two operations on this not ...
- 解决有关flask-socketio中服务端和客户端回调函数callback参数的问题(全网最全)
由于工作当中需要用的flask_socketio,所以自己学习了一下如何使用,查阅了有关文档,当看到回调函数callback的时候,发现文档里都描述的不太清楚,最后终于琢磨出来了,分享给有需要的朋友 ...
- XMLHTTPRequestObject获取服务器数据
http://www.educity.cn/develop/526316.html 在Web客户端使用xmlhttp对象,可以十分方便的和服务器交换数据,我们可以获取和发送任何类型的数据,甚至二进制数 ...
- [SDOI2014]重建
题目描述 T国有N个城市,用若干双向道路连接.一对城市之间至多存在一条道路. 在一次洪水之后,一些道路受损无法通行.虽然已经有人开始调查道路的损毁情况,但直到现在几乎没有消息传回. 辛运的是,此前T国 ...
- 紧急疏散evacuate
1689: [HNOI2007]紧急疏散evacuate 题目描述 发生了火警,所有人员需要紧急疏散!假设每个房间是一个N M的矩形区域.每个格子如果是".",那么表示这是一块空地 ...
- codeforces round #419 A. Karen and Morning
Karen is getting ready for a new school day! It is currently hh:mm, given in a 24-hour format. As yo ...
- NOIP提高组2010 乌龟棋
小明过生日的时候,爸爸送给他一副乌龟棋当作礼物. 乌龟棋的棋盘是一行N个格子,每个格子上一个分数(非负整数).棋盘第1格是唯一的起点,第N格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点. 乌 ...
- [BZOJ]2017省队十连测推广赛1 T2.七彩树
题目大意:给你一棵n个点的树,每个点有颜色,m次询问,每次询问一个点x的子树内深度不超过depth[x]+d的节点的颜色数量,强制在线.(n,m<=100000,多组数据,保证n,m总和不超过5 ...