Layer中自定义属性的动画
2015-12-03 15:57
190 查看
转载自:http://blog.jobbole.com/69211/
默认情况下,
但有时候我们希望能同时为好几个属性添加动画,使它们看起来像是一个动画一样;或者,我们需要执行的动画不能通过使用标准 Layer 属性动画来实现。
在本文中,我们将讨论如何子类化
一般说来,我们希望添加到
能间接动画 Layer (或其子类)的一个或多个标准属性的属性。
能触发 Layer 背后的图像(即
不涉及 Layer 重绘或对任何已有属性执行动画的属性。
如果被我们设置的属性已经预设好标准动画,那我们完全不需要编写任何实际的动画代码,因为我们修改这些属性后,它们就会继承任何被配置在当前
换句话说,即使
为了演示这种方法,让我们来创建一个简单的模拟时钟,之后我们可以使用被声明为
同时我们要设置一个包含
现在我们只需要实现
结果看起来像这样:
你可以 从 GitHub 上 下载这个项目看看。
如你所见,我们实在没有做什么太费脑筋的事情;我们并没有创建一个新的可动画属性,而只是在单个方法里设置了几个标准可动画 Layer 属性而已。然而,如果我们想创建的动画并不能映射到任何已有的 Layer 属性上时,该怎么办呢?
与
在我们开始之前,需要先做一个小调整:因为不幸的是,
现在,既然我们已经移除了自定义的 setter 方法,那我们要如何才能知晓
这就告诉了 Layer ,无论何时
如果我们设置
但这还不是我们真正想要的;我们希望
现在,如果我们再次设置
由于某些原因,当我们在每个中间点打印
当你设置某个
但连接到 model Layer 的是所谓的 presentation Layer ——它是 model Layer 的一个拷贝,但它的值所表示的是 当前的,中间动画状态。如果我们修改
下面是打印出的值:
所以现在我们所要做就是画出时钟。我们将使用普通的 Core Graphics 函数以绘制到一个 Graphics Context 上来做到这一点,然后将产生出图像设置为我们 Layer 的
结果看起来如下:
如你所见,不同于第一个时钟动画,随着时针的变化,分针实际上对每一个小时都会转上满满一圈(就像一个真正的时钟那样),而不仅仅只是通过最短的路径移动到它的最终位置;因为我们正在动画的是
通过这样的方式绘制一个时钟并不是很理想,因为 Core Graphics 函数没有硬件加速,可能会引起动画帧数的下降。另一种能每秒重绘
通过避免在每一帧里都用昂贵的软件绘制,我们能改善动画的性能,但代价是我们需要在内存里存储所有预先绘制的动画帧图像,对于一个复杂的动画来说,这可能造成惊人的内存浪费。
但这提出了一个有趣的可能性。如果我们完全不在
下面的代码使用一个
我们可以通过使用一个简单的有着播放、停止、音量增大以及音量减小按钮的 View Controller 来做测试:
注意:尽管我们的 Layer 没有可见的外观,但它依然需要被添加到屏幕上的视图层级里,以便动画能正常工作。
通过使用这些属性,我们不仅仅避免了重复造轮子,同时还确保了我们的自定义动画能与标准动画的时机和控制函数协同工作,以此就能非常容易地与其它动画属性同步。
默认情况下,
CALayer及其子类的绝大部分标准属性都可以执行动画,无论是添加一个
CAAnimation到 Layer(显式动画),亦或是为属性指定一个动作然后修改它(隐式动画)。
但有时候我们希望能同时为好几个属性添加动画,使它们看起来像是一个动画一样;或者,我们需要执行的动画不能通过使用标准 Layer 属性动画来实现。
在本文中,我们将讨论如何子类化
CALayer并添加我们自己的属性,以便比较容易地创建那些如果以其他方式实现起来会很麻烦的动画效果。
一般说来,我们希望添加到
CALayer的子类上的可动画属性有三种类型:
能间接动画 Layer (或其子类)的一个或多个标准属性的属性。
能触发 Layer 背后的图像(即
contents属性)重绘的属性。
不涉及 Layer 重绘或对任何已有属性执行动画的属性。
间接属性动画
能间接修改其它标准 Layer 属性的自定义属性是这些选项中最简单的。它们仅仅只是自定义 setter 方法。然后将它们的输入转换为适用于创建动画的一个或多个不同的值。如果被我们设置的属性已经预设好标准动画,那我们完全不需要编写任何实际的动画代码,因为我们修改这些属性后,它们就会继承任何被配置在当前
CATransaction上的动画设置,并且自动执行动画。
换句话说,即使
CALayer不知道如何对我们自定义的属性进行动画,它依然能对因自定义属性被改变而引起的其它可见副作用进行动画,而这恰好就是我们所需要的。
为了演示这种方法,让我们来创建一个简单的模拟时钟,之后我们可以使用被声明为
NSDate类型
time属性来设置它的时间。我会将从创建一个静态的时钟面盘开始。这个时钟包含三个
CAShapeLayer实例 —— 一个用于时钟面盘的圆形 Layer 和两个用于时针和分针的长方形 Sublayer。
UIDatePicker的基本的 View Controller,这样我们就能测试我们的 Layer (日期选择器在 Storyboard 里设置)了:
time属性的 setter 方法。这个方法使用
NSCalendar将时间变为小时和分钟,之后我们将它们转换为角坐标。然后我们就可以使用这些角度去生成两个
CGAffineTransform以旋转时针和分针。
你可以 从 GitHub 上 下载这个项目看看。
如你所见,我们实在没有做什么太费脑筋的事情;我们并没有创建一个新的可动画属性,而只是在单个方法里设置了几个标准可动画 Layer 属性而已。然而,如果我们想创建的动画并不能映射到任何已有的 Layer 属性上时,该怎么办呢?
动画 Layer 内容
假设不使用几个分离的 Layer 来实现我们的时钟面板,那我们可以改用 Core Graphics 来绘制时钟。(这通常会降低性能,但我们可以假想我们所要实现的效果需要许多复杂的绘图操作,而它们很难用常规的 Layer 属性和 transform 来复制。)我们要怎么做呢?与
NSManagedObject很类似,
CALayer具有为任何被声明的属性生成 dynamic 的 setter 和 getter 的能力。在我们当前的实现中,我们让编译器去 synthesize 了
time属性的 ivar 和 getter 方法,而我们自己实现了 setter 方法。但让我们来改变一下:丢弃我们的 setter 并将属性标记为
@dynamic。同时我们也丢弃分离的时针和分针 Layer ,因为我们将自己去绘制它们。
CALayer不知道如何对
NSDate属性进行插值(interpolate)(例如,虽然它可以处理数字类型和其它例如
CGColor和
CGAffineTransform这样的类型,但它不能自动生成不同的
NSDate实例之间的中间值)。我们可以保留我们的自定义 setter 方法并用它设置另一个等价于
NSTimeInterval的动态属性(这是一个数字值,可以被插值),但为了保持例子的简单性,我们会用一个浮点值替换
NSDate属性来表征时钟的小时。我们还更新了用户界面,现在使用一个简单的
UITextField来设置浮点值,而不再使用日期选择器:
time属性的改变呢?我们需要一个无论何时
time属性改变时都能自动通知
CALayer的方式,这样它才好重绘它的内容。我们通过覆写
+needsDisplayForKey:方法即可做到这一点,如下:
time属性被修改,它都需要调用
-display方法。现在我们就覆写
-display方法,添加一个
NSLog语句打印出
time的值:
time属性为 1.5 ,我们就会看到
-display被调用,打印出新值:
time属性能在旧值和新值之间在几帧之内做一个平滑的过渡动画。为了实现这一点,我们需要为
time属性指定一个动画(或“动作(action)”),而通过覆写
-actionForKey:方法就能做到:
time属性,我们就会看到
-display被多次调用。调用的次数大约为每秒 60 次,至于动画的长度,默认为 0.25 秒,大约是 15 帧:
time值时,我们一直看到的是最终值。为何不能得到插值呢?因为我们查看的是错误的
time属性。
当你设置某个
CALayer的某个属性,你实际设置的是 model Layer 的值 —— 这里的 model Layer 表示正在进行的动画结束时, Layer 所达到的最终状态。如果你取 model Layer 的值,它就总是给你它被设置到的最终值。
但连接到 model Layer 的是所谓的 presentation Layer ——它是 model Layer 的一个拷贝,但它的值所表示的是 当前的,中间动画状态。如果我们修改
-display方法去打印 Layer 的
presentationLayer的
time属性,那我们就会看到我们所期望的插值。(同时我们也使用
presentationLayer的
time属性来获取动画的开始值,替代
self.time):
contents。下面是更新后的
-display方法:
如你所见,不同于第一个时钟动画,随着时针的变化,分针实际上对每一个小时都会转上满满一圈(就像一个真正的时钟那样),而不仅仅只是通过最短的路径移动到它的最终位置;因为我们正在动画的是
time值本身而不仅仅是时针或分针的位置,所以上下文信息被保留了。
通过这样的方式绘制一个时钟并不是很理想,因为 Core Graphics 函数没有硬件加速,可能会引起动画帧数的下降。另一种能每秒重绘
contents图像 60 次的方式是用一个数组存储一些预先绘制好的图像,然后基于合适的插值简单的选择对应的图像即可。实现代码大概如下:
但这提出了一个有趣的可能性。如果我们完全不在
-display里更新
contents图像会发生什么?我们做一些其它的事情怎样?
非可视属性的动画
在-display里更新其它 Layer 属性就是不必要的,因为我们可以很简单地直接对任何这样的属性做动画,如同我们在第一个时钟面板例子里所做的那样。但如果我们设置一些其它的东西,比如某些完全和 Layer 不相关的东西,会怎样呢?
下面的代码使用一个
CALayer结合
AVAudioPlayer来创建一个可动画的音量控制器。通过把音量绑定到 dynamic 的 Layer 属性上,我们可以使用 Core Animation 的属性插值来平滑的在两个不同的音量之间渐变,以同样的方式我们可以动画 Layer 上的任何自定义属性:
结论
CALayer的 dynamic 属性提供了一中简单的机制来实现任何形式的动画 —— 不仅仅只是内建的那些。而通过覆写
-display方法,我们可以使用这些属性去控制任何我们想控制的东西,甚至是音量值这样的东西。
通过使用这些属性,我们不仅仅避免了重复造轮子,同时还确保了我们的自定义动画能与标准动画的时机和控制函数协同工作,以此就能非常容易地与其它动画属性同步。
相关文章推荐
- Linux shell实现Mysql异地备份数据库
- 一个容易被忽略的ReportingService超时问题
- Xcode7 网络访问限制
- 手机浏览器市场份额统计 和 UserAgent使用
- 斐波那契数列求解的优化
- Java NIO系列教程(五) 通道之间的数据传输
- 用腾讯的技术实现自己的搜索和大数据分析
- ubuntu中&&(命令执行控制)
- Lua和C++交互总结(很详细)
- 破解由于异步执行而导致的JS插件未加载就使用的问题
- iOS开发之键盘类型UIKeyboardType
- iOS-数据持久化-属性列表
- 在matlab和opencv中分别实现稀疏表示
- Linux 下自解压文件的制作
- unity3d 根据tag查找物体编辑器工具
- 信息学奥林匹克竞赛-小鱼的数字游戏
- 招标系统-easyui插件中标签选择和创建
- Qt浅谈之三十四仿登录界面
- 常用电压基准芯片
- 模仿实现C++库函数----String 类----用 写时拷贝 实现