接着捋~
锚点(anchorPoint)
锚点也需要使用单位坐标,默认是(0.5,0.5),也就是中心点。当把锚点值设置为(0,0)时锚点将位于图层左上角,(1,1)时为右下角,以此类推。
关于锚点的使用书中给了一个时钟的例子,而在开发中我唯一使用到锚点的场景是在制作抽奖的转盘时,为了给奖品的图片做旋转,需要更改图片的锚点来处理(其实和时钟例子的用法几乎一样)。大概就是下面图片中的效果。
至于转盘的实现方式和代码,等把该整理的都整理完,如果还想再写点东西的话再附上一篇抽奖转盘的实现吧= =。
不同坐标系之间的转换方法
就是这几个方法:
- (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;
拿第一个方法为例,意思按我自己的理解大概就是将点(point)从图层(layer)中的值转换到当前图层,并返回转换后的值。第二个方法:将点(point)从他所在的图层转移到指定图层(layer),并返回转以后的值。
或者可以看下面的一个例子。
UIView *outView = [[UIView alloc] initWithFrame:CGRectMake(30, 30, 300, 300)];
outView.backgroundColor = [UIColor whiteColor];
[self.view addSubview:outView];
CALayer *innerLayer = [CALayer layer];
innerLayer.backgroundColor = [UIColor redColor].CGColor;
innerLayer.frame = CGRectMake(0, 0, 50, 50);
innerLayer.position = outView.layer.position;
[outView.layer addSublayer:innerLayer];
首先定义了一个外层的outView,然后在outView.layer上添加了一个新的图层叫innerLayer,并且让innerLayer在outView.layer上居中显示。我们期望这段代码执行后看到的效果应该是这样的。
但是实际上运行起来会是这样的。
看到这样的效果,老司机肯定会暗骂一声“凑,xxx忘写了”。然后萌新就会有点懵“麻辣鸡,这是什么鬼,说好的居中呢”
至于为什么会出现上图中的效果,是因为白色View中position的值是以它的父视图(也就是灰色的view)来计算的,在本例中这个值是(180,210)。也就是说白色view的position的值是在父视图坐标系中计算出来的。那么这个时候直接把这个值赋给红色layer(innerLayer)的position来达到居中的目的肯定是做不到的。如果我没解释清楚的话直接看position属性(或者UIView的center属性,效果是一样的)的官方解释可能会更清晰。
执行“innerLayer.position = outView.layer.position;”这句代码在本例中等同于“innerLayer.position = CGPointMake(180, 210);”。因为innerLayer的布局是依赖它的父图层(也就是outView.layer)坐标系,而在outView的坐标系中,中心点显然不是(180,210)这个点(明显是(150,150)的啊),所以,导致innerLayer跑偏了。
这个问题就是因为坐标系使用混乱导致的。下面我们将innerLayer的代码这么写:
CALayer *innerLayer = [CALayer layer];
innerLayer.backgroundColor = [UIColor redColor].CGColor;
innerLayer.frame = CGRectMake(0, 0, 50, 50);
CGPoint innerCenter = [self.view.layer convertPoint:outView.layer.position toLayer:outView.layer];
innerLayer.position = innerCenter;
[outView.layer addSublayer:innerLayer];
与之前的代码相比,新加入了一句代码“CGPoint innerCenter = [self.view.layer convertPoint:outView.layer.position toLayer:outView.layer];”。这句代码用来把outView.layer.position的值从他的父视图坐标系转换到outView.layer的坐标系中。innerLayer就可以正常显示了。
zPosition
描述了图层在z轴上的位置,可以用来改变图层显示顺序,注意仅仅是显示顺序。
zPosition默认是0,数值越高它的显示优先级也越高,但是不能改变响应优先级
geometryFlipped
geometryFlipped决定了一个图层的坐标是否相对于父图层垂直翻转,默认情况下是NO,也就是从左上角开始绘制,当把值改为YES的时候这个图层和他的子图层将会被垂直翻转,也就是从左下角开始绘制。
hitTest方法
在说hitTest方法之前要先说CALayer的另一个方法:
- (BOOL)containsPoint:(CGPoint)p;
在CALayer属性整理(一)里面开头就提到过CALayer不处理交互,但是它仍提供了一些方法来帮助我们处理交互。上面的方法接受一个在本图层坐标系下的CGPoint,注意是本图层坐标系!如果这个点在该图层的frame范围内返回YES,反之返回NO。
书上有一个这样的例子:首先界面是这样的,一个view上加了个layer就不贴代码了
然后在vc里面重写一下touchesBegan方法
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//获取self.view坐标系中的触摸点
CGPoint point = [[touches anyObject] locationInView:self.view];
//把获取到的点从self.view.layer中转换到self.aView.layer的坐标系中。
point = [self.aView.layer convertPoint:point fromLayer:self.view.layer];
//判断self.aView.layer是否包含这个点
if ([self.aView.layer containsPoint:point]) {
//继续把点从self.aView.layer中转移到self.blueLayer的坐标系
point = [self.blueLayer convertPoint:point fromLayer:self.aView.layer];
if ([self.blueLayer containsPoint:point]) {
NSLog(@"Inside Blue Layer");
} else {
NSLog(@"Inside White Layer");
}
}
}
可以看到要想使用containsPoint方法必须要先转换坐标系,这样做还是有点麻烦的。好在我们还有hitTest方法可以用。hitTest方法同样需要传入一个点,但是他的返回值不再是布尔值。而是一个可以为空的CALayer。
- (nullable CALayer *)hitTest:(CGPoint)p;
当我们传入一个点,这个点在哪一个layer上就会返回这个layer,如果在这个layer的子图层上则会返回子图层,如果这个点在最外面图层的范围之外,则返回nil。使用hitTest方法之后上面的代码可以改成这样:
- (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(@"Inside Blue Layer");
} else if (layer == self.layerView.layer) {
NSLog(@"Inside White Layer");
}
}
代码看起来比之前的好理解多了,而且也变得更简洁。这次就写这么多吧。