我是MikeAsh的Let'sBuild...系列文章的忠实粉丝,他在这些文章中从头开始设计Cocoa控件以解释它们的工作原理。在这里我将做一些类似的事情,用几行代码来实现我自己的滚动视图。但首先,让我们看一下坐标系在UIKit中是如何工作的。如果您只对滚动视图的代码实现感兴趣,请跳过下一节。UIKit坐标系每个View都定义了自己的坐标系。如下图,x轴指向右侧,y轴指向下方:注意这个逻辑坐标系并不关心其中包含的View的宽高。整个坐标系没有边界,向四周无限延伸。我们在坐标系中放置了四个子视图。每个色块代表一个View:添加View的代码实现如下:UIView*redView=[[UIViewalloc]initWithFrame:CGRectMake(20,20,100,100)];redView.backgroundColor=[UIColorcolorWithRed:0.815green:0.007blue:0.105alpha:1];UIView*greenView=[[UIViewalloc]initWithFrame:CGRectMake(150,160,150,200)];greenView.backgroundColor=[UIColorcolorWithRed:0.494green:0.827blue:0.129alpha:1];UIView*blueView=[[UIViewalloc]initWithFrame0,04Make1,04,CGRect)];blueView.backgroundColor=[UIColorcolorWithRed:0.29green:0.564blue:0.886alpha:1];UIView*yellowView=[[UIViewalloc]initWithFrame:CGRectMake(100,600,180,150)];yellowView.backgroundColor=[UIColorcolorWithbluereen.Red:0.972g:0.109alpha:1];[mainViewaddSubview:redView];[mainViewaddSubview:greenView];[mainViewaddSubview:blueView];[mainViewaddSubview:yellowView];boundsApple在UIView上的文档这样描述边界属性:边界矩形...描述了视图在自身坐标系中的位置和大小。一个View可以看作是一个窗口,或者是定义在其坐标系平面上的一个矩形可视区域。视图的边界指示矩形可视区域的位置和大小。假设我们的View宽320像素,高480像素,原点在(0,0)。那么这个View就成了整个坐标系平面的观察口,它只展示了整个平面的一小部分。View边界之外的区域仍然存在,只是隐藏起来了。View提供其平面的查看端口。视图的边界矩形描述了区域的位置和大小。Frame接下来我们尝试修改bounds的原点坐标:CGRectbounds=mainView.bounds;bounds.origin=CGPointMake(0,100);mainView.bounds=bounds;当我们将bounds的原点设置为(0,100)时,整个屏幕看起来是这样的:修改bounds的原点相当于在平面上移动viewport。看起来View向下移动了100个像素,这在View自己的坐标系中是正确的。但是,View在屏幕上的实际位置(更准确的说是在其父View上的位置)并没有改变,因为它是由View的frame属性决定的,它并没有改变:framerectangle...定义View在其父View的坐标系中的位置和大小。由于View的位置是比较固定的,所以你可以把整个坐标平面看成是我们可以上下拖动的透明幕布,把这个View看成是我们观察坐标平面的窗口。调整View的Bounds属性相当于拖拉窗帘,那么在我们的View中可以观察到如下内容:由于View的位置是固定的(从它自己的角度来看),所以把坐标系平面想象成一张透明的薄膜我们可以四处拖动,并将视图作为我们正在查看的固定窗口。调整bounds的原点相当于移动透明膜,使其另一部分通过视图可见:最好将整个坐标系向上拖动,因为视图的框架没有改变,所以它的位置相对于父视图没有改变。实际上,这就是UIScrollView滑动时发生的情况。注意,从一个用户的角度来看,他认为这个View中的子View在移动,但是它们在坐标系中的位置(它们的frame)并没有改变。构建您的UIScrollView滚动视图不需要其子视图的坐标来使它们滚动。***你所要做的就是改变他的bounds属性。知道了这一点,实现一个简单的滚动视图就不难了。我们使用手势识别器来识别用户的拖动操作,并根据用户拖动的偏移量改变边界的原点://CustomScrollView.h@importUIKit;@interfaceCustomScrollView:UIView@property(nonatomic)CGSizecontentSize;@end//CustomScrollView.m#import"CustomScrollView.h"@implementationCustomScrollView-(id)initWithFrame:(CGRect)frame{self=[superinitWithFrame:frame];if(self==nil){returnnil;}UIPanGestureRecognizer*gestureRecognizer=[[UIPanGestureRecognizer]]initWithTarget:selfaction:@selector(handlePanGesture:)];[selfaddGestureRecognizer:gestureRecognizer];returnself;}-(void)handlePanGesture:(UIPanGestureRecognizer*)gestureRecognizer{CGPointtranslation=[gestureRecognizerTranslationInView=GestureRecognizer;b/self]'sbounds,butdonotGFrigpermitvaluesthatwouldOoundsBoundviolateinSize的边界bounds.origin.x-translation.x;CGFloatminBoundsOriginX=0.0;CGFloatmaxBoundsOriginX=self.contentSize.width-bounds.size.width;bounds.origin.x=fmax(BinXOrigin.x=fmax(newBoundsOrigin.x=fmax(,maxBoundsOriginX));CGFloatnewBoundsOriginY=bounds.origin.y-translation.y;CGFloatminBoundsOriginY=0.0;CGFloatmaxBoundsOriginY=self.contentSize.height-bounds.size.height;bounds.origin.y=fmax(minBoundsOriginY,foundmin(newBoundsYriginY,foundmin(newBoundsYriginY));self.bounds=bounds;[gestureRecognizersetTranslation:CGPointZeroinView:self];}@end和真正的UIScrollView一样,我们的类也有一个contentSize属性,必须从外部设置这个值来指定可滚动区域,当当我们改变边界的大小时,我们确保我们设置的值是有效的结果:我们的滚动视图已经可以工作了,但是它缺少动量滚动、弹跳效果和滚动提示。总结感谢UIKit的坐标系特性,我们只需要30行代码就可以重现UIScrollView的本质。当然,真正的UIScrollView要比我们做的复杂的多,弹跳效果,动量滚动,放大,Proxy方法,这些功能这里不做介绍.更新ate5/2,2014:本文的代码位于https://github.com/ole/CustomScrollView。2014年5月8日更新:1.坐标系不会无限延伸。坐标系的范围由CGFloat的长度决定,根据32位和64位系统不同,一般来说这是一个很大的值。2.事实上,除非你设置clipToBounds==YES,否则超出所有子View的部分其实还是可见的。只是View不会再检测超出部分的触摸事件。原文链接:OleBegemann译文:袁鑫译文链接:http://blog.jobbole.com/70143/
