注意:我为过渡动画写了两篇文章:
第一篇:[iOS]过渡动画之简单模仿系统,主要分析系统简单的动画实现原理,以及讲解坐标系、绝对坐标系、相对坐标系,坐标系转换等知识,为第二篇储备理论基础。最后实现 Mac 上的文件预览动画。
第二篇:[iOS]过渡动画之高级模仿 airbnb,主要基于第一篇的理论来实现复杂的界面过渡,包括进入和退出动画的串联。最后将这个动画的实现部分与当前界面解耦,并封装为一个普适(其他类似界面也适用)的工具类。


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

如果你没看第一篇,那我建议你去看一下第一篇,因为如果没有第一篇的基础,这篇还是比较难理解的。不如,现在就去吧。
好,准备好了吗?现在开始第二篇。这一篇主要基于第一篇的理论来实现复杂的界面过渡,包括进入和退出动画的串联。最后将这个动画的实现部分与当前界面解耦,并封装为一个普适(其他类似界面也适用)的工具类。

01.这个界面的架构

我们首先来分析一下这个界面的架构。窗口上是一个可以上下滚动的 UITableViewController,每个 UITableViewCell 上有一个可以左右滑动的 UICollectionView,在每一个 UICollectionViewCell 上布局一张封面图片和其他元素。很主流的布局,大致就是这样,对吧?

02.基于第一篇,我们应该怎么划分这个动画的结构?

上面的动画应该怎么分析呢?什么❓我好像听到有同学在说:“太快了❓根本看不清❓” 好,那我就放慢一点,你再仔细瞧瞧。

看清楚没❓还没看清❓什么❓只看到它们在动❓有一种轻拿轻放的感觉❓

那我们再看张图吧。如果我们脑洞大一点,不要管界面结构,我们把界面想象成为一个平面,那么我们可以按照下面这张图来划分一下动画结构。

如你所见,其实动画分为三个部分,UpAnimationPart + CentreAnimationPart + DownAnimationPart,UpAnimationPart 和 DownAnimationPart 的动画可以归为一类,他们只是简单的上移或者下移。重点是中间的 CentreAnimationPart,它和其他部分都有一些区别。

CentreAnimationPart,每张图片都要作为一个单个的个体进行动画,而不能将中间整个模块一起进行动画。为什么呢?因为每张图片最后都要在下个界面的顶部填充满一个控件。这么说太难懂了?意思就是,如果你拿到的是中间区域整体进行动画的话,那么你拿到的将是中间图片区域有遮盖的部分,而右边露出来那片橙色的图片你将拿不到,这个时候当用户点击的刚好又是右边那张露出半边的图片,你将无法实现中间区域的动画。

如果你理解了我所说的,那么你将会理解这两张图片的微妙区别。

上面两张图片,第二张图片的动画结构是正确的。

划分完动画结构以后,你应该有一种庖丁解牛的感觉。你有没有感觉到和第一篇文章所实现的系统动画已经很像了。希望你能从这种分析和训练中总结出问题的核心:最难的部分是我们的理解,而不是实现。

03.注意点

既然思路都有了,那赶紧写代码吧?吁... 等等,你的思路真的有了吗?能说出来是什么吗?

没关系,学东西哪有那么快。这不是安慰,这是事情的真相。

我们先来看两个知识点和一个注意点。

3.1.Block 的循环引用

可能你已经隐隐意识到了,这个动画已经难免会用到 block 了,block 有很难搞的“循环引用”,你可能仍然搞不懂什么是“引用环”,说清楚这个问题可能要再写一篇文章才够,所以我也不打算在这里说清楚这个问题。

所以我给你的建议是,凡是你拿不准是不是会出现循环引用的地方,你都这么写:

__weak typeof(self) weakSelf = self;
self.aBlock = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) return; // 其它代码
...
}

为什么这么写?

  • 解除循环引用的问题。__weak 是弱引用,不会将 self 的引用计数器 +1。_strong 将 weakSelf 引用计数器 +1,以保持对 weakSelf 的持有,但是 strongSelf 是一个局部变量,过完这个代码块,strongSelf 就会自动释放,所以解除了循环引用的可能性。

  • 防止应用奔溃。if (!strongSelf) return; 我们假设一种很常见的情况,当 self 已经释放的时候,这个 block 被调起,然后就去访问一个为 nil 的僵尸对象,比如说将 self 的某个属性插入字典什么的,这个时候往字典里插入空元素,自然会造成应用奔溃,有了这一行代码,就不会再出现类似的情况了。

3.2.循环利用池

我们天天在用的 UITableView 为什么性能这么好,很大一部分原因是得益于循环利用池这个设计思想。

循环利用池的设计思想可以概括为:

  • 当要用到一个对象的时候,先从 ReusePool 中取,如果 ReusePool 中有缓存就把这个缓存取出来,返还给使用者,然后将这个对象从 ReusePool 中移除。如果 ReusePool 中没有,就创建一个新的对象,返还给使用者。
  • 当一个对象已经移出视野(或不需要使用)的时候,就会将它加入到 ReusePool 中等待再次循环。

基于高性能的这个目的,我们应该给我们的做动画的 UIImageView 实例创建一个 ReusePool。

3.3.如何测试自己计算的 frame 是否正确?

计算 frame 和迁移 frame 是一件很纠结的事情,而且不知道自己究竟有没有算对,如果算不对,就会导致动画错乱。但是如果最后调动画的时候才回过来调 frame,就要反复的检查究竟哪个 frame 算错了。这样是很蛋疼的。

所以我给你一个建议。就是你每算完一个 frame,就在这个 frame 上添加一个占位视图看一下对不对。比方说,像这样添加一个红色的 View 到屏幕上检查一下 frame 对不对:

UIView *redView = [[UIView alloc]init];
redView.backgroundColor = [UIColor redColor];
redView.frame = YourFrame;
[self.view.window addSubview:redView];

3.4.怎么找到 UICollectionViewCell 上那个显示封面图片的 UIImageView?

这个需要你在使用的时候给这个 UIImageView 绑定一个 tag,这样我就能拿到这个 UIImageView。

04.具体实现思路?

让我们总结一下上面分析的内容,看能不能从中分析出我们的具体实现思路。

4.1.动画素材

  • 4.1.1、当用户点击的那一刻,我们首先应该把当前窗口(注意,是窗口 Window)进行截图,备用。

  • 4.1.2、我们需要有一个工具,给这个工具传入裁剪的点和裁剪的类型,就能帮我们把一张图片裁成我们想要的样式。比方说,我们只要截图的上半部分,或者下半部分。

  • 4.1.3、我们需要把用户点击的那个 UICollectionViewCell 上面的那张图片的 Frame 迁移到窗口坐标中,然后计算出需要裁剪的点的位置,最后把窗口截屏和这个点传进去进行裁剪,得到我们做动画需要的图片。

  • 4.1.4、现在做动画需要的元素中我们已经有了 UpAnimationPart 和 DownAnimationPart 需要的图片了,现在只差 CentreAnimationPart 需要的可见 Cell 上面的图片了,这个我们通过 UICollectionView 的 visiableCells 可以拿到。这样一来,做动画的素材已经齐备了。

4.2.动画起始位置

动画的起始位置应该是这个动画最容易的部分。

  • 4.2.1、UpAnimationPart 的起始位置应该是点击那个 Cell 的图片的 Y 坐标以上。如果把 upTailorY 指定为点击那个 Cell 的图片的 Y 坐标的话,那么:

    CGRect upAnimationImageViewFrame_start = CGRectMake(0, 0, JPScreenWidth, upTailorY);
  • 4.2.2、同理,如果把 downTailorY 指定为为点击那个 Cell 的图片的底部(Y 坐标加上图片的高度)。那么,DownAnimationPart 的起始位置应该是:

    CGRect downAnimationImageViewFrame_start = CGRectMake(0, downTailorY, JPScreenWidth, JPScreenHeight - downTailorY);
  • 4.2.3、而中间可见 Cell 的图片的起始位置,都可以通过坐标系迁移直接得到。这样以后,各部分的动画起点位置我们也都有了,这样以后我们就可以在窗口上添加 UIImageView 了。

4.3.动画终点位置

第一篇里说的考验数学功底的部分终于来,还是有点小激动。其实也很简单,你看一张图就知道了:

  • 4.3.1、UpAnimationPart 的终点位置很 easy,简单到你可以直接写出来:

    CGRect upAnimationImageViewFrame_end = CGRectMake(0, -upTailorY, JPScreenWidth, upTailorY);
  • 4.3.2、DownAnimationPart 的终点位置是:

    CGRect downAnimationImageViewFrame_end = CGRectMake(0, JPScreenHeight, JPScreenWidth, JPScreenHeight - downTailorY);
  • 4.3.3、CentreAnimationPart 会稍微有点复杂,其实应该分为三种情况的。具体见下图:

    • 4.3.3.1、TapImage 这张被点击的图片,它的终点位置应该很容易确定,就是填充屏幕顶部:
      这个没有异议吧?而其他两类需要参考它的位置来定位。

      CGRect tapAnimationImageViewFrame_end = CGRectMake(0, 0, JPScreenWidth, JPScreenWidth*2.0/3.0);
    • 4.3.3.2、TapImage_Left。请看下面这张图,你肯定明白了,对吧?TapImage 的初始宽度我们知道,左侧图片和 TapImage 的左侧间距我们也可以算出来,屏幕宽度我们也知道,现在利用十字相乘法,我们就可以很快拿到下图红色方框里的值,也就是我们要的终点位置的 X 坐标。

    • 4.3.3.3、TapImage_Right。这个就不用我再赘述了吧?和上面的情况类似。

05.代码实现

我不打算在文章里粘贴代码了,一个,篇幅已经很长了,再粘贴代码,就会让有些“太长不看”的同学感觉压力很大。二来,代码已经放在 GitHub 上了,看代码还是在 Xcode 中更舒服一点。而且我把 Keynote 也一并放上去了。

06.解耦和抽成工具类

如果你按照这个思路去写的话,你会发现所有的代码都会集中在一个方法里,导致这个方法的代码量有三四百行,非常臃肿。而且进入和退出动画居然耦合在一起,要解耦,要抽工具类又感觉无从下手。这个时候就应该要有一种壮士断腕的勇气:“老子一定要把你抽成工具”的决心。有了这个决心,剩下的就是想办法了。

这个动画有很多参数,所以对于哪些是必须的,要有所取舍。也就是要尝试为工具类设计 API。

/*!
* \~chinese
* @prama indexPath 用户选中的那个UICollectionViewCell的 indexPath.
* @prama collectionView 用户选中的那个UICollectionViewCell的 UICollectionView.
* @prama viewController 动画之前窗口上显示的 viewController.
* @prama presentViewController 动画完成之后要在窗口上显示的 viewController.
* @prama afterPresentedBlock 动画完成之后要在 presentViewController 做的事情.
*
* @return JPContainIDBlock 关闭动画的 block.
*/

对于解耦,我的理解就是,首先写代码之前就要有“尽量不要耦合”的意识。如果项目特别赶时间,你可以暂时不用太理会耦合,这些很深的东西可能需要长期的积累和对于项目的全局观,这些可以等周末有空或者项目之间有空档期的时候再去细细琢磨。

还有就是要有坚韧不拔的意志,有些时候给某个类解耦的时间可能比你重新写一遍花的时间,还多。但是,总结一下,多出来的时间我们都在思考什么?是不是这些时间都用在比实现功能更高一个层次的事务上了?

07.关于JPNavigationController

这个动画得以最后呈现,和我之前的一个框架是分不开的。也就说,如果没有我之前的那个框架做基础,那么这个动画的关闭部分就无法实现。

具体体现在对于 pop 手势的拦截。

#pragma mark --------------------------------------------------
#pragma mark JPNavigationControllerDelegate -(BOOL)jp_navigationControllerShouldPushRight{
[self backBtnClick:nil];
return NO;
}

大致可以概述为,当用户开始 pop 的时候,我们当前控制器会收到代理方法的询问,询问是否需要继续 pop 行为。在我们这个动画中迫切需要收到这个询问,但是不需要继续 pop 行为,所以我们 return NO。


如果你想了解这个框架的实现,你可以看下面这三篇文章:
第一篇:[iOS]UINavigationController全屏pop之为每个控制器自定义UINavigationBar。这篇文章主要是讲述如何实现自定义导航栏的,所有的思路和实现都是 JNTian的。
第二篇:[iOS]UINavigationController全屏pop之为每个控制器添加底部联动视图。这篇文章讲述,如何在已有的自定义导航栏基础上添加自定义的“底部联动视图”。所有的思路和实现都是我自己的。
第三篇:[iOS]UINavigationController全屏pop之为控制器添加左滑push。这次将讲述如何实现左滑push到绑定的控制器中,并且带有push动画。
或者访问我的 GitHub 的JPNavigationController


08.GitHub 地址

GitHub 地址在这里。

感谢分享

[iOS]过渡动画之高级模仿 airbnb的更多相关文章

  1. day49 定位布局和过渡动画

    复习 1.盒子在父级水平居中 margin: 0 auto; 2.文本样式操作 color: red; text-align: center; font: 900 30px/200px "S ...

  2. UGUI 过渡动画插件,模仿NGUI的Tween (转载)

    最近在相亲,后来好朋友跟我说他写了一个好插件,于是我就把女朋友甩了,看看他的插件,可以在UGUI制作简单过渡动画. 我看了下是模仿NGUI的Tween, 我在筱程的基础上稍微改到人性化, 简单支持的让 ...

  3. iOS学习笔记-自定义过渡动画

    代码地址如下:http://www.demodashi.com/demo/11678.html 这篇笔记翻译自raywenderlick网站的过渡动画的一篇文章,原文用的swift,由于考虑到swif ...

  4. iOS核心动画高级技巧之图层变换和专用图层(二)

    iOS核心动画高级技巧之CALayer(一) iOS核心动画高级技巧之图层变换和专用图层(二)iOS核心动画高级技巧之核心动画(三)iOS核心动画高级技巧之性能(四)iOS核心动画高级技巧之动画总结( ...

  5. iOS核心动画高级技巧之CALayer(一)

    iOS核心动画高级技巧之CALayer(一) iOS核心动画高级技巧之图层变换和专用图层(二)iOS核心动画高级技巧之核心动画(三)iOS核心动画高级技巧之性能(四)iOS核心动画高级技巧之动画总结( ...

  6. iOS 开发--动画

    在iOS开发中,制作动画效果是最让开发者享受的环节之一.一个设计严谨.精细的动画效果能给用户耳目一新的效果,吸引他们的眼光 —— 这对于app而言是非常重要的.我们总是追求更为酷炫的实现,如果足够仔细 ...

  7. iOS开发——动画篇Swift篇&动画效果的实现

    Swift - 动画效果的实现   在iOS中,实现动画有两种方法.一个是统一的animateWithDuration,另一个是组合出现的beginAnimations和commitAnimation ...

  8. IOS 动画专题 --iOS核心动画

    iOS开发系列--让你的应用“动”起来 --iOS核心动画 概览 通过核心动画创建基础动画.关键帧动画.动画组.转场动画,如何通过UIView的装饰方法对这些动画操作进行简化等.在今天的文章里您可以看 ...

  9. 遇到的一个移动端从下往上过渡的弹框,在Android下过渡动画的优化问题。

    优化之前: /* 分享弹框样式 */ .popUpDiv { width: 100vw; height: 100vh; transition: all 0.5s ease; position: fix ...

随机推荐

  1. DeDeCMS(织梦)变量覆盖0day getshell

    测试方法: @Sebug.net   dis本站提供程序(方法)可能带有攻击性,仅供安全研究与教学之用,风险自负! #!usr/bin/php -w <?php error_reporting( ...

  2. Sort Colors leetcode java

    题目: Given an array with n objects colored red, white or blue, sort them so that objects of the same ...

  3. 【软引用】弱引用 图片的加载与缓存 OOM

    在java.lang.ref包中提供了几个类:SoftReference类.WeakReference类和PhantomReference类,它们分别代表软引用.弱引用和虚引用. ReferenceQ ...

  4. jQuery的deferred对象使用笔记

    一.什么是deferred对象? 开发网站的过程中,我们经常遇到某些耗时很长的javascript操作.其中,既有异步的操作(比如ajax读取服务器数据),也有同步的操作(比如遍历一个大型数组),它们 ...

  5. 又议android中的manifest清单文件

    写过java程序的人,都知道了配置文件时java实现各种各样的框架的一大利器,manifest清单文件对android的作用自然不言而喻,然而他里面究竟定义了些什么,并且他是如何加载到程序中的. 他里 ...

  6. ObservableCollection

    1)可以使绑定控件与基础数据源保持同步2)还可以在您添加.删除.移动.刷新或替换集合中的项目时引发 CollectionChanged 事件3)还可以在您的窗口以外的代码修改基础数据时做出反应4)相互 ...

  7. C语言打印字母金字塔(第一行是A 第二行是ABA ……)

    #include <stdio.h> #include <stdlib.h> int main() { int line;//代表行数 int i; char letter,c ...

  8. C#.NET常见问题(FAQ)-使用SharpDevelop开发 如何在项目中添加类文件

    点击文件-新建-文件,然后再工程内创建文件   或者工程-添加-新建项     更多教学视频和资料下载,欢迎关注以下信息: 我的优酷空间: http://i.youku.com/acetaohai12 ...

  9. ArcEngine真正释放锁文件,彻底移除图层

    ArcMap在加载图层时会自动生成一个lock格式的加锁文件,右击移除图层后,加锁文件也会自动删除.但AE开发中却不能正常删除,移除图层后加锁文件依然存在,这就导致在其他地方无法对该图层进行操作,只有 ...

  10. 谈谈Boost网络编程(2)—— 新系统的设计

    写文章之前.我们一般会想要採用何种方式,是"开门见山",还是"疑问式开头".写代码也有些类似.在编码之前我们须要考虑系统总体方案,这也就是各种设计文档的作用.在 ...