本篇翻譯至 --Understanding UIScrollView衅胀,夾雜了部分自己的理解
坐標(biāo)系統(tǒng)
每個(gè)視圖都定義了自己的坐標(biāo)系〕牛看起來(lái)如下圖掸掏,x軸指向右側(cè),y軸指向下:
請(qǐng)注意阅束,這個(gè)邏輯坐標(biāo)系不關(guān)心視圖的寬度和高度。它沒(méi)有邊界蝇更,在四個(gè)方向上無(wú)限延伸『襞瑁現(xiàn)在讓我們?cè)谶@個(gè)坐標(biāo)系中展示一些項(xiàng)目(又稱(chēng)子視圖)。每個(gè)彩色矩形代表一個(gè)子視圖:
UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(20, 20, 100, 100)];
redView.backgroundColor = [UIColor colorWithRed:0.815 green:0.007
blue:0.105 alpha:1];
UIView *greenView = [[UIView alloc] initWithFrame:CGRectMake(150, 160, 150, 200)];
greenView.backgroundColor = [UIColor colorWithRed:0.494 green:0.827
blue:0.129 alpha:1];
UIView *blueView = [[UIView alloc] initWithFrame:CGRectMake(40, 400, 200, 150)];
blueView.backgroundColor = [UIColor colorWithRed:0.29 green:0.564
blue:0.886 alpha:1];
UIView *yellowView = [[UIView alloc] initWithFrame:CGRectMake(100, 600, 180, 150)];
yellowView.backgroundColor = [UIColor colorWithRed:0.972 green:0.905
blue:0.109 alpha:1];
[mainView addSubview:redView];
[mainView addSubview:greenView];
[mainView addSubview:blueView];
[mainView addSubview:yellowView];
邊界
UIView
的bounds
屬性是在自己的坐標(biāo)系中描述視圖的位置和大小况脆。
一個(gè)視圖可以被認(rèn)為是一個(gè)窗口或視口到由其坐標(biāo)系定義的平面的矩形區(qū)域。view
的bounds
表示這個(gè)矩形的位置和大小格了。
假設(shè)我們視圖的bounds
矩形的寬度和高度為320×480點(diǎn),其原點(diǎn)是默認(rèn)的(0, 0)
弹惦。視圖成為坐標(biāo)系平面的視口悄但,顯示整個(gè)平面的一小部分。界外的一切仍然存在檐嚣,只有隱藏:
實(shí)際上,除非clipsToBounds == YES(默認(rèn)是NO)报咳,否則邊界矩形外的子視圖將保持可見(jiàn)挖藏。盡管如此厢漩,該視圖并沒(méi)有檢測(cè)到界限之外的觸摸。
幀
接下來(lái)炸宵,我們將修改邊界矩形的原點(diǎn):
CGRect bounds = mainView.bounds;
bounds.origin = CGPointMake(0, 100);
mainView.bounds = bounds;
邊界矩形的起源現(xiàn)在在(0, 100)我們的場(chǎng)景如下所示:
看起來(lái)好像這個(gè)view
已經(jīng)下降了100個(gè)點(diǎn),事實(shí)上這個(gè)view
對(duì)于它自己的坐標(biāo)系是正確的捎琐。視圖在屏幕上的實(shí)際位置(或者在其父視圖中)仍然是固定的裹匙,由它決定的frame
并沒(méi)有改變:
frame
在其父視圖的坐標(biāo)系中描述視圖的位置和大小。
由于視圖的位置是固定的(從它自己的角度來(lái)看)籽御,所以把坐標(biāo)系平面看作是我們可以拖動(dòng)的一片透明膠片,將view
看作是我們正在查看的固定窗口技掏。調(diào)整bounds
原點(diǎn)相當(dāng)于移動(dòng)透明薄膜,使其另一部分通過(guò)視圖變得可見(jiàn):
而這正是UIScrollView
的滾動(dòng)。請(qǐng)注意雁比,從用戶(hù)的角度來(lái)看撤嫩,盡管視圖的子視圖在視圖的坐標(biāo)系統(tǒng)(換言之,其幀)方面的位置保持不變序攘,但看起來(lái)好像視圖的子視圖正在移動(dòng)。
我們來(lái)構(gòu)建UIScrollView
滾動(dòng)視圖不需要不斷更新其子視圖的坐標(biāo)以使其滾動(dòng)丈牢。它所要做的就是調(diào)整邊界的起點(diǎn)瞄沙。有了這些知識(shí),實(shí)現(xiàn)一個(gè)非常簡(jiǎn)單的滾動(dòng)視圖是微不足道的距境。我們?cè)O(shè)置了一個(gè)手勢(shì)識(shí)別器來(lái)檢測(cè)用戶(hù)的平移手勢(shì),為了響應(yīng)手勢(shì)师幕,我們bounds通過(guò)拖動(dòng)量來(lái)轉(zhuǎn)換視圖:
// CustomScrollView.h
@import UIKit;
@interface CustomScrollView : UIView
@property (nonatomic) CGSize contentSize;
@end
// CustomScrollView.m
#import "CustomScrollView.h"
@implementation CustomScrollView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self == nil) {
return nil;
}
UIPanGestureRecognizer *gestureRecognizer = [[UIPanGestureRecognizer alloc]
initWithTarget:self action:@selector(handlePanGesture:)];
[self addGestureRecognizer:gestureRecognizer];
return self;
}
- (void)handlePanGesture:(UIPanGestureRecognizer *)gestureRecognizer
{
CGPoint translation = [gestureRecognizer translationInView:self];
CGRect bounds = self.bounds;
// Translate the view's bounds, but do not permit values that would violate contentSize
CGFloat newBoundsOriginX = bounds.origin.x - translation.x;
CGFloat minBoundsOriginX = 0.0;
CGFloat maxBoundsOriginX = self.contentSize.width - bounds.size.width;
bounds.origin.x = fmax(minBoundsOriginX, fmin(newBoundsOriginX, maxBoundsOriginX));
CGFloat newBoundsOriginY = bounds.origin.y - translation.y;
CGFloat minBoundsOriginY = 0.0;
CGFloat maxBoundsOriginY = self.contentSize.height - bounds.size.height;
bounds.origin.y = fmax(minBoundsOriginY, fmin(newBoundsOriginY, maxBoundsOriginY));
self.bounds = bounds;
[gestureRecognizer setTranslation:CGPointZero inView:self];
}
@end
就像真實(shí)一樣UIScrollView霹粥,我們有一個(gè)contentSize必須從外面設(shè)置的屬性來(lái)定義可滾動(dòng)區(qū)域的范圍疼鸟。當(dāng)我們調(diào)整邊界時(shí),我們確保只允許有效值愚臀。
結(jié)果:
結(jié)論
由于UIKit中坐標(biāo)系的嵌套本質(zhì)泽台,它用不到30行的代碼來(lái)重新實(shí)現(xiàn)UIScrollView。當(dāng)然怀酷,還有更多的事情是真實(shí)的UIScrollView。動(dòng)量滾動(dòng)桅锄,彈跳样眠,滾動(dòng)指標(biāo),縮放和委托方法只是我們?cè)谶@里沒(méi)有實(shí)現(xiàn)的一些功能檐束。
代碼現(xiàn)在在GitHub上可用。