《图层树和寄宿图 -- iOS Core Animation 系列一》介绍了图层的基础知识和一些属性方法。这篇主要内容是学习下图层在父图层上怎么控制位置和尺寸的。segmentfault
首先看一张例图:布局
对于图上的frame
、bounds
、center
、postion
的概念我就不赘述了。若是有不明白的自行搜索下了解一下。post
frame
表明了图层的外部坐标(也就是在父图层上占据的空间),bounds
是内部坐标({0, 0}
一般是图层的左上角),center
和position
都表明了相对于父图层anchorPoint
所在的位置
视图的frame
、bounds
、center
属性仅仅是存取方法,当操纵视图的frame
时,其实是在改变视图对应的CALayer
的frame
, 不能独立于图层以外改变视图的frame
.学习
若是对图层作了变换,好比旋转缩放等。frame
的值实际指的是图层旋转以后整个轴对齐的矩形区域。此时frame
的宽高可能和bounds
的宽高不一致:测试
默认来讲,anchorPoint
位于图层的中点。这个属性没有被UIView
直接暴露出来。可是图层的anchorPoint
能够被移动。咱们能够把anchorPoint
置于图层frame
的左上角。将会出现下图右侧的状况:动画
注意上图,改变anchorPoint
后position
的值并没变。
和系列一中提到的contentsRect
相似,anchorPoint
用单位坐标来表示(默认状况是{0.5, 0.5}
)。能够经过指定x和y值小于0或者大于1,使它放置在图层范围以外。atom
为了学习这个anchorPoint
属性,下面建立一个闹钟的示例demo。
资源文件我是从原文上截图下来的spa
建立4个UIImageView
并设置好约束(都是居中显示)。指针
咱们用NSTimer来更新闹钟,使用视图的transform属性来旋转钟表。
代码以下:code
@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]; self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(tick) userInfo:nil repeats:YES]; [self tick]; } - (void)tick { //获取对应的hours mins seconds NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; NSUInteger units = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond; NSDateComponents *components = [calendar components:units fromDate:[NSDate date]]; CGFloat hoursAngle = (components.hour / 12.0) * M_PI * 2.0; CGFloat minsAngle = (components.minute / 60.0) * M_PI * 2.0; CGFloat secsAngle = (components.second / 60.0) * M_PI * 2.0; //旋转对应的视图 self.hourHand.transform = CGAffineTransformMakeRotation(hoursAngle); self.minuteHand.transform = CGAffineTransformMakeRotation(minsAngle); self.secondHand.transform = CGAffineTransformMakeRotation(secsAngle); }
运行项目以下图:
除了指针图片的位置,其余的都正常。
可能这时候咱们最早想到的方法,是调整对应图片的位置来解决。可是这样的话,你能够试试,并不能解决问题。不用卖关子了。这时候就是要用到anchorPoint
的时候。处理代码以下:
// 在viewdidload中添加 self.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);
运行完美。
众所周知,一个图层的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;
常规来讲,一个图层的
postion
位于父图层的左上角,但在 Mac OS 中,一般位于左下角。
和UIView
的二维坐标不一样,CALayer
存在于一个三维空间中,它还提供了zPostion
和anchorPointz
属性。zPosition
属性大多数不经常使用,除了三维动画以外,它最实用的功能是能够改变图层的显示顺序。
咱们演示下改变zPosition
会怎么改变视图的显示顺序。
首先我在SB中设置两个视图,以下图:
若是咱们不作任何操做,运行后,两个视图显示的顺序就是咱们如今设置的这样。可是假如咱们对yellowView
设置zPosition
,哪怕很小的值,都会发现显示的顺序反了。
@interface ViewController () @property (weak, nonatomic) IBOutlet UIView *cyanView; @property (weak, nonatomic) IBOutlet UIView *yellowView; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.yellowView.layer.zPosition = 1.f; }
如今的显示效果以下:
虽然说图层基本没有厚度,可是咱们也尽可能不要设置
zPosition = 0.01f
之类的。由于浮点类型的四舍五入可能致使难以察觉的麻烦。
虽然说CALayer
不关心响应链事件,可是它提供了一些方法让咱们处理事件-containsPoint:
和-hitTest:
。
-containsPoint:
接受一个在本图层坐标系下的CGPoint
,若是这个点在图层frame
范围内就返回YES
.咱们可使用这个方法判断是哪一个图层被触摸了。
代码以下:
@interface ViewController () @property (weak, nonatomic) IBOutlet UIView *layerView; @property (nonatomic, strong) CALayer *blueLayer; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.blueLayer = [CALayer layer]; self.blueLayer.frame = CGRectMake(20.f, 20.f, 100.f, 100.f); self.blueLayer.backgroundColor = [UIColor blueColor].CGColor; [self.layerView.layer addSublayer:self.blueLayer]; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // 获取触摸点 CGPoint point = [[touches anyObject] locationInView:self.view]; // 转换触摸点在layerView的图层的位置 point = [self.layerView.layer convertPoint:point fromLayer:self.view.layer]; // 判断是否包含在layerview里面 if ([self.layerView.layer containsPoint:point]) { point = [self.blueLayer convertPoint:point fromLayer:self.layerView.layer]; if ([self.blueLayer containsPoint:point]) { NSLog(@"点击蓝色图层"); } else { NSLog(@"点击了白色图层"); } } }
运行点击能够在控制台看到NSLog
的输出信息。
-hitTest:
方法一样接受一个CGPoint
参数,可是返回的是图层自己,而不是BOOL
类型。这使咱们不用像-containsPoint:
同样每一个子图层去测试点击的坐标。若是这个点是在最外面的图层,则返回nil
。
把上面-containsPoint:
示例的代码下面的部分修改一下便可:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // 获取点击点 CGPoint point = [[touches anyObject] locationInView:self.view]; // 获取这个点所在的图层 CALayer *layer = [self.layerView.layer hitTest:point]; if (layer == self.blueLayer) { NSLog(@"点击蓝色图层"); } else if (layer == self.layerView.layer) { NSLog(@"点击了白色图层"); } }
尝试修改self.layerView
的zPosition
,会有不一样的结果。有兴趣的能够本身测试一下。