UI动画高级篇(一)
一、引入·Xcode设置圆角按钮
1.在何处设置圆角属性
Xcode的属性检查器中不能直接设置按钮为圆角,因为圆角属性是程序运行时显示的。故应在身份检查器的user defined runtime attributes(用户定义的运行时属性)处增加KeyPath并通过KVC添加Button的layer.cornerRadius(层的角半径)来设置圆角。
注意:user defined runtime attributes无报错机制,我们须保障正确的属性名书写和Type选择。
(图1 Xcode检查器分类)
2.按钮上有无激活masksToBounds属性的必要
首先我们来看下cornerRadius属性的官方描述和定义,如图。
(图2 cornerRadius属性的官方描述)
可知,只设置layer.cornerRadius就可以显示圆角了。
那为何有人想多激活一个layer的masksToBounds属性呢。我们可以再看下masksToBounds属性的官方描述和定义。
(图3 masksToBounds属性的官方描述)
即是说,若给控件激活了masksToBounds属性,该属性就会生成一个和控件大小一样的隐式罩子,严密地覆在控件上,若设置了控件为圆角,则该罩子也是圆角罩。以button为例:
就是该罩子无论如何都严丝合缝地覆盖在button上,超出罩子的都裁边。
那么此人添加masksToBounds的理由就是,防止程序猿在button上又放一个子控件时,如该子控件有超出button的bounds的部分,那么子控件就会覆盖button显示,导致设按钮为圆角毫无意义。而激活maskToBounds后,因为罩子以外范围的东西都被裁边。故就算button思维子控件千变万化,最后看到的仍是圆角button的样子。但没有必要这么做的原因如下:
UIButton上是无法添加子控件的。故不存在UIButton的子控件超出本控件显示的问题,因为UIButton根本不可能有子控件!
当然,就算不能添加子控件,但可以在button的layer上添加一个或多个sublayer,那样也可能造成边界溢出的现象。好吧,如果你非要这么添加,那就加上masksToBounds=YES以避免这种智障情况吧。
3.何时使用masksToBounds才恰当
答案是在可添加子控件的控件上都可使用。如UIView。下面举个例子。
(图4 添加一个UIView和它的一个子视图UIButton)
(1)我们将layer.masksToBounds的对勾去掉,运行后视图如下
(图5 关闭UIView的masksToBounds属性后的运行效果)
(2)我们将layer.masksToBounds的对勾打上,运行后视图如下
(图6 打开UIView的masksToBounds属性后的运行效果)
可知,这就是为何有时在某些控件上要激活masksToBounds,防止出现一些bug的原因。但UIButton控件是不用多此一举的。
4.什么是mask属性
masksToBounds属性的说明中,提到了mask属性,mask是个CALayer类对象,即它是一个层。
该属性官方说明如下图
(图7 mask属性的官方说明)
(图8 mask属性的官方注释)
(1)这里提到第一个名词叫阿尔法通道。阿尔法通道是一个8位的灰度通道,该通道用256级灰度来记录图像中的透明度信息,定义透明、不透明和半透明区域,其中黑表示透明,白表示不透明,灰表示半透明。也就说,阿尔法通道可看做过滤器,即便是彩色图,通过阿尔法通道也会过滤为灰色。
(2)这里提到的第二个名词是"transparent" 和 "opaque",这两个词都可翻译成中文的“透明”或“不透明”,出现这种情况的原因是中西方文化差异。中国人说透明就是可见,不透明就是不可见,西方说透明反而是不可见,说透明反而是可见。只是可不可见的指代对象不同。好比你在一堵完全透明的玻璃前,是看不到玻璃本身的,走过去就会撞到玻璃上,因为透明到了极限,这东西就对你不可见了,但是你可以看见玻璃后面的东西。故对玻璃我们可以说它透明,也可说它不透明,但编程中,我们一般遵照西方思维,说玻璃是不透明的,即玻璃不可见。
transparent若译为中文的“可见”,指的是能看见透明物的后面。若译为中文的“不可见”,指的是看不见透明物本身。
opaque若译为中文的“可见”,指的是能看见透明物。若译为中文的“不可见”,指的是看不见透明物的后面。
原句:
Fully or partially opaque pixels allow the underlying content to show through but fully transparent pixels block that content.
语义读者可自行揣摩。
(3)同一层的mask属性和masksToBounds可以共存,且效果叠加。前者是在本层显示范围内过滤,后者是把超出本层bounds的子层裁边。二者其实没有相互影响的。
(4)但一个细节是,按理说,mask和masksToBounds都是生成一个“罩子”,为何后者要用mask的复数形式(即masks-ToBounds)来定义属性名,而前者只用单数呢。其实想想也就明白,mask永远只要一层就够了,一层就足够过滤它所覆盖的层的内容和背景了。但masksToBounds是要裁边子层,子层可能有很多,所以,由masksToBounds生成的罩子肯定不止一个,可以把maskToBounds生成的罩子想成一个立体的空水桶,所以用masks就不足为奇了。
(5)下图验证默认的mask和masksToBounds都为空。
(图9 mask和masksToBounds属性都默认为空)
二、起止型动画/动画三剑客/老式动画
1.起止型动画有哪三个基本方法
开始动画、动画时长、提交动画
(图10 起止型动画三要素)
动画块执行是一个线性的过程,故也可以用此方法逐渐改变控件的透明度。
如,要使一button逐渐从不透明改变到0.5的透明度,只需要在起止方法间加上如下代码:
button.alpha=0.5;
2.起止型动画在控件移动中为何不能处理点击事件
因为[UIView commitAnimations];后面的代码不代表动画结束。
我们看起来控件是在慢慢移动,但是实际上,控件是瞬间移动到动画结束位置,故而点击移动中的控件是没有任何效果的。测试如下:
(图11 起止型动画不能在控件移动中处理点击事件)
3.若我们要在动画结束后进行操作,该用什么方法
我们想在动画结束后一顿操作,又不能在[UIView commitAnimations];方法之后写,那么怎么实现功能呢。这时我们可以用一个废弃的代理方法。
因为是代理方法,所以我们必须先设置代理。
[UIView setAnimationDelegate:self];
然后再在本类中实现如下图代码:
(图12 废弃的代理方法)
因为此方法已经废弃,Xcode 6之后都不能用了,官方文档上也已经查不到此方法了。
当然还有第二种方法也可以完成此项操作,后面会提到。
4.起止型动画的其他常用方法
(1)让动画从当前状态开始。
[UIView setAnimationBeginsFromCurrentState:YES];
用法举例:
若我们用一个UIButton控制一个UIView,第一次点击button让view垂直下落300像素,第二次点击button让view水平右移200像素。
若是按钮动画时间设得较长而我们点击较快时,view不会中途转变方向,而是出现一些难以理解的幺蛾子。但激活animationBeginsFromCurrentState属性后,动画就会从当前状态开始移动,当我们在下落途中就点击了第二次,那么view就会从点击时下落到的地方开始,完成第二次动画。如下图
(图13 正常运行和脑残运行路线)
(2)让动画传参
[UIView setAnimationWillStartSelector:@selector(begin)];[UIView setAnimationDidStopSelector:@selector(over)];
这两个方法是为动画设置一个启动器和停止器。当动画开始时,启动器可以调用我们自己写的begin方法,动画结束时,停止器可以调用我们自己写的over方法。通过第二个方法,我们也可以实现在动画结束后一顿操作。我们可以在方法中打个输出来观察。
2016-09-27 14:19:16.969 AnimationTest[4111:139250] 动画开始2016-09-27 14:19:17.968 AnimationTest[4111:139250] 动画结束
4.这里将官方文档的相关方法部分翻译
(图13 beginAnimations:context:方法)
(图13 commitAnimations方法)
(图14 setAnimationStartDate:方法)
(图15 setAnimationsEnabled:方法)
(图16 setAnimationDelegate:方法)
(图17 setAnimationWillStartSelector:方法)
(图18 setAnimationDidStopSelector:方法)
(图19 setAnimationDuration:方法)
(图19 setAnimationDelay:方法)
(图19 setAnimationCurve:方法)
(图20 setAnimationRepeatCount:方法)
(图21 setAnimationRepeatAutoreverses:方法)
(图22 setAnimationBeginsFromCurrentState:方法)
(图23 setAnimationTransition:forView:cache:方法)
(图24 areAnimationsEnabled属性)