iOS 开发之动画篇 - Transform和KeyFrame动画

序言

追求美好是人的天性,这是猿们无法避免的。我们总是追求更为酷炫的实现,如果足够仔细,我们不难发现一个好的动画通过步骤分解后本质上不过是一个个简单的动画实现,正是这些基本的动画在经过合理的搭配组合后化腐朽为神奇,令人惊艳。因此,掌握最基本的动画是完成酷炫开发之旅的根本。

作为动画篇的第二篇文章,我在从UIView动画说起简单介绍了关于UIView的几种基本动画,这几种动画的搭配让我们的登录界面富有灵性生动,但是这几种动画总是无法满足我们对于动画的需求。同样的,本文将从一个小demo开始讲解强大的transform动画以及关键帧keyFrame动画。


demo动效图

可以看到两个动画:叶子被风吹落以及左边的文字从summer变化到autumn,这两个动画都是基于强大的transform形变,其中叶子的飘落动画通过关键帧动画实现。demo链接

transform动画

transform是一个非常重要的属性,它在矩阵变换的层面上改变视图的显示效果,完成旋转、形变、平移等等操作。在它被修改的同时,视图的frame也会被真实改变。有两个数据类型用来表示transform,分别是CGAffineTransformCATransform3D。前者作用于UIView,后者为layer层次的变换类型。基于后者可以实现更加强大的功能,但我们需要先掌握CGAffineTransform类型的使用。同时,本文讲解也是这个变换类型。

对于想要了解矩阵变换是如何作用实现的,可以参考这篇博客:CGAffineTransform 放射变换

talk is cheap show you the code

在开始使用transform实现你的动画之前,我先介绍几个常用的函数:

/// 用来连接两个变换效果并返回。返回的t = t1 * t2
CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2)

/// 矩阵初始值。[ 1 0 0 1 0 0 ]
CGAffineTransformIdentity

/// 自定义矩阵变换,需要掌握矩阵变换的知识才知道怎么用。参照上面推荐的原理链接
CGAffineTransformMake(CGFloat a, CGFloat b, CGFloat c, CGFloat d, CGFloat tx, CGFloat ty)

/// 旋转视图。传入参数为 角度 * (M_PI / 180)。等同于 CGAffineTransformRotate(self.transform, angle)
CGAffineTransformMakeRotation(CGFloat angle)
CGAffineTransformRotate(CGAffineTransform t, CGFloat angle)

/// 缩放视图。等同于CGAffineTransformScale(self.transform, sx, sy)
CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)
CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy)

/// 缩放视图。等同于CGAffineTransformTranslate(self.transform, tx, ty)
CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty)
CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty)

我把demo左下角文字的变形过程记录下来。这里推荐mac上面的一款截取动图的程序licecap,非常简单好用。博主用它来分解动画步骤,然后进行重现。


文字变形过程

不难看出在文字的动画中做了两个处理:y轴上的形变缩小、透明度的渐变过程。首先在项目中新增两个UILabel,分别命名为label1、label2.然后在viewDidAppear中加入这么一段代码:

- (void)viewDidAppear: (BOOL)animated {
    label1.transform = CGAffineTransformMakeScale(0, 0);
    label1.alpha = 0;
    [UIView animateWithDuration: 3. animations: ^ {
        label1.transform = CGAffineTransformMakeScale(0, 1);
        label2.transform = CGAffineTransformMakeScale(0, 0.1);
        label1.alpha = 1;
        label2.alpha = 0;
    }];
}

这里解释一下为什么label2为什么在动画中y轴逐渐缩小为0.1而不是0。如果我们设为0的话,那么在动画提交之后,label2会直接保持动画结束的状态(这是出于性能优化自动完成的),因此在使用任何缩小的形变时,你可以将缩小值设置的很小,只要不是0。

运行你的代码,文字的形变过程你已经做出来了,但是demo中的动画不仅仅是形变,还包括位移的过程。很显然,我们可以通过改变center的位置来实现这个效果,但这显然不是我们今天想要的结果,实现新的动画方式来实现更有意义。

动画开始时形变出现的label高度为0,然后逐渐的的变高变为height,而label从头到尾基于顶部的位置不发生改变。因此动画开始前这个label在y轴上的位置是0,在完成显示之后的y轴中心点为height / 2(基于label自身的坐标系而言),那么动画的代码就可以写成这样:

- (void)viewDidAppear: (BOOL)animated {
    ///  初始化动画开始前label的位置
    CGFloat offset = label1.frame.size.height * 0.5;

    label1.transform = CGAffineTransformConcat(
      CGAffineTransformMakeScale(0, 0),
      CGAffineTransformTranslate(0, -offset)
    );
    label1.alpha = 0;
    [UIView animateWithDuration: 3. animations: ^ {
        ///  还原label1的变换状态并形变和偏移label2
        label1.transform = CGAffineTransformIdentifier;
        label1.transform = CGAffineTransformConcat(
          CGAffineTransformMakeScale(0, 0),
          CGAffineTransformTranslate(0, offset)
        );
        label1.alpha = 1;
        label2.alpha = 0;
    }];
}

调整两个label的位置,并且设置其中一个透明显示。然后运行这段代码,你会发现文字转变过程的动画完成了。

keyframe动画

将文章开头的gif图另存为到本地,然后使用预览打开看看,你会发现预览中的gif图变成了很多张的图片。实际上,无论是动画、电影、CG等动态效果,都可以看做是一张张图片接连渲染实现的,而这些图片切换的速度足够快时我们就会当做是动画。在此之前我们所讲述的平移视图在UIView动画提交之后系统会根据动画时长计算出视图移动的所有帧界面,然后逐个渲染。

回到我们demo中的落叶动画来,我总共对叶子的center进行过五次修改,我将落叶平移的线性路径绘制出来并且标注关键的转折点:


1.png

上面这个平移用UIView动画代码要如何实现呢?毫无疑问,我们需要不断的嵌套UIView动画的使用来实现,具体代码如下:

[self moveLeafWithOffset: (CGPoint){ 15, 80 } completion: ^(BOOL finished) {
    [self moveLeafWithOffset: (CGPoint){ 30, 105 } completion: ^(BOOL finished) {
        [self moveLeafWithOffset: (CGPoint){ 40, 110 } completion: ^(BOOL finished) {
            [self moveLeafWithOffset: (CGPoint){ 90, 80 } completion: ^(BOOL finished) {
                [self moveLeafWithOffset: (CGPoint){ 80, 60 } completion: nil duration: 0.6];
            } duration: 1.2];
        } duration: 1.2];
    } duration: 0.6];
} duration: 0.4];

- (void)moveLeafWithOffset: (CGPoint)offset completion: (void(^)(BOOL finished))completion duration: (NSTimeInterval)duration
{
    [UIView animateWithDuration: duration delay: 0 options: UIViewAnimationOptionCurveLinear animations: ^{
        CGPoint center = _leaf.center;
        center.x += offset.x;
        center.y += offset.y;
        _leaf.center = center;
    } completion: completion];
}

看起来还蛮容易的,上面的代码只是移动叶子,在gif图中我们的叶子还有旋转,因此我们还需要加上这么一段代码:

[UIView animateWithDuration: 4 animations: ^{
    _leaf.transform = CGAffineTransformMakeRotation(M_PI);
}];

那么ok,运行这段代码看看,落叶的移动非常的生硬,我们可以明显的看到拐角。其次,这段代码中的duration传入是没有任何意义的(传入一个固定的动画时长无法体现出在落叶飘下这一过程中的层次步骤)

对于这两个问题,UIView也提供了另一种动画方式来帮助我们解决这两个问题 —— keyframe动画:

+ (void)animateKeyframesWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewKeyframeAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion
+ (void)addKeyframeWithRelativeStartTime:(double)frameStartTime relativeDuration:(double)frameDuration animations:(void (^)(void))animations

第一个方法是创建一个关键帧动画,第二个方法用于在动画的代码块中插入关键帧动画信息,两个参数的意义表示如下:

  • frameStartTime  表示关键帧动画开始的时刻在整个动画中的百分比
  • frameDuration  表示这个关键帧动画占用整个动画时长的百分比。

我做了一张图片来表示参数含义:

添加关键帧方法参数说明

对比UIView动画跟关键帧动画,关键帧动画引入了动画占比时长的概念,这让我们能控制每个关键帧动画的占用比例而不是传入一个无意义的动画时长 —— 这让我们的代码更加难以理解。当然,除了动画占比之外,关键帧动画的options参数也让动画变得更加平滑,下面是关键帧特有的配置参数:

UIViewKeyframeAnimationOptionCalculationModeLinear      // 连续运算模式,线性
UIViewKeyframeAnimationOptionCalculationModeDiscrete    // 离散运算模式,只显示关键帧
UIViewKeyframeAnimationOptionCalculationModePaced       // 均匀执行运算模式,线性
UIViewKeyframeAnimationOptionCalculationModeCubic       // 平滑运算模式
UIViewKeyframeAnimationOptionCalculationModeCubicPaced  // 平滑均匀运算模式

在demo中我使用的是UIViewKeyframeAnimationOptionCalculationModeCubic,这个参数使用了贝塞尔曲线让落叶的下落动画变得更加平滑。效果可见最开始的gif动画,你可以修改demo传入的不同参数来查看效果。接下来我们就根据新的方法把上面的UIView动画转换成关键帧动画代码,具体代码如下:

[UIView animateKeyframesWithDuration: 4 delay: 0 options: UIViewKeyframeAnimationOptionCalculationModeLinear animations: ^{
    __block CGPoint center = _leaf.center;
    [UIView addKeyframeWithRelativeStartTime: 0 relativeDuration: 0.1 animations: ^{
        _leaf.center = (CGPoint){ center.x + 15, center.y + 80 };
    }];
    [UIView addKeyframeWithRelativeStartTime: 0.1 relativeDuration: 0.15 animations: ^{
        _leaf.center = (CGPoint){ center.x + 45, center.y + 185 };
    }];
    [UIView addKeyframeWithRelativeStartTime: 0.25 relativeDuration: 0.3 animations: ^{
        _leaf.center = (CGPoint){ center.x + 90, center.y + 295 };
    }];
    [UIView addKeyframeWithRelativeStartTime: 0.55 relativeDuration: 0.3 animations: ^{
        _leaf.center = (CGPoint){ center.x + 180, center.y + 375 };
    }];
    [UIView addKeyframeWithRelativeStartTime: 0.85 relativeDuration: 0.15 animations: ^{
        _leaf.center = (CGPoint){ center.x + 260, center.y + 435 };
    }];
    [UIView addKeyframeWithRelativeStartTime: 0 relativeDuration: 1 animations: ^{
        _leaf.transform = CGAffineTransformMakeRotation(M_PI);
    }];
} completion: nil];

可以看到相比UIView的动画,关键帧动画更加直观的让我们明白每一次平移动画的时间占比,代码也相对的更加简洁。

尾言

本文作为动画篇的第二篇博客,到了这里UIView的所有动画教程已经完成,在之后的文章中将进一步讲解autolayout动画和图层层次的动画。时值新年,祝愿各位????年快乐,心想事成!本文demo地址

转载请注明地址和原文作者

时间: 2024-10-30 03:21:12

iOS 开发之动画篇 - Transform和KeyFrame动画的相关文章

iOS开发CoreAnimation解读之一——初识CoreAnimation核心动画编程

iOS开发CoreAnimation解读之一--初识CoreAnimation核心动画编程 一.引言         众所周知,绚丽动画效果是iOS系统的一大特点,通过UIView层封装的动画,基本已经可以满足我们应用开发的所有需求,但若需要更加自由的控制动画的展示,我们就需要使用CoreAnimation框架中的一些类与方法.这里先附上前几篇与动画相关的博客地址,这一系列,我们抽出其中的CoreAnimation框架来详细解读. UIViewAnimation动画的使用:http://my.o

iOS 开发之动画篇 - 从 UIView 动画说起

毋庸置疑的:在iOS开发中,制作动画效果是最让开发者享受的环节之一.一个设计严谨.精细的动画效果能给用户耳目一新的效果,吸引他们的眼光 -- 这对于app而言是非常重要的. 本文作为动画文集的第一篇,最开始是想做个qq下拉刷新的水滴动画的制作讲解,但这几天研读<iOS Animations by Tutorials>一书,对iOS动画开发有了更为深刻的了解,于是决定动画篇将从UIView动画开始讲起,以后还会有Layer.Transitioning等在内的动画,希望本文能抛砖引玉,带给大家不一

iOS开发系列--触摸事件、手势识别、摇晃事件、耳机线控

概览 iPhone的成功很大一部分得益于它多点触摸的强大功能,乔布斯让人们认识到手机其实是可以不用按键和手写笔直接操作的,这不愧为一项伟大的设计.今天我们就针对iOS的触摸事件(手势操作).运动事件.远程控制事件等展开学习: iOS事件简介 触摸事件 手势识别 运动事件 远程控制事件 iOS事件 在iOS中事件分为三类: 触摸事件:通过触摸.手势进行触发(例如手指点击.缩放) 运动事件:通过加速器进行触发(例如手机晃动) 远程控制事件:通过其他远程设备触发(例如耳机控制按钮) 下图是苹果官方对于

iOS开发实用技巧—项目新特性页面的处理

iOS开发实用技巧篇-项目新特性页面的处理 说明:本文主要说明在项目开发中会涉及到的最最简单的新特性界面(实用UIScrollView展示多张图片的轮播)的处理. 代码示例: 新建一个专门的处理新特性界面的控制器,可以实用代码也可以用xib,在这里实用纯代码方式,创建一个控制器NewfeatureViewController. 头文件代码: 1 // 2 // JMNewfeatureViewController.h 3 // 4 5 #import <UIKit/UIKit.h> 6 7 t

IOS开发UI篇--IOS动画(Core Animation)总结

一.简介 IOS 动画主要是指Core Animation框架.官方使用文档地址为:Core Animation Guide. Core Animation是IOS和OS X平台上负责图形渲染与动画的基础框架.Core Animation可以作用与动画视图或者其他可视元素,为你完成了动画所需的大部分绘帧工作.你只需要配置少量的动画参数(如开始点的位置和结束点的位置)即可使用Core Animation的动画效果.Core Animation将大部分实际的绘图任务交给了图形硬件来处理,图形硬件会加

iOS开发UI篇—核心动画(转场动画和组动画)

一.转场动画简单介绍 CAAnimation的子类,用于做转场动画,能够为层提供移出屏幕和移入屏幕的动画效果.iOS比Mac OS X的转场动画效果少一点 UINavigationController就是通过CATransition实现了将控制器的视图推入屏幕的动画效果 属性解析: type:动画过渡类型 subtype:动画过渡方向 startProgress:动画起点(在整体动画的百分比) endProgress:动画终点(在整体动画的百分比) 二.转场动画代码示例 1.界面搭建 2.实现代

iOS开发UI篇—核心动画(UIView封装动画)

一.UIView动画(首尾) 1.简单说明 UIKit直接将动画集成到UIView类中,当内部的一些属性发生改变时,UIView将为这些改变提供动画支持 执行动画所需要的工作由UIView类自动完成,但仍要在希望执行动画时通知视图,为此需要将改变属性的代码放在[UIViewbeginAnimations:nil context:nil]和[UIView commitAnimations]之间 常见方法解析: + (void)setAnimationDelegate:(id)delegate 设置

iOS开发UI篇—核心动画简介

一.简单介绍 Core Animation,中文翻译为核心动画,它是一组非常强大的动画处理API,使用它能做出非常炫丽的动画效果,而且往往是事半功倍.也就是说,使用少量的代码就可以实现非常强大的功能. Core Animation是跨平台的,可以用在Mac OS X和iOS平台. Core Animation的动画执行过程都是在后台操作的,不会阻塞主线程.不阻塞主线程,可以理解为在执行动画的时候还能点击(按钮). 要注意的是,Core Animation是直接作用在CALayer上的,并非UIV

iOS开发UI篇—核心动画(基础动画)

一.简单介绍 CAPropertyAnimation的子类 属性解析: fromValue:keyPath相应属性的初始值 toValue:keyPath相应属性的结束值 随着动画的进行,在长度为duration的持续时间内,keyPath相应属性的值从fromValue渐渐地变为toValue 如果fillMode=kCAFillModeForwards和removedOnComletion=NO,那么在动画执行完毕后,图层会保持显示动画执行后的状态.但在实质上,图层的属性值还是动画执行前的初