起始
做開發也有一段時間了,經歷了第一次完成專案的激動,也經歷了天天呼叫系統的API的枯燥,於是就有了探索底層實現的想法。
關於scrollView的思考
在iOS開發中我們會大量用到scrollView這個控制元件,我們使用的tableView/collectionview/textView都繼承自它。scrollView的頻繁使用讓我對它的底層實現產生了興趣,它到底是如何工作的?如何實現一個scrollView?讀完本篇部落格,相信你一定也可以自己實現一個簡易的scrollView。
我們首先來思考以下幾個問題:
- scrollView繼承自誰,它如何檢測到手指滑動?
- scrollView如何實現滾動?
- scrollView裡的各種屬性是如何實現的?如contentSize/contentOffSet……
通過一步步解決上邊的問題,我們就能實現一個自己的scrollView。
一步一步實現scrollView
1.毫無疑問我們都知道scrollView繼承自UIView,檢測手指滑動應該是在view上放置了一個手勢識別,實現程式碼如下:
1 2 3 4 5 6 7 8 9 |
- (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] init]; [panGesture addTarget:self action:@selector(panGestureAction:)]; [self addGestureRecognizer:panGesture]; } return self; } |
2.要搞清楚第二個問題,首先我們必須理解frame和bounds的概念。
提到它們,大家都知道frame是相對於父檢視座標系來說自己的位置和尺寸,bounds相對於自身座標系來說的位置和尺寸,並且origin一般為(0,0)。但是bounds的origin有什麼用處?改變它會出現什麼效果呢?
當我們嘗試改變bounds的origin時,我們就會發現檢視本身沒有發生變化,但是它的子檢視的位置卻發生了變化,why???其實當我們改變bounds的origin的時候,檢視本身位置沒有變化,但是由於origin的值是基於自身的座標系,所以自身座標系的位置被我們改變了。而子檢視的frame正是基於父檢視的座標系,當我們更改父檢視bounds中origin的時候子檢視的位置就發生了變化,這就是實現scrollView的關鍵點!!!
是不是很好理解?
基於這點我們很容易實現一個簡單的最初級版本的scrollView,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
- (void)panGestureAction:(UIPanGestureRecognizer *)pan { // 記錄每次滑動開始時的初始位置 if (pan.state == UIGestureRecognizerStateBegan) { self.startLocation = self.bounds.origin; NSLog(@"%@", NSStringFromCGPoint(self.startLocation)); } // 相對於初始觸控點的偏移量 if (pan.state == UIGestureRecognizerStateChanged) { CGPoint point = [pan translationInView:self]; NSLog(@"%@", NSStringFromCGPoint(point)); CGFloat newOriginalX = self.startLocation.x - point.x; CGFloat newOriginalY = self.startLocation.y - point.y; CGRect bounds = self.bounds; bounds.origin = CGPointMake(newOriginalX, newOriginalY); self.bounds = bounds; } } |
3.理解了上邊內容的關鍵點,下邊我們將對我們實現的scrollView做一個簡單的優化。
通過contentSize限制scrollView的內部空間,實現程式碼如下
1 2 3 4 5 6 7 8 |
if (newOriginalX maxMoveWidth) { newOriginalX = maxMoveWidth; } } if (newOriginalY maxMoveHeight) { newOriginalY = maxMoveHeight; } } |
通過contentOffset設定scrollView的初始偏移量,相信大家已經懂了如何設定偏移量了吧?沒錯我們只需設定view自身bounds的origin是實現程式碼如下:
1 2 3 4 5 6 |
- (void)setContentOffset:(CGPoint)contentOffset { _contentOffset = contentOffset; CGRect newBounds = self.bounds; newBounds.origin = contentOffset; self.bounds = newBounds; } |
防止scrollView的子檢視超出scrollView
1 |
self.layer.masksToBounds = YES; |
總結
UIScrollView還有很多其它強大的功能,以上我們只是完成了一個特別簡單的scrollView,以後如果有時間我會對它進行完善。當然如果你有興趣,你完全可以對它進行擴充套件,下載地址放在這裡。同時我也會繼續研究UIKit中其它控制元件的底層實現,歡迎您的持續關注!