詳解iOS 滾動(dòng)視圖的復(fù)用問題解決方案
LazyScroll是什么
LazyScrollView 繼承自ScrollView,目標(biāo)是解決異構(gòu)(與TableView的同構(gòu)對(duì)比)滾動(dòng)視圖的復(fù)用回收問題。它可以支持跨View層的復(fù)用,用易用方式來生成一個(gè)高性能的滾動(dòng)視圖。
為什么要用LazyScrollView
我們?cè)谧鍪醉摰臅r(shí)候,往往展示的東西會(huì)很多,隨著View數(shù)量逐漸膨脹,沒有一套復(fù)用回收機(jī)制的ScrollView已經(jīng)影響到性能了,迫切需要處理對(duì)ScrollView中View的復(fù)用和回收。使用TableView只能用來解決同類Cell的展示,然而在實(shí)際的場(chǎng)景中在ScrollView里面,View的種類往往會(huì)比較多,所以使用TableView不適合我們的場(chǎng)景。
而UICollectionView本身的布局和復(fù)用回收機(jī)制不夠靈活,用起來也較為繁瑣。所以誕生了LazyScrollView去解決這個(gè)問題。這也是天貓iOS客戶端的首頁落地方案。
LazyScroll使用
LazyScrollView的使用和TableView很像,不過多了一個(gè)需要實(shí)現(xiàn)的方法:返回對(duì)應(yīng)index的View 相對(duì)LazyScrollView的絕對(duì)坐標(biāo)。
實(shí)現(xiàn)LazyScrollViewDatasource
類似TableView的用法,我們需要使用方實(shí)現(xiàn)LazyScrollViewDatasource的Delegate。
@protocol TMMuiLazyScrollViewDataSource <NSObject> @required //ScrollView展示item個(gè)數(shù) - (NSUInteger)numberOfItemInScrollView:(TMMuiLazyScrollView *)scrollView; //要求根據(jù)index直接返回RectModel - (TMMuiRectModel *)scrollView:(TMMuiLazyScrollView *)scrollView rectModelAtIndex:(NSUInteger)index; //返回下標(biāo)所對(duì)應(yīng)的view - (UIView *)scrollView:(TMMuiLazyScrollView *)scrollView itemByMuiID:(NSString *)muiID;
LazyScrollView的核心是在初始狀態(tài)就得知所有View應(yīng)該顯示的位置。第一個(gè)方法很簡(jiǎn)單,獲取LazyScrollView中item的個(gè)數(shù)。第二個(gè)方法需要按照Index返回TMMuiRectModel ,它會(huì)攜帶對(duì)應(yīng)index的View 相對(duì)LazyScrollView的絕對(duì)坐標(biāo)。
這里出現(xiàn)了一個(gè)TMMuiRectModel ,這是個(gè)什么東西呢?我們看一下代碼:
@interface TMMuiRectModel:NSObject //轉(zhuǎn)換后的絕對(duì)值rect @property (nonatomic,assign) CGRect absRect; //業(yè)務(wù)下標(biāo) @property (nonatomic,copy) NSString *muiID;
這里有兩個(gè)屬性,absRect是LazyScroll中的View相對(duì)LazyScrollView的絕對(duì)坐標(biāo),muiID是這個(gè)View在LazyScrollView中唯一的標(biāo)識(shí)符,可賦值也可不賦值。
第三個(gè)方法,返回View。
@interface UIView(TMMui)
//索引過的標(biāo)識(shí),在LazyScrollView范圍內(nèi)唯一 @property (nonatomic, copy) NSString *muiID; //重用的ID @property (nonatomic, copy) NSString *reuseIdentifier;
首先,我們?cè)赨IView之外加了一個(gè)Category,這個(gè)category可以讓View攜帶muiID和reuseIdentifier,對(duì)于返回的View來說,只需要在乎對(duì)View的reuseIdentifier賦值,muiID的賦值會(huì)在lazyScrollView中處理掉。reuseIdentifier相同的View會(huì)被復(fù)用,如果這個(gè)View的reuseIdentifier是nil或者空字符串,則不會(huì)被復(fù)用。
LazyScrollView內(nèi)部原理分析
首先來看一個(gè)簡(jiǎn)單的案例:
根據(jù)DataSource獲取所有的TMMuiRectModel
根據(jù)DataSource的Delegate,拿到所有的View應(yīng)該被顯示的位置。這一步,核心是拿到的位置是確定的。根據(jù)Demo,我們觀察從 0/1 - 2/3 之間這些View,這個(gè)時(shí)候LazyScrollView拿到的Rect如下:
Index | 標(biāo)號(hào)(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) |
排序
拿到了這些位置之后,接下來做的事情就是排序。排序生成的索引會(huì)有兩個(gè):根據(jù)頂邊(y)升序排序的索引和根據(jù)底邊(y+height)降序排序的索引。
根據(jù)頂邊(y)升序排序的索引
Index | 標(biāo)號(hào)(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) |
根據(jù)底邊(y+height)降序排序的索引
Index | 標(biāo)號(hào)(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) |
查找
前兩步是在執(zhí)行完reload,在視圖還沒有生成的時(shí)候就開始做了,而接下來的步驟在要生成視圖(初始化或滾動(dòng)的時(shí)候)才會(huì)去做。
我們?cè)O(shè)定了Buffer為上下各20,滾動(dòng)超過20個(gè)像素后才會(huì)指定查找視圖并顯示的動(dòng)作。舉個(gè)例子,如下圖,紅圈是應(yīng)該顯示的區(qū)域。
如上圖所示,現(xiàn)在已知的是紅圈頂邊y是242,底邊y是949,加上緩沖區(qū)Buffer,應(yīng)該是找222 - 969 之間的View。我們要做的是,找到底邊y小于969的Model和頂邊y大于222的Model,取交集,就是我們要顯示的View。
采用的方法為二分查找,在根據(jù)頂邊升序排序的索引中找949,找到的index為0(MUIID為2/2),我們使用一個(gè)Set,把根據(jù)頂邊排序中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。
根據(jù)底邊排序的索引中找222,找到的index為2,我們把index >= 2的元素放在另一個(gè)Set,獲取的Set中包含的muiID為0/2,0/3,1/0,1/1,1/2,1/3,2/0,2/1,2/2
兩個(gè)Set取交集,得到的就是我們的ResultSet,這里面都是我們要顯示View的Model,它們的muiID是0/2,0/3,1/0,1/1,1/2,1/3,2/0,2/1,2/2。
回收、復(fù)用、生成
我們知道了應(yīng)該顯示哪些View,但是我們之后做的第一步是把不需要顯示的View加入到復(fù)用池中。LazyScroll可以取到當(dāng)前顯示了的View,拿當(dāng)前顯示的View的muiID和將要顯示view的Model的muiID做對(duì)比,可以知道當(dāng)前顯示的View哪些應(yīng)該被回收。
LazyScrollView中有一個(gè)Dictionary,key是reuseIdentifier,Value是對(duì)應(yīng)reuseIdentifier被回收的View,當(dāng)LazyScrollView得知這個(gè)View不該再出現(xiàn)了,會(huì)把View放在這里,并且把這個(gè)View hidden掉。
然后,用LazyScrollView會(huì)去調(diào)用datasource。
- (UIView *)scrollView:(TMMuiLazyScrollView *)scrollView itemByMuiID:(NSString *)muiID;
復(fù)用還是不復(fù)用,是由datasource決定的。如果要復(fù)用,需要datasource方法內(nèi)調(diào)用,即:
- (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier
獲取復(fù)用的View,這個(gè)方法取出來的View就是在上一段所說的Dictionary中拿的。
最后我們看一下LazyScrollView的使用流程:找到所有View將要顯示的位置 – 排序 – 查找應(yīng)該顯示的View – 回收 – 創(chuàng)建/復(fù)用。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
iOS仿網(wǎng)易簡(jiǎn)單頭部滾動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了iOS仿網(wǎng)易簡(jiǎn)單頭部滾動(dòng)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05使用objc runtime實(shí)現(xiàn)iOS閉環(huán)的懶加載功能
利用objc runtime的動(dòng)態(tài)性實(shí)現(xiàn)懶加載可以實(shí)現(xiàn)即可增加又可刪除功能,也可以避免污染類型。這篇文章主要介紹了使用objc runtime實(shí)現(xiàn)iOS閉環(huán)的懶加載功能,需要的朋友可以參考下2019-06-06iOS實(shí)現(xiàn)相冊(cè)多選圖片上傳功能
這篇文章主要為大家詳細(xì)介紹了iOS實(shí)現(xiàn)相冊(cè)多選圖片上傳功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08ios開發(fā)UITableViewCell圖片加載優(yōu)化詳解
這篇文章主要為大家介紹了ios開發(fā)UITableViewCell圖片加載優(yōu)化的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07iOS Swift控制器轉(zhuǎn)場(chǎng)動(dòng)畫示例代碼
這篇文章主要給大家介紹了關(guān)于iOS Swift控制器轉(zhuǎn)場(chǎng)動(dòng)畫的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)各位iOS開發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-01-01