iOS-Core Animation 核心动画高级编程/3-图层几何学
图层几何学
不熟悉几何学的人就不要来这里了 --柏拉图学院入口的签名
在第二章里面,我们介绍了图层背后的图片,和一些控制图层坐标和旋转的属性。在这一章中,我们将要看一看图层内部是如何根据父图层和兄弟图层来控制位置和尺寸的。另外我们也会涉及如何管理图层的几何结构,以及它是如何被自动调整和自动布局影响的。
布局
视图的frame
,bounds
和center
属性仅仅是存取方法,当操纵视图的frame
,实际上是在改变位于视图下方CALayer
的frame
,不能够独立于图层之外改变视图的frame
。
记住当对图层做变换的时候,比如旋转或者缩放,frame
实际上代表了覆盖在图层旋转之后的整个轴对齐的矩形区域,也就是说frame
的宽高可能和bounds
的宽高不再一致了(图3.2)
锚点
注意在图3.3中,当改变了anchorPoint
,position
属性保持固定的值并没有发生改变,但是frame
却移动了。
那在什么场合需要改变anchorPoint
呢?既然我们可以随意改变图层位置,那改变anchorPoint
不会造成困惑么?为了举例说明,我们来举一个实用的例子,创建一个模拟闹钟的项目。
钟面和钟表由四张图片组成(图3.4),为了简单说明,我们还是用传统的方式来装载和加载图片,使用四个UIImageView
实例(当然你也可以用正常的视图,设置他们图层的contents
图片)。
我们用NSTimer
来更新闹钟,使用视图的transform
属性来旋转钟表(如果你对这个属性不太熟悉,不要着急,我们将会在第5章“变换”当中详细说明),具体代码见清单3.1
图3.5 在Interface Builder中布局闹钟视图
@interface ViewController ()@property (nonatomic, weak) IBOutlet UIImageView *hourHand; @property (nonatomic, weak) IBOutlet UIImageView *minuteHand; @property (nonatomic, weak) IBOutlet UIImageView *secondHand; @property (nonatomic, weak) NSTimer *timer;@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];//start timerself.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(tick) userInfo:nil repeats:YES];//set initial hand positions[self tick]; }- (void)tick {//convert time to hours, minutes and secondsNSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];NSUInteger units = NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;NSDateComponents *components = [calendar components:units fromDate:[NSDate date]];CGFloat hoursAngle = (components.hour / 12.0) * M_PI * 2.0;//calculate hour hand angle //calculate minute hand angleCGFloat minsAngle = (components.minute / 60.0) * M_PI * 2.0;//calculate second hand angleCGFloat secsAngle = (components.second / 60.0) * M_PI * 2.0;//rotate handsself.hourHand.transform = CGAffineTransformMakeRotation(hoursAngle);self.minuteHand.transform = CGAffineTransformMakeRotation(minsAngle);self.secondHand.transform = CGAffineTransformMakeRotation(secsAngle); }@end
运行项目,看起来有点奇怪(图3.6),因为钟表的图片在围绕着中心旋转,这并不是我们期待的一个支点。
图3.6 钟面,和不对齐的钟指针
你也许会认为可以在Interface Builder当中调整指针图片的位置来解决,但其实并不能达到目的,因为如果不放在钟面中间的话,同样不能正确的旋转。
也许在图片末尾添加一个透明空间也是个解决方案,但这样会让图片变大,也会消耗更多的内存,这样并不优雅。
更好的方案是使用anchorPoint
属性,我们来在-viewDidLoad
方法中添加几行代码来给每个钟指针的anchorPoint
做一些平移(清单3.2),图3.7显示了正确的结果。
清单3.2
- (void)viewDidLoad {[super viewDidLoad];// adjust anchor pointsself.secondHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f); self.minuteHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f); self.hourHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);// start timer }
图3.7 钟面,和正确对齐的钟指针
坐标系
和视图一样,图层在图层树当中也是相对于父图层按层级关系放置,一个图层的position
依赖于它父图层的bounds
,如果父图层发生了移动,它的所有子图层也会跟着移动。
这样对于放置图层会更加方便,因为你可以通过移动根图层来将它的子图层作为一个整体来移动,但是有时候你需要知道一个图层的绝对位置,或者是相对于另一个图层的位置,而不是它当前父图层的位置。
CALayer
给不同坐标系之间的图层转换提供了一些工具类方法:
- (CGPoint)convertPoint:(CGPoint)point fromLayer:(CALayer *)layer;
- (CGPoint)convertPoint:(CGPoint)point toLayer:(CALayer *)layer;
- (CGRect)convertRect:(CGRect)rect fromLayer:(CALayer *)layer;
- (CGRect)convertRect:(CGRect)rect toLayer:(CALayer *)layer;
这些方法可以把定义在一个图层坐标系下的点或者矩形转换成另一个图层坐标系下的点或者矩形
翻转的几何结构
Z坐标轴
注意这里并没有更深的属性来描述由宽和高做成的bounds
了,图层是一个完全扁平的对象,你可以把它们想象成类似于一页二维的坚硬的纸片,用胶水粘成一个空洞,就像三维结构的折纸一样。
这里所谓的“相机”实际上是相对于用户是视角,这里和iPhone背后的内置相机没任何关系。
图3.8显示了在Interface Builder内的一对视图,正如你所见,首先出现在视图层级绿色的视图被绘制在红色视图的后面。
@interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *greenView; @property (nonatomic, weak) IBOutlet UIView *redView;@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];//move the green view zPosition nearer to the cameraself.greenView.layer.zPosition = 1.0f; } @end
图3.9 绿色视图被绘制在红色视图的前面
Hit Testing
第一章“图层树”证实了最好使用图层相关视图,而不是创建独立的图层关系。其中一个原因就是要处理额外复杂的触摸事件。
CALayer
并不关心任何响应链事件,所以不能直接处理触摸事件或者手势。但是它有一系列的方法帮你处理事件:-containsPoint:
和-hitTest:
。
@interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *layerView; @property (nonatomic, weak) CALayer *blueLayer;@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];//create sublayerself.blueLayer = [CALayer layer];self.blueLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);self.blueLayer.backgroundColor = [UIColor blueColor].CGColor;//add it to our view[self.layerView.layer addSublayer:self.blueLayer]; }- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {//get touch position relative to main viewCGPoint point = [[touches anyObject] locationInView:self.view];//convert point to the white layer's coordinatespoint = [self.layerView.layer convertPoint:point fromLayer:self.view.layer];//get layer using containsPoint:if ([self.layerView.layer containsPoint:point]) {//convert point to blueLayer’s coordinatespoint = [self.blueLayer convertPoint:point fromLayer:self.layerView.layer];if ([self.blueLayer containsPoint:point]) {[[[UIAlertView alloc] initWithTitle:@"Inside Blue Layer" message:nildelegate:nil cancelButtonTitle:@"OK"otherButtonTitles:nil] show];} else {[[[UIAlertView alloc] initWithTitle:@"Inside White Layer"message:nil delegate:nilcancelButtonTitle:@"OK"otherButtonTitles:nil] show];}} }@end
图3.10 点击图层被正确标识
-hitTest:
方法同样接受一个CGPoint
类型参数,而不是BOOL
类型,它返回图层本身,或者包含这个坐标点的叶子节点图层。这意味着不再需要像使用-containsPoint:
那样,人工地在每个子图层变换或者测试点击的坐标。如果这个点在最外面图层的范围之外,则返回nil。具体使用-hitTest:
方法被点击图层的代码如清单3.5所示。
清单3.5 使用hitTest判断被点击的图层
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {//get touch positionCGPoint point = [[touches anyObject] locationInView:self.view];//get touched layerCALayer *layer = [self.layerView.layer hitTest:point];//get layer using hitTestif (layer == self.blueLayer) {[[[UIAlertView alloc] initWithTitle:@"Inside Blue Layer"message:nildelegate:nilcancelButtonTitle:@"OK"otherButtonTitles:nil] show];} else if (layer == self.layerView.layer) {[[[UIAlertView alloc] initWithTitle:@"Inside White Layer"message:nildelegate:nilcancelButtonTitle:@"OK"otherButtonTitles:nil] show];} }
注意当调用图层的-hitTest:
方法时,测算的顺序严格依赖于图层树当中的图层顺序(和UIView处理事件类似)。之前提到的zPosition
属性可以明显改变屏幕上图层的顺序,但不能改变事件传递的顺序。
这意味着如果改变了图层的z轴顺序,你会发现将不能够检测到最前方的视图点击事件,这是因为被另一个图层遮盖住了,虽然它的zPosition
值较小,但是在图层树中的顺序靠前。我们将在第五章详细讨论这个问题。
自动布局
你可能用过UIViewAutoresizingMask
类型的一些常量,应用于当父视图改变尺寸的时候,相应UIView
的frame
也跟着更新的场景(通常用于横竖屏切换)。
在iOS6中,苹果介绍了自动排版机制,它和自动调整不同,并且更加复杂。
- (void)layoutSublayersOfLayer:(CALayer *)layer;
这也是为什么最好使用视图而不是单独的图层来构建应用程序的另一个重要原因之一。
总结
iOS-Core Animation 核心动画高级编程/3-图层几何学相关推荐
- iOS - Core Animation 核心动画
1.UIView 动画 具体讲解见 iOS - UIView 动画 2.UIImageView 动画 具体讲解见 iOS - UIImageView 动画 3.CADisplayLink 定时器 具体 ...
- iOS-Core Animation 核心动画高级编程/5-变换
变换 很不幸,没人能告诉你母体是什么,你只能自己体会 -- 骇客帝国 在第四章"可视效果"中,我们研究了一些增强图层和它的内容显示效果的一些技术,在这一章中,我们将要研究可以用来对 ...
- iOS - Core Animation 核心动画的使用
1.简单使用示例 1.1 时钟 QClockView.h @interface QClockView : UIView/// 创建时钟界面+ (instancetype)q_clockViewWith ...
- iOS开发 - Core Animation 核心动画
Core Animation Core Animation,中文翻译为核心动画,它是一组非常强大的动画处理API,使用它能做出非常炫丽的动画效果,而且往往是事半功倍.也就是说,使用少量的代码就可以实现 ...
- Core Animation(核心动画)
iOS开发UI篇-核心动画简介 一.简单介绍 Core Animation,中文翻译为核心动画,它是一组非常强大的动画处理API,使用它能做出非常炫丽的动画效果,而且往往是事半功倍.也就是说,使用少量 ...
- 五 iOS之 Core Animation(核心动画)
核心动画继承结构 开发步骤 1.首先得有CALayer 2.初始化一个CAAnimation对象,并设置一些动画相关属性 3.通过调用CALayer的addAnimation:forKey:方法,增加 ...
- iOS核心动画高级技术(九) 图层时间
The biggest difference between time and space is that you can't reuse time. 时间和空间最大的区别在于,时间不能被复用 -- ...
- Core Animation核心动画的使用
什么是核心动画 核心动画就是CoreAnimation直译过来的中文,它是一组非常强大的动画处理API,只需要使用少量代码就能实现炫酷的动画效果. 核心动画的好处 核心动画可以跨平台使用,Mac OS ...
- iOS Core Animation学习总结(2)--实现自定义图层
一. 创建图层继承于CALayer,并在子类实现drawInContext方法 @interface CTLayer : CALayer@end@implementation CTLayer -(vo ...
最新文章
- 干货|pytorch必须掌握的的4种学习率衰减策略
- C#利用ICSharpCode.SharpZipLib.dll压缩文件和解压文件
- ubuntu 安装OpenBLAS
- c语言的内存管理方式,c语言内存管理
- Ubuntu 8.10今起正式退休
- 在Windows系统中安装WAMP
- 用golang完成tcp协议传输
- spring boot 报错:Your ApplicationContext is unlikely to start due to a @ComponentScan of the default p
- Atitit 图像处理 深刻理解梯度原理计算.v1 qc8
- ​领域模型vs数据模型,应该怎么用?
- 贪心算法及几个常用的例题
- mac ubuntu双系统EFI分区修复,内置磁盘分区修复
- linux命令gw,Linux 基础命令
- 通过IP地址连接两台电脑
- kpi绩效考核流程图_KPI及绩效考核流程
- Jenkins-cents7.6 rpm安装
- 适合中小企业的项目管理系统有哪些?
- VUE+VSCODE(新建一个项目)
- MongoDB 杂事
- 瞧一瞧~看一看~MyCat架构剖析免费不要钱!(下)
热门文章
- java BufferedImage 转base64
- lisp pl线线段数_编写lisp程序多条多段线连接成一条多段线
- CAD软件如何合并区间
- js下载Word文档
- 分机计算机怎么设置共享打印机,win7打印机共享设置分机怎么设置
- 将 s1 和 r1 上的启动配置文件上传到服务器进行备份,packettracer综合技能练习261...
- node.js集成sendgrid邮件发送及其它功能
- matlab 定时器timercallback,matlab定时器timer的用法,特别要注意回调函数的参数!...
- 即使挨骂也要说:刚毕业,就别去初创企业了
- Linux系统下操作Oracle数据库