iOS动画-CALayer布局属性详解

本篇主要内容:
1.Frame与Bounds的区别
2.中心点(position)与锚点(anchorPoint)
3.视图与图层的坐标系

一、Frame与Bounds的区别

我们已经知道UIView的很多布局属性其实都来自于图层;UIView的布局属性包括:frame、bouns、center,分别对应了CALayer中frame、bounds、position。为了能清楚区分,图层用了position,视图用了center,但它们都代表了同样的值。

UIView属性CALayer属性属性说明
frameframe表示相对于其父视图的坐标位置
boundsbounds表示相对于其自身的坐标位置,{0,0}通常是其左上角
centerposition相对于父图层锚点AnchorPoint所在位置

frame&&bounds.png

上图对原有视图做了旋转变换,之后的frame实际上代表了覆盖在图层旋转之后的整个轴对齐的矩形区域,此时frame的宽和高和bounds不再一致了。

其实,对于视图和图层来说,frame是根据bounds、position、和transform计算而来的;所以当其中的任何一个值发生变化时,frame就会发生变化,相反改变frame也同样影响他们当中的值。

六、中心点(position)与锚点(anchorPoint)

1.锚点的概念

position与anchorPoint是两个容易混淆的概念,我们首先从Xcode中找到关于它们的注释说明如下:

/* The position in the superlayer that the anchor point of the layer's
 * bounds rect is aligned to. Defaults to the zero point. Animatable. */
@property CGPoint position;

/* Defines the anchor point of the layer's bounds rect, as a point in
 * normalized layer coordinates - '(0, 0)' is the bottom left corner of
 * the bounds rect, '(1, 1)' is the top right corner. Defaults to
 * '(0.5, 0.5)', i.e. the center of the bounds rect. Animatable. */
@property CGPoint anchorPoint;

我们可以看出,position被用于描述当前layer在superlayer中的位置,而且是通过当前layer的anchorPoint来确定的。换句话来讲就是:position是当前layer的anchorPoint在superLayer中的位置

我们也可以更确切理解为:position是相对于superLayer来讲,而anchorPoint是相对于当前layer来讲;只不过在默认情况下,anchorPoint与position是重合的;锚点是用单位坐标来描述的(即图层的相对坐标),图层的左上角是{0,0},右下角是{1,1},因此图层的默认锚点是{0.5, 0.5},表示图层的中间位置代表了其位置position。

下面的图示是将锚点从{0.5,0.5}改为了{0,0},我们在这里更容易看到position与anchorPoint之间的关系:

anchorPoint.png

如图,修改图层锚点会改变layer的frame,但是其position不会改变,这看起来似乎有点奇怪,但是我们依然可以通过一些计算方式看出端倪:

position.x = frame.origin.x + 0.5 * bounds.size.width;  
position.y = frame.origin.y + 0.5 * bounds.size.height; 

这里的0.5参数,其实就是由于锚点默认值得到的,所以改进公式如下:

position.x = frame.origin.x + anchorPoint.x * bounds.size.width;  
position.y = frame.origin.y + anchorPoint.y * bounds.size.height;

此时,我们如果通过修改anchorPoint的值来进行测试,就会发现改变的只有frame的origin,这就说明修改position与anchorPoint中任何一个属性都不能影响另一个属性,由此我们也可以再次改进公式:

frame.origin.x = position.x - anchorPoint.x * bounds.size.width;  
frame.origin.y = position.y - anchorPoint.y * bounds.size.height;

最后得出结论:frame的origin坐标由position与anchorPoint来共同决定;

2.锚点的作用

锚点就相当于一个支点,可以形象的理解为一颗固定了图层的图钉,尤其是我们在做旋转动画时,可能会需要设置此属性来决定图层是围绕哪一个点旋转的;但这时候我们又不得不考虑一个问题:修改锚点可以让我们的动画围绕非中心点旋转,但是这也改变了原有视图的位置frame,这是我们不想要的结果,该如何解决呢?这里提供一种方法如下:

- (void)setAnchorPoint:(CGPoint)anchorPoint forView:(UIView *)view{
    CGPoint oldOrigin = view.frame.origin;
    view.layer.anchorPoint = anchorPoint;
    CGPoint newOrigin = view.frame.origin;
    
    CGPoint transition;
    transition.x = newOrigin.x - oldOrigin.x;
    transition.y = newOrigin.y - oldOrigin.y;
    
    view.center = CGPointMake (view.center.x - transition.x, view.center.y - transition.y);
}

下面再来具体演示一下修改锚点改变动画状态的用法,我们分别创建橙色视图默认围绕中心旋转,而紫色视图围绕左顶点旋转,关键代码如下:

#import "TestLayerFiveVC.h"
@interface TestLayerFiveVC ()

@property (nonatomic,strong) UIView *viewA;
@property (nonatomic,strong) UIView *viewB;

@end

@implementation TestLayerFiveVC

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.view addSubview:self.orangeView];
    [self.view addSubview:self.purpleView];
    
    [self.orangeView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.view).offset(100);
        make.centerX.equalTo(self.view);
        make.width.height.mas_equalTo(100);
    }];
    
    [self.purpleView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.orangeView.mas_bottom).offset(150);
        make.centerX.equalTo(self.view);
        make.width.height.mas_equalTo(100);
    }];
    [self.view layoutIfNeeded];
    
    //orangeView的旋转动画
    [self addRotationAnimation:self.orangeView withDuration:3];

    //修改purpleView的锚点,并恢复其原先的Frame,使其可以绕着左上角顶点旋转
    [self resetAnchorPoint:CGPointMake(0, 0) forView:self.purpleView];
    [self addRotationAnimation:self.purpleView withDuration:3];
}


- (void)resetAnchorPoint:(CGPoint)anchorPoint forView:(UIView *)view{
    CGPoint oldOrigin = view.frame.origin;
    view.layer.anchorPoint = anchorPoint;
    CGPoint newOrigin = view.frame.origin;
    
    CGPoint transition;
    transition.x = newOrigin.x - oldOrigin.x;
    transition.y = newOrigin.y - oldOrigin.y;
    
    //重新设置原来视图位置
    view.center = CGPointMake (view.center.x - transition.x, view.center.y - transition.y);
    [view mas_remakeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(view.superview).offset(view.center.y - transition.y);
        make.leading.equalTo(view.superview).offset(view.center.x - transition.x);
        make.width.mas_equalTo(view.width);
        make.height.mas_equalTo(view.height);
    }];
}

- (void)addRotationAnimation:(__kindof UIView *)view withDuration:(CFTimeInterval)dutation {
    CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    rotationAnimation.toValue = [NSNumber numberWithFloat: -M_PI * 2.0];
    rotationAnimation.duration = dutation;
    rotationAnimation.cumulative = YES;
    rotationAnimation.repeatCount = MAXFLOAT;
    rotationAnimation.removedOnCompletion = NO;
    [view.layer addAnimation:rotationAnimation forKey:@"rotationAnimation"];
}

效果图如下:

 

锚点动画.gif

三、视图与图层的坐标系

CALayer给不同坐标系之间的图层转换提供了一些工具类方法:

- (CGPoint)convertPoint:(CGPoint)p fromLayer:(nullable CALayer *)l;
- (CGPoint)convertPoint:(CGPoint)p toLayer:(nullable CALayer *)l;
- (CGRect)convertRect:(CGRect)r fromLayer:(nullable CALayer *)l;
- (CGRect)convertRect:(CGRect)r toLayer:(nullable CALayer *)l;

与此对应的UIView也具有相似的方法如下:

- (CGPoint)convertPoint:(CGPoint)point toView:(nullable UIView *)view;
- (CGPoint)convertPoint:(CGPoint)point fromView:(nullable UIView *)view;
- (CGRect)convertRect:(CGRect)rect toView:(nullable UIView *)view;
- (CGRect)convertRect:(CGRect)rect fromView:(nullable UIView *)view;

通过这些方法,我们可以把定义在一个图层(或视图)坐标系下的点或者矩形转换为另一个图层(或视图)坐标系下的点或者矩形;开发过程中我们通常操作的对象都是视图,所以下面以视图为例简单演示其用法:首先创建添加两个宽高都是100*100的橙色、紫色视图在控制器的View上,

坐标系.png

使用下面的代码进行测试,结果如下:

CGPoint targetPoint = CGPointMake(10, 10);

CGPoint point1 = [purpleView convertPoint:targetPoint toView:orangeView]; //代码1
CGPoint point2 = [orangeView convertPoint:targetPoint fromView:purpleView];
NSLog(@"\npoint1:%@\npoint2:%@",NSStringFromCGPoint(point1),NSStringFromCGPoint(point2));

/*测试结果:
 point1:{160, 60}
 point2:{160, 60}
 */

代码分析:
这里分别测试了convertPoint的两种用法(convertRect与其相似),我们可以将代码1理解为:参考organView为坐标系时,purpleView上坐标为target的点的坐标值;

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值