约束动画的文章要比预计的迟迟来临,最大的原因是没有找到我认为的足够好的动画来讲解约束动画 —— 当然了,这并不是因为约束动画太难。相反,因为约束动画实在太简单了,反而没有足够多的简单动画素材让我选用。下面这个动画取自于朋友公司的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 hierarchy和Navigation View中间
  • 红色背景处在Navigation view和UIWindow之间

由于在本文的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动画中转入约束动画内,至于下一篇我会讲解如何调用重绘方法来实现约束重制的动效实现以及在动画中替换更新约束。

笔记-动画篇-layout动画初体验的更多相关文章

  1. android动画(3)layout动画,layoutChanged动画及算定义它,ListViewActivity的Layout动画(代码和xm配置两种实现l)

    1.layout切换动画 代码: 本示例是fragment切换.在它的oncreateView中 public class LayoutAnimationFrgmt extends Fragment ...

  2. Android遍历API (1) 动画篇——克隆动画AnimationCloning

    从我学Android开始,一直就想做一件事.就是好好把APIDemo看一遍.今天开始会抽时间把Android官方的APIDemo程序全部过一遍.主要是为了两个目的:第一,复习以前学习的API用法.第二 ...

  3. (原)Unreal源码搬山-动画篇 自定义动画节点(一)

    @author:黑袍小道 太忙了,来更新下,嘿嘿 前言: 本文是接着上文 Unreal搬山之动画模块_Unreal动画流程和框架,进行简单入门如何自定义动画图标的AnimNode. 正文: 一.Ani ...

  4. jQuery-4.动画篇---自定义动画

    jQuery中动画animate(上) 有些复杂的动画通过之前学到的几个动画函数是不能够实现,这时候就需要强大的animate方法了 操作一个元素执行3秒的淡入动画,对比一下2组动画设置的区别 $(e ...

  5. Android学习笔记(十)BroadcastReceiver初体验

    BroadcastReceiver是Android系统的四大组件之一,本质是一种全局的监听器,用于接收系统全局的广播消息.真因为如此,BroadcastReceiver可以很方便的是实现系统中不同组件 ...

  6. Git学习笔记(windows git之初体验)

    阿里国内镜像地址: https://npm.taobao.org/mirrors/git-for-windows/ 最近在学习廖雪峰老师关于git的教程,链接可以在我的首页找到.首先使用国内镜像下载并 ...

  7. iOS开发--动画篇之layout动画深入

    "不得不说,单单是文章的标题,可能不足以说明本文的内容.因此,在继续讲述约束动画之前,我先放上本文要实现的动画效果." 编辑:Bison投稿:Sindri的小巢 约束动画并不是非常 ...

  8. VSTO学习笔记(十五)Office 2013 初体验

    原文:VSTO学习笔记(十五)Office 2013 初体验 Office 2013 近期发布了首个面向消费者的预览版本,我也于第一时间进行了更新试用.从此开始VSTO系列全面转向Office 201 ...

  9. 数据结构(逻辑结构,物理结构,特点) C#多线程编程的同步也线程安全 C#多线程编程笔记 String 与 StringBuilder (StringBuffer) 数据结构与算法-初体验(极客专栏)

    数据结构(逻辑结构,物理结构,特点) 一.数据的逻辑结构:指反映数据元素之间的逻辑关系的数据结构,其中的逻辑关系是指数据元素之间的前后件关系,而与他们在计算机中的存储位置无关.逻辑结构包括: 集合 数 ...

随机推荐

  1. [itint5]环形最大连续子段和

    http://www.itint5.com/oj/#9 一开始有了个n*n的算法,就是把原来的数组*2,由环形的展开成数组.然后调用n次最大子段和的方法.超时. 后来看到个O(n)的算法,就是如果不跨 ...

  2. Android:自定义适配器

    无论是ArrayAdapter还是SimpleAdapter都继承了BaseAdapter,自定义适配器同样继承BaseAdapter 实例:Gallery实现图片浏览器 <?xml versi ...

  3. laravel5的坑

    以此记录学习laravel的一些问题 问题:laravel转移文件夹到另外一pc或者环境后访问出现500 设置权限为777 问题: 设置路由后页面总是404 not found 解决:需要在apach ...

  4. WPF之小动画三

    如果前两篇的博客太为普通,那么接下来的内容将让你动画实在是太厉害了.本文将会介绍两个关于纯手工实现动画的形式,当然动画效果就不用我多说了. 基于帧的动画: 此处的帧并不是之前介绍的Animation这 ...

  5. ORA-00911 :无效字符

    ———————————————————————————————————————————————————— 附: 1   ORA-01790:表达式必须具有与对应表达式相同的数据类型 知识解析:SQL ...

  6. Myeclipse 8.5 优化设置

    1.1 更改JavaScript文件默认编码 1.2 关闭Myeclipse不需要的启动项 1.3 取消Myeclipse自动更新 1.4 关闭Myeclipse自动验证 1.5 设置Myeclips ...

  7. VS下遇到未能加载文件或程序集 错误

    这个的错误原因可能是在64的系统上编译32位的应用程序,遇到这个错误,可以通过下面的手段解决! 1.关闭Visual Studio. 2. 在Visual Studio Tools子目录,以管理员身份 ...

  8. itoa函数的实现(不同进制)

    2013-07-08 17:12:30 itoa函数相对于atoi函数,比较简单,还是要注意考虑的全面. 小结: 一下几点需要考虑: 对负数,要加上负号: 考虑不同进制,根据要求进行处理:对不同的进制 ...

  9. Git教程(4)常用小技巧之标签

    Git 使用两种主要类型的标签:轻量标签(lightweight)与附注标签(annotated). 一个轻量标签很像一个不会改变的分支 - 它只是一个特定提交的引用. 然而,附注标签是存储在 Git ...

  10. ngui 脚本绘制sprite

    public GameObject _background; public UIAtlas atlas; private Dictionary<int, UISprite> _allCar ...