iOS动画-CALayer布局属性详解

作者:神秘网友 发布时间:2020-10-24 00:44:11

iOS动画-CALayer布局属性详解

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所在位置

iOS动画-CALayer布局属性详解

frame&&bounds.png

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

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

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之间的关系:

iOS动画-CALayer布局属性详解

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"];
}

效果图如下:

 

iOS动画-CALayer布局属性详解

锚点动画.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上,

iOS动画-CALayer布局属性详解

坐标系.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的点的坐标值;

 

iOS动画-CALayer布局属性详解相关教程

  1. iOS动画-CALayer寄宿图与绘制原理

    iOS动画-CALayer寄宿图与绘制原理 核心动画Core Animation,其实是由Layer Kit这样一个名字演变而来。它实际上是一个复合引擎,可以将存储在图层树体系中的不同独立图层,尽可能快地组合成不同的可视内容呈现于屏幕上;所以做动画只是Core Animation的特性之

  2. iOS动画-CALayer隐式动画原理与特性

    iOS动画-CALayer隐式动画原理与特性 Core Animation的一个非常显著的特性是就是实现动画,而且它支持隐式动画和显式动画两种形式,本篇我们主要从隐式动画说起; 本篇主要内容: 1.何为隐式动画 2.隐式动画原理-事务与图层行为 3.隐式动画的关闭与显示 4.隐式

  3. iOS 券商京东SDK接入附demo

    iOS 券商京东SDK接入附demo 最近对接京东券商SDK,总结一下。 1、注册京东APP的时候需要的是上架后的链接,如果链接不对是申请不成功的,只要审核通过的APP链接,提交申请就成功了。 2、下载对应京东SDK,复制APPkey和AppSecret,把SDK拉到项目中,注意是拉到

  4. iOS动画-CAAnimation使用详解

    iOS动画-CAAnimation使用详解 理解了隐式动画后,显式动画就更加通俗易懂了。区别于隐式动画的特点, 显式动画就是需要我们明确指定类型、时间等参数来实现效果的动画 。除此之外,我们也可以创建非线性动画,比如沿着任意一条曲线运动等; 我们平时最常用的

  5. Android干货:自定义带动画的View

    Android干货:自定义带动画的View 对于一个自定义View来说,onMeasure只是用来计算View尺寸,onDraw()才是真正执行View的绘制,所以一般我们都需要重写onDraw()函数来绘制我们期望的UI界面,下 面我以一个具体的例子探索自定义View的onDraw()的实现过程和关键

  6. JetPack知识点实战系列十一:MotionLayout让动画如此简单

    JetPack知识点实战系列十一:MotionLayout让动画如此简单 MotionLayout 是 ConstraintLayout 的子类,所以它是一种布局类型,但是它能够为布局属性添加动画效果,是开发者实现动画效果的另一个新的选择。 让动画跑起来 在入门练习的例子中,我们先利用 Motion

  7. iOS完整推流采集音视频数据编码同步合成流

    iOS完整推流采集音视频数据编码同步合成流 需求 众所周知,原始的音视频数据无法直接在网络上传输,推流需要编码后的音视频数据以合成的视频流,如flv, mov, asf流等,根据接收方需要的格式进行合成并传输,这里以合成asf流为例,讲述一个完整推流过程:即音视频从采

  8. 微信小程序点击按钮聚合动画

    微信小程序点击按钮聚合动画 微信小程序点击按钮聚合动画 效果如下 wxml view class=allimgs image src=qst_img/999_1.png class=img-style animation={{animCollect}} bindtap=collect/image image src=qst_img/999.png class=img-style animation={{animTra