本文转载至 http://www.tuicool.com/articles/e2qaYjA

背景

随着达达业务的扩大,越来越多的人开始使用达达客户端,参加到众包物流的行业中。达达客户端分为iOS平台和安卓平台。

APP开发也从快速迭代的粗旷性开发转向高可复用,提升用户提现的精细化方向发展。iOS动画交互良好,使用广泛,良好的用户体验离不开流畅的界面变换。为此,达达iOS团队对动画实现以及背后原理做了学习探索。

目的

  • 了解Core Animation 基本框架
  • 理解图层的定义
  • 解析动画的流程
  • 掌握3D动画的原理
  • 实现3D动画的代码实现

基本框架

Core Animation位于AppKit、UIKit下方,集成于View的Cocoa、Cocoa Touch中。当然Core Animation向view提供很多接口,好让开发者更好的控制动画。

图层的定义

  1. 开发者所有关于Core Animation的操作都是基于图层,图层对象是3D空间向后垂直投影的一个2D平面。
  2. 一个图层抓取由View提供的内容,并把这些内容缓存在一张位图中。
  3. 在硬件层面中对位图的操作远远快于在软件层面。

理解

  1. 垂直投影可以想象成,一个物体站在一面墙前,一个垂直于墙面的光源,照射物体,在墙上留下的阴影,由于是垂直光源,无论物体是远离墙面,还是靠近墙面,阴影的大小都不会发生变化。

  2. iOS程序中每个View控件都有自己的Layer,View上的子控件,例如:Label,Image等,便是View向Layer提供的内容,而平移,旋转,缩放等参数,便是状态信息。

  3. 基于Layer的动画,Core Animation把layer保留的bitmap和状态信息传给图形硬件,图形硬件负责使用新的信息操作bitmap。基于View 绘制,是通过调用view的drawRect方法来使用新的参数重绘内容改变view。这种绘制代价很高,因为绘制在总线程消耗CPU完成工作。

动画的流程

动画的原理

下面内容将会涉及的知识,依次是数学坐标系,线性代数矩阵,物理成像原理,数学相似三角形,数学方程组。不用担心,我们会从基础入手,让理解更加高效。

坐标系

在图层平面坐标系中,使用两种坐标系。

  • 基于点的坐标系
原点位于图层的左上角,向右为x轴的正方向,向下为y轴的正方向,一个点的x、y坐标以点为单位。
  • 单位坐标系
原点位于图层的左上角,向右为x轴的正方向,向下为y轴的正方向,一个点的x、y坐标以相对x轴、y轴的比例为值,取值范围[0,1]。锚点(anchorPoint)使用单位坐标系, 如下图所示position根据锚点而变。

锚点的作用

锚点决定了动画在变化时,z轴的位置。如下图,由于锚点不同,图层绕z轴的旋转效果也一样。

矩阵

  1. 通过矩阵对图层位图进行平移、旋转、缩放变换。
  2. 矩阵相乘只有在第一个矩阵的列数(column)和第二个矩阵的行数(row)相同时才有意义。
  3. 图层的bitmap由点组成,每个点可以对应1×4矩阵,乘以一个4×4变换矩阵,得到一个1×4矩阵,即为变换后的结果。

矩阵乘法

  1. 矩阵C的行数等于矩阵A的行数,C的列数等于B的列数。
  2. 乘积C的第m行第n列的元素等于矩阵A的第m行的元素与矩阵B的第n列对应元素乘积之和。

思考?

1. 点坐标为什么要转换为1×4矩阵
2. 变换矩阵为什么必须是4×4矩阵
3. 如何实现移动,缩放,旋转

齐次矩阵

  1. 齐次坐标就是将一个原本是n维的向量用一个n+1维向量来表示。
  2. 使用1×4矩阵,是相对点的三维坐标进行齐次坐标。
齐次坐标变换 (x, y, z) -> (x × h,  y × h,  z × h,  h) -> (xˊ, yˊ, zˊ, h)

齐次坐标还原 (xˊ, yˊ, zˊ, h) -> (x / h,  y / h,  z / h,  1) -> (x, y, z)

如果不使用1×4齐次矩阵和4×4变换矩阵?

只使用3×3变换矩阵:

              m11, m12, m13
{x, y, z} * { m21, m22, m23 } = {x', y', z'}
m31, m32, m33

xˊ=x × m11 + y × m21 + z × m31 在预先不对变量系数(m11, m21, m31)做其他计算的情况下,只能实现在各个坐标轴的缩放

但是使用使用1×4齐次矩阵和4×4变换矩阵后

xˊ= x × m11 + y × m21 + z × m31 + 1 × m41

m11=2  m21=0 m31=0 m41=8

可同时实现向x轴正方向放大2倍,在沿着x轴正方向平移8个单位

引入齐次坐标的目的主要是合并矩阵运算中的乘法和加法。

基本变换矩阵

  • 矩阵就是利用矩阵内特殊位置的值,在做矩阵乘法时,达到对点坐标进行变换,下面时常用变换矩阵

3D动画效果

iOS中的CALayer的3D本质上并不能算真正的3D,而只是3D在二维平面上的投影,投影平面就是手机屏幕也就是xy轴组成的平面。

如此,只使用基本变换矩阵实现的平移、缩放、旋转,不会有近大远小的透视效果。

那该如何产生近大远小呢?

  • 要达到近大远小目的,需要在系统做垂直投影前,先对图层做一次视点变换。如此垂直投影别是视点观察到的近大远小的物体。

  • Layer的z轴的位置则是通过anchorPoint来指定的,所谓的anchorPoint(锚点)就是在变换中保持不变的点,也就是某个Layer在变换中的原点,xyz三轴相交于此点。下图为锚点常用位置

  • 在原点(0 , 0)沿着Y轴的正方向,得到如图坐标系, 首先在Z轴选择一个视点

  • 添加两个child layer,观察区域便能看到两个child layer顶部的短线,绿色在前,红色在后,且长度相等

  • 通过视点对顶部,作相对X轴的投影,得到视点投影

  • 绿线、红线本来长度相等,通过视点投影后造成了“近大远小”的透视效果

所以只要在iOS垂直投影前,对layer作视点投影变换,就能得到透视效果

实践透视原理

  • 使用上图的坐标系,红点为观察区域一点,对红点做视点投影,得到绿点,同时对红点做z轴的垂直线得到黑点。

  • 使用相似三角形原理,得到如下公式

  • 简化公式后,得到 方程1 ,绿点x轴的值只于视点z轴值有关

  • 对红点做h = 1的齐次坐标(6, 0, 5, 1),通过乘以一个矩阵,得到变换后的绿点的齐次矩阵

  • 变换后的矩阵只与视点z轴值有关,所以只设置m34,对(6, 0, 5, 1 + 5r)还原得到 方程2

  • 结合 方程1 和 方程2 ,最后得到

至此只要修改变换矩阵m34的值为视点z轴值,便能得到相应的视点投影变换矩阵

动画的代码实现

  • 使用达达启动页面来实践以上部分内容。PS:为了查看简介,未对方法封装

  • 属性申明

    @property (weak, nonatomic) IBOutlet UIImageView *logoImg; 	//达达Logo
    @property (weak, nonatomic) IBOutlet UILabel *nameLab; // 达达
    @property (weak, nonatomic) IBOutlet UILabel *desLab; // 可靠配送,在你身边
  • 初始化设置,对两个Label设置透明度为0,缩小到原来的0.5倍

     - (void)viewDidLoad
    {
    [super viewDidLoad]; self.nameLab.alpha = 0.f;
    self.nameLab.layer.transform = CATransform3DMakeScale(0.5f, 0.5f, 1.f);
    self.desLab.alpha = 0.f;
    self.desLab.layer.transform = CATransform3DMakeScale(0.5f, 0.5f, 1.f);
    }
  • 动画设置,对Logo和Label的分开实现动画

     - (void)viewDidAppear:(BOOL)animated
    {
    [self animationDaDaLabel];
    [self animationDaDaLogo];
    }
  • 对Label的动画,使用UIView自带的block方式

    - (void) animationDaDaLabel
    {
    [UIView animateWithDuration:0.5f animations:^{
    // 放大并模糊
    self.nameLab.alpha = 0.5f;
    self.nameLab.layer.transform = CATransform3DMakeScale(1.2f, 1.2f, 1.f);
    self.desLab.alpha = 0.5f;
    self.desLab.layer.transform = CATransform3DMakeScale(1.2f, 1.2f, 1.f); } completion:^(BOOL finished) { [UIView animateWithDuration:0.5f animations:^{
    // 恢复并清晰
    self.nameLab.alpha = 1.f;
    self.nameLab.layer.transform = CATransform3DMakeScale(1.f, 1.f, 1.f);
    self.desLab.alpha = 1.f;
    self.desLab.layer.transform = CATransform3DMakeScale(1.f, 1.f, 1.f);
    }];
    }];
    }
  • 对Logo的动画,使用CABasicAnimation对象

       - (void) animationDaDaLogo
    {
    CATransform3D transform = CATransform3DIdentity;
    transform.m34 = - 1 / 100.0f; // 设置视点在Z轴正方形z=100 // 动画结束时,在Z轴负方向60
    CATransform3D startTransform = CATransform3DTranslate(transform, 0, 0, -60);
    // 动画结束时,绕Y轴逆时针旋转90度
    CATransform3D firstTransform = CATransform3DRotate(startTransform, M_PI_2, 0, 1, 0); // 通过CABasicAnimation修改transform属性
    CABasicAnimation *animation1 = [CABasicAnimation animationWithKeyPath:@"transform"];
    // 向后移动同时绕Y轴逆时针旋转90度
    animation1.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
    animation1.toValue = [NSValue valueWithCATransform3D:firstTransform]; // 虽然只有一个动画,但用Group只为以后好扩展
    CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
    animationGroup.animations = [NSArray arrayWithObjects:animation1, nil];
    animationGroup.duration = 0.5f;
    animationGroup.delegate = self; // 动画回调,在动画结束调用animationDidStop
    animationGroup.removedOnCompletion = NO; // 动画结束时停止,不回复原样 // 对logoImg的图层应用动画
    [self.logoImg.layer addAnimation:animationGroup forKey:@"FristAnimation"];
    }
  • 实际上,只对Logo使用“一半动画”,Logo一边向后移动,一边逆时针绕Z轴旋转90度,在此动画结束后,通过回调补全剩下的“一半动画”。利用这两部分,实现,向后移动同时逆时针旋转,旋转到90度时,向前移动,同时继续逆时针旋转90度

      - (void) animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
    {
    if (flag)
    {
    if (anim == [self.logoImg.layer animationForKey:@"FristAnimation"])
    {
    CATransform3D transform = CATransform3DIdentity;
    transform.m34 = - 1 / 100.0f; // 设置视点在Z轴正方形z=100 // 动画开始时,在Z轴负方向60
    CATransform3D startTransform = CATransform3DTranslate(transform, 0, 0, -60);
    // 动画开始时,绕Y轴顺时针旋转90度
    CATransform3D secondTransform = CATransform3DRotate(startTransform, -M_PI_2, 0, 1, 0); // 通过CABasicAnimation修改transform属性
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"];
    // 向前移动同时绕Y轴逆时针旋转90度
    animation.fromValue = [NSValue valueWithCATransform3D:secondTransform];
    animation.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
    animation.duration = 0.5f; // 对logoImg的图层应用动画
    [self.logoImg.layer addAnimation:animation forKey:@"SecondAnimation"];
    }
    }
    }
  • 最终效果(PS:仅用于讲解)

小结

根据以上内容,总结以下Core Animation相关重点

  • 理解图层意义,图层是动画的核心和载体
  • 理解两种平面坐标系的用途,在做3D视点变换的时,要通过三维坐标系来协助思考
  • 理解矩阵,齐次坐标的使用目的
  • 如果对成像原理不了解,可以搜索相关资料
  • 通过代码进一步实践

申明:本文的图片源于苹果CoreAnimation Programming Guide,如果想进一步了解,推荐学习苹果官方文档

简析iOS动画原理及实现——Core Animation的更多相关文章

  1. iOS动画-从UIView到Core Animation

    首先,介绍一下UIView相关的动画. UIView普通动画: [UIView beginAnimations: context:]; [UIView commitAnimations]; 动画属性设 ...

  2. iOS动画原理

    1. iOS动画原理 本质:动画对象(这里是UIView)的状态,基于时间变化的反应 分类:可以分为显式动画(关键帧动画和逐帧动画)和隐式动画 关键帧和逐帧总结:关键帧动画的实现方式,只需要修改某个属 ...

  3. 解析 iOS 动画原理与实现

    这篇文章不会教大家如何实现一个具体的动画效果,我会从动画的本质出发,来说说 iOS 动画的原理与实现方式. 什么是动画 动画,顾名思义,就是能“动”的画.人的眼睛对图像有短暂的记忆效应,所以当眼睛看到 ...

  4. iOS开发基础知识:Core Animation(核心动画)

    Core Animation,中文翻译为核心动画,它是一组非常强大的动画处理API,使用它能做出非常炫丽的动画效果,而且往往是事半功倍.也就是说,使用少量的代码就可以实现非常强大的功能. Core A ...

  5. [转]简析 IOS 程序图标的设计

    表现形态**** 在有限的空间里表达出相对应的信息,在IOS 程序图标设计中,直观是第一个解决的问题,不应该出现大多繁琐的修饰,当然还要有很好的视觉表现力,使用户可以更容易理解此应用的实际作用,更轻松 ...

  6. java Spring系列之 配置文件的操作 +Bean的生命周期+不同数据类型的注入简析+注入的原理详解+配置文件中不同标签体的使用方式

    Spring系列之 配置文件的操作 写在文章前面: 本文带大家掌握Spring配置文件的基础操作以及带领大家理清依赖注入的概念,本文涉及内容广泛,如果各位读者耐心看完,应该会对自身有一个提升 Spri ...

  7. IOS动画(Core Animation)总结 (参考多方文章)

    一.简介 iOS 动画主要是指Core Animation框架.官方使用文档地址为:Core Animation Guide. Core Animation是IOS和OS X平台上负责图形渲染与动画的 ...

  8. iOS动画效果和实现

    动画效果提供了状态或页面转换时流畅的用户体验,在iOS系统中,咱们不需要自己编写绘制动画的代码,Core Animation提供了丰富的api来实现你需要的动画效果. UIKit只用UIView来展示 ...

  9. 【转】IOS动画的实现,其实很简单

    动画效果提供了状态或页面转换时流畅的用户体验,在iOS系统中,咱们不需要自己编写绘制动画的代码,Core Animation提供了丰富的api来实现你需要的动画效果.UIKit只用UIView来展示动 ...

随机推荐

  1. 实验一 ASP.NET应用环境配置 总结

    通过本次实验我学会了在Windows XP系统中安装IIS服务器,虽然我的电脑是Windows7系统,但是通过虚拟机软件,配置了一台Windows XP系统的虚拟机,在虚拟机内进行各项配置操作.这次实 ...

  2. python进行数据分析----线性回归

    线性回归分析: 方法: import statsmodels.api as sm import pandas as pd from patsy.highlevel import dmatrices - ...

  3. 【转帖】Windows下PostgreSQL安装图解

    Windows下PostgreSQL安装图解     这篇文章主要为大家介绍了如果在Windows下安装PostgreSQL数据库的方法,需要的朋友可以参考下     现在谈起免费数据库,大多数人首先 ...

  4. e614. Setting the Initial Focused Component in a Window

    There is no straightforward way to set the initial focused component in a window. The typical method ...

  5. (转)linux用文件锁实现保证一个程序只能启动一个进程

    #include <stdio.h> #include <unistd.h>#include <fcntl.h>#include <errno.h>in ...

  6. Cisco交换机端口聚合(EtherChannel)

    端口聚合,英文简称EtherChannel(以太通道)是由Cisco研发的,应用于交换机之间的多链路捆绑技术.它的基本原理是: 将两个设备间多条物理链路捆绑在一起组成一条逻辑链路,从而达到带宽倍增的目 ...

  7. 机器学习——利用PCA来简化数据

    降维技术的好处: 1.使得数据集更易使用 2.降低很多算法的计算开销 3.取出噪声 4.使得结果易懂 在已标注和未标注的数据上都有降维技术,降维的方法: 1.主成分分析(Principal Compo ...

  8. UITableView:可展开的 UITableView

    针对 TableView,有些时候需要在点击 cell 时,展开这行 cell,显现出更多的选项或者全部内容等. 比较容易想到的处理方案就是利用 section,在未选择之前,每一行都是一个 sect ...

  9. useradd groupadd passwd usermod userdel chfn id

    chfn   修改用户信息 id 显示当前用户和用户组的id

  10. SSM框架快速整合实例——学生查询

    一.快速准备 SSM 框架即 Spring 框架.SpringMVC 框架.MyBatis 框架,关于这几个框架的基础和入门程序,我前面已经写过几篇文章作为基础和入门介绍了.这里再简单的介绍一下: 1 ...