51好读  ›  专栏  ›  NewPan

[iOS]过渡动画之入门模仿系统

NewPan  · 掘金  ·  · 2017-12-26 02:34

正文

注意:我为过渡动画写了两篇文章: 第一篇: [iOS]过渡动画之简单模仿系统 ,主要分析系统简单的动画实现原理,以及讲解坐标系、绝对坐标系、相对坐标系,坐标系转换等知识,为第二篇储备理论基础。最后实现 Mac 上的文件预览动画。

第二篇: [iOS]过渡动画之高级模仿 airbnb ,主要基于第一篇的理论来实现复杂的界面过渡,包括进入和退出动画的串联。最后将这个动画的实现部分与当前界面解耦,并封装为一个普适(其他类似界面也适用)的工具类。

这两篇文章将会带你学到如何实现下图 airbnb 首页类似的过渡动画,同时最重要的,你将学会怎么分析类似的动画,并且知道如何动手实现。 GitHub 地址在这里。

好,准备好了吗?现在开始第一篇。这一篇主要分析系统简单的动画实现原理,以及讲解坐标系、绝对坐标系、相对坐标系,坐标系转换等知识,为第二篇储备理论基础。最后实现 Mac 上的文件预览动画。

01. 系统的过渡动画

我很多时候做一个东西的时候,我会先想一下,我们的老东家苹果有没有做过类似的?如果有,那肯定苹果的更靠谱。看到上面那个 airbnb 动画的时候,我首先想到 Mac 上这个文件预览的动画。

你还能想到 iPhone 上系统自带更多的类似的动画吗?

这个动画应该怎么实现呢?我来描述一下这个过程,你看我说的对不对。

  • 首先你要选中这个文件夹,然后当你按下 space 键的时候,会产生一个用来做动画的元素 Object ,Object 从当前选中文件夹的位置开始运动到屏幕中央(终点位置),边运动边放大。这是打开预览的过程。

  • 当你再次按下 space 键的时候,当前动画元素 Object 会从屏幕中央运动到你选中的那个文件夹的位置,边运动边缩小。这是关闭预览。

有没有从这个描述中 get 到几个关键点呢?

如果尝试把这些关键点和动画过程串起来,是不是就应该是下面这样?动画开始,先创建用来做动画的元素(是新产生,不是拿到文件夹进行动画,因为你也看到,之前那个文件夹它仍然在那里没有动),然后计算起点位置,在把这个元素添加到起点位置,接下来计算终点位置,然后开始做动画。

02.坐标系、绝对坐标系、相对坐标系,坐标系转换

在实现之前,我们先来复习一下初中物理。

  • 这里我们只讨论二维坐标系,因为我们的动画是基于二维坐标系的。
  • 如下图,我们有一台 iPhone,它的坐标原点在左上角,就是白色的坐标系,我们物理里面又叫做绝对坐标系,其他的坐标系都是参考它来定位的。
  • 在我们的 iPhone 屏幕上有一个红色的矩形,它处在(60,100)的位置上(相对于绝对坐标系),它自身也有一个坐标系,让它体内的元素相对它进行定位,它的坐标系叫做相对坐标系(相对于绝对坐标系的坐标系)。
  • 在屏幕中央还有一个绿色的矩形,它相对于红色的矩形定位为(40,60)(相对坐标系的坐标)。

现在我们要计算这个绿色的矩形的绝对坐标,也就是坐标系转换。从下图计算我们可以很快算出这个值为(100, 160)。

03.知道上面这些有什么用?

可能你看到这里会觉得这些都很简单,还用你再说一遍?而且这些好像也没什么用,对吧?

上面说过坐标转换的问题,在实际开发中,我们的视图 View 都是层层嵌套,所以将一个点的 frame 从一个坐标系迁移到另外一个坐标系不可能依赖于我们开发者去手动计算。因为系统需要将视图渲染到屏幕上,所以系统是知道视图关系的。好在系统提供了两个 frame 转换函数。这两个函数都是 UIView 的对象方法。

- (CGRect)convertRect:(CGRect)rect toView:(nullable UIView *)view;
- (CGRect)convertRect:(CGRect)rect fromView:(nullable UIView *)view;
  • 第一个函数,将一个当前 View 坐标系的 frame 转换为另一个 View 的坐标系上。比如说下图 A 中有个 B,如果要将 B 的 frame 迁移到 C 中,就应该这么写:
CGRect targetFrame = [A convertRect:B.frame toView:C];

  • 同样的,如果使用第二个函数来实现将 B 的 frame 迁移到 C 中,那就应该这么写:
CGRect targetFrame = [C convertRect:B.frame fromView:A];
  • 同时需要注意,如果想要把 B 的 frame 迁移到窗口坐标(绝对坐标系,也就是白色的坐标系),那就应该这么写:
CGRect targetFrame =  [A convertRect:B.frame toView:window];
CGRect targetFrame = [window convertRect:B.frame fromView:A];

或者这么写:







请到「今天看啥」查看全文