iOS 滾動視圖的複用問題解決方案

LazyScroll是什麼

LazyScrollView 繼承自ScrollView,目標是解決異構(與TableView的同構對比)滾動視圖的複用回收問題。它能夠支持跨View層的複用,用易用方式來生成一個高性能的滾動視圖。web

爲何要用LazyScrollView

咱們在作首頁的時候,每每展現的東西會不少,隨着View數量逐漸膨脹,沒有一套複用回收機制的ScrollView已經影響到性能了,迫切須要處理對ScrollView中View的複用和回收。使用TableView只能用來解決同類Cell的展現,然而在實際的場景中在ScrollView裏面,View的種類每每會比較多,因此使用TableView不適合咱們的場景。
而UICollectionView自己的佈局和複用回收機制不夠靈活,用起來也較爲繁瑣。因此誕生了LazyScrollView去解決這個問題。這也是天貓iOS客戶端的首頁落地方案。ide

LazyScroll使用

LazyScrollView的使用和TableView很像,不過多了一個須要實現的方法:返回對應index的View 相對LazyScrollView的絕對座標。svg

實現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。ui

@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或者空字符串,則不會被複用。atom

LazyScrollView內部原理分析

首先來看一個簡單的案例:
這裏寫圖片描述spa

根據DataSource獲取全部的TMMuiRectModel

根據DataSource的Delegate,拿到全部的View應該被顯示的位置。這一步,核心是拿到的位置是肯定的。根據Demo,咱們觀察從 0/1 - 2/3 之間這些View,這個時候LazyScrollView拿到的Rect以下:.net

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)升序排序的索引3d

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 – 回收 – 建立/複用。

本文同步分享在 博客「xiangzhihong8」(CSDN)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索