iOS动画-layout动画初体验

前言

约束动画的文章要比预计的迟迟来临,最大的原因是没有找到我认为的足够好的动画来讲解约束动画 —— 当然了,这并不是因为约束动画太难。相反,因为约束动画实在太简单了,反而没有足够多的简单动画素材让我选用。下面这个动画取自于朋友公司的app,我仿做了一个,作为今天的demo,具体效果如下:


约束动画

关于约束

在这一小节我会简单的介绍一下约束的用法,如果您已经在使用storyboard进行开发了,那么可以跳过这一节。
假设现在有这么一个需求,你需要将文章显示在界面的中间位置,大致是左右空30pt、上方间隔20pt,效果图如下:


需求界面

你满心不在意的想这个任务多简单啊,于是挥洒的写下了这么一段代码:

self.textView = [[UITextView alloc] initWithFrame: CGRectMake(30, 20, self.view.bounds.size.width - 60, 0)];
self.textView.text = ..........
[self.textView sizeToFit];

然后运行你的代码,ok,效果不错。但请等等,按住command + 左右方向键,你的界面变成了这样:

错误的界面

你很快就意识到问题所在。好的,你接收了屏幕转向的通知UIApplicationWillChangeStatusBarOrientationNotification,然后在回调里面重新修改了文本的宽度:

- (void)notifyToAdjustTextView: (NSNotification *)notification
{
    switch ([UIDevice currentDevice].orientation) {
        case UIDeviceOrientationLandscapeLeft:
        case UIDeviceOrientationLandscapeRight:
            CGRect frame = self.textView.frame;
            frame.size.width = self.view.bounds.size.height - 60;
            self.textView.frame = frame;
            [self.textView sizeToFit];
        //more
        ......
    }
}

这次再次运行你的代码,不管你怎么转向,文本都稳稳的显示在中心,你成功了!但是这样工作有些太繁杂了 —— 你做了这么多的工作只是为了解决一个布局问题。因此,这就提及到了我们今天的主题:约束

在你新建的项目中总会存在一个Main.storyboard,让我们打开这个文件,在xcode为我们创建好的控制器里面直接添加文本:

故事板使用

点击选中textView,xcode为我们提供了下方的几个按钮来给我们的控件添加约束。我在这里给控件添加了距离上方20pt、左右距离边缘30pt的约束,完成后点击Add 3 Constraints约束就算添加完成了:
添加约束

现在运行你的项目,尝试屏幕转向,无论如何文本总是距离上方20pt,左右距离30pt。使用约束让我们的工作更加的简单不是吗?

所有的动画都是将一连串的计算展示开来的过程,约束动画也不例外,那么约束是如何计算的呢?以上面的基础约束demo为例,在我们为文本控件添加了约束之后,故事板上的textView多了一些直线,每一条直线代表一个约束。双击这个约束我们可以设置更多东西。


约束的相关值

以这个约束为例,所有的约束计算都遵循这样的计算:
TextView.Left = 1 * superView.Left + 30

父视图的left坐标点位置总是基于0pt的位置进行计算的,因此与父视图的约束计算总是以constant的值为结果。因此修改constant的大小并重新绘制界面可以产生动画。

关于autolayout使用的更多教学可以看这篇文章

动画分析

在开工之前分析代码的实现过程可以让我们对动画的实现有更清晰的了解。可以看到,动画建立在tableView的滚动条件上,那么监听tableView的滚动偏移是动画实现的核心部分。(ps:为什么是kvo而不是直接代理回调呢?当我们无法保证工程中tableView的代理和动画实现方是否为同一对象时,使用kvo是最稳妥的选择

根据用户滑动的时候,我们可以总结出下面这几个动画效果:

  • 导航栏标题淡出颜色渐变
  • 导航栏标题上移
  • 红色的背景层上移并隐藏
  • 倒计时信息和文本信息上移显示在导航栏中

综合这些信息我写了一些需要用到的宏定义:

#define OBSERVERKEY @"contentOffset"            ///<    监听属性
#define MAXMOVE 64.0                            ///<    滚动最大值
#define TITLEMAXMOVE 16.0                       ///<    导航文本位移最大值
#define TOPANDBOTTOMCONSTANT 9                  ///<    红色背景上下间距约束值

本文的动画可以归类为视觉差动画,通过利用不同视图的层次在视图移位形成视觉动画效果,因此我们可以先将动画中发生了视觉位移的视图的层次写出来,这样会帮助我们更好的实现动画。

在动画过程中,红色的背景层上移之后就不再显示,因此红色的背景图层肯定位于导航栏的蓝色视图下方。而倒计时和文本信息上移之后依旧显示,可以肯定这些视图处在导航栏视图上面。因此可以得出这么一个层次:文本信息和倒计时控件 -> 导航栏 -> 红色背景。另一方面如果在动画中同时移动这些文本信息和倒计时控件,代码量肯定很多,所以我们可以把这些控件全部当做一个透明的UIView的子视图,然后简单的移动这个UIView就可以了。下面我放上一张官方文档中苹果对于导航栏控制器的视图层次图:

导航栏控制器层次图

结合上面的结论,我们可以知道:

  • 透明视图处在Custom view hierarchyNavigation View中间
  • 红色背景处在Navigation viewUIWindow之间

由于在本文的demo中动画控制器作为一级界面,基于简(tou)单(lan)方(shao)便(xie)的原则,我使用了UIViewController,并在上面添加了一个冒充导航栏的view来实现的。如果动画实现需要在二级以上的界面实现,你可能需要继承UINavigationController来自定义这个效果。

准备工作

首先我们在故事板中将界面搭建起来,我们从右下侧的控件栏拖出来一个UIView,注意这个UIView一定要拖拉到顶部在状态栏以内,否则你创建的约束顶部将会以状态栏下方开始,比预期的位置下移了20pt


自定义导航栏

其次在这个导航栏view上面拉进来一个UILabel关联文件名为navigationTitle,并设置约束为下、左右三个方向的约束为0,高度设置为44pt限制。文本居中,并修改为白色字体跟系统字体18号

添加导航标题label

最重要的是创建一个用于添加倒计时信息和一些label的透明视图,添加过程以及约束建立我就不再多说了,这是我添加完成之后的图示:


透明视图

完成这些工作之后,在左侧的视图层次栏里面移动这些视图的位置,确保红色背景的视图在导航栏视图的下方,内容透明视图在导航栏上方,接着就将一部分关键的约束关联到文件中。这些属性关联包括:

  • 滚动视图tableView
  • 透明视图内部子视图的居中约束titleCenterX
  • 透明背景视图的顶部约束contentTopConstraint
  • 红色背景视图的顶部约束backgroundTopConstraint
  • 还有伪装的导航栏标题文本的底部约束labelBottomConstraint

动画制作

首先我们需要监听tableView的滚动,这一部分代码我通常写在viewDidAppear:方法中。同样的,我喜欢在viewDidDisappear:中释放监听或者通知。

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear: animated];
    [self.tableView addObserver: self forKeyPath: OBSERVERKEY options: NSKeyValueObservingOptionNew context: nil];
}

- (void)viewDidDisappear:(BOOL)animated
{
    [self.tableView removeObserver: self forKeyPath: OBSERVERKEY];
}

在前面我已经说过滚动视图的动效了,那么我们在监听中需要获取滑动偏移,然后计算出滚动效果百分比:

CGFloat yOffset = self.tableView.contentOffset.y / MAXMOVE;
yOffset = MAX(0, MIN(1, yOffset));

这个数值会随着用户的滚动增大逐渐增加到1就不再增加,当到达1的时候就是动效的最终效果。在这过程中,导航栏的标题总共存在上移、渐变隐藏两种效果:

self.navigationTitle.alpha = 1 - yOffset;
self.labelBottomConstraint.constant = yOffset * TITLEMAXMOVE;

接着是红色的背景视图和倒计时这些控件的上移,其中所有的数据展示的控件都被添加到透明的视图上,所以我们只需要移动透明视图:

CGFloat yPoint = TOPANDBOTTOMCONSTANT - yOffset * MAXMOVE;
self.backgroundTopConstraint.constant = self.contentTopConstraint.constant = yPoint;

ok,所有组件的移动代码已经写完了,运行代码试一试。你发现了在上移过程中,透明视图的子控件似乎上移的有些过了。

正常情况下导航栏的高度是44pt,上面有个高度为20pt的状态栏,因此在动效中我设置的移动最大距离为44+20=64pt。而在demo中我们可以看到在移动64pt之后红色背景和透明视图确实移动到了64pt的中心,就是说在这两个视图高度为45pt的情况下,上面有9.5pt的尺寸进入了状态栏的范围中。因此,我们在移动动画过程中,如果要让文本信息能够在导航栏的位置上居中,我们还需要让这些文本信息下移9.5pt的距离。因此在监听回调中添加这么一句代码:

self.infoCenterX.constant = TOPANDBOTTOMCONSTANT * yOffset;

添加完这句代码再次运行,我们的动效就完成了!

尾言

我想说,作为自动约束动画的第一篇博客,我构思了很久。因为对于约束动画来说,这个demo的实现更像是UIView动画式的实现 —— 修改某些坐标值。但最终我还是觉得使用这个demo可以更快速的从UIView动画中转入约束动画内,至于下一篇我会讲解如何调用重绘方法来实现约束重制的动效实现以及在动画中替换更新约束。

上一篇:Transform和keyFrame动画
下一篇:layout动画的更多使用

本文demo
转载注明原作者和地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,881评论 4 368
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,052评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,598评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,407评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,823评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,872评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,037评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,778评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,505评论 1 247
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,745评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,233评论 1 264
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,568评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,231评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,141评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,939评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,954评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,784评论 2 275

推荐阅读更多精彩内容

  • 约束动画的文章要比预计的迟迟来临,最大的原因是没有找到我认为的足够好的动画来讲解约束动画 —— 当然了,这并不是因...
    YYT1992阅读 1,798评论 0 1
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,589评论 25 707
  • 培训结束,驱车回家,途径十八湾,停车驻足,拾起回忆。 十八湾是我来锡城的第一站,一晃四年过去,美丽的山水给了我四年...
    溪南客阅读 438评论 4 5
  • (一) 相传人死后,过了鬼门关便上了黄泉路,路上盛开着只见花,不见叶的彼岸...
    暖心奶茶阅读 1,227评论 0 0
  • PHP 脚本可放置于文档中的任何位置。当一个网页被请求的时候,php解析器首先分析文档里面的php语句,将php部...
    Farewell_V587阅读 325评论 1 1