在App设计中为了加强用户体验,我们会常常加入一些友好的动画效果。比如类似UIAlertView弹出的动画效果,由于系统中并没有直接提供类似的动画API,如果我们想要做出一样的效果,那就得深入的研究一下系统中的UIAlertView了。
仔细观察UIAlertView的动画你就会发现:这个动画是由几部分组成,它带一个视图大小抖动的效果。先是由小变大,再由大变小,最后变成本来的大小。但是这个大小的具体参数值和动画的速度恐怕是肉眼所不能看出来的。
本篇文章会使用一些objc runtime和CAAnimation的一些知识,通过本文你可以了解到如何研究一些objc中内部调用机制和动画基础。
要想知道这些动画的组成,我们就要从比较低层次的API:CALayer的一些调用开始。iOS动画最终都是加到Layer中的,加入Layer就要调用Layer对象这个方法:

1
- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key;

所以只要我们知道了anim参数,并把anim动画对象的属性揪出来,就可以知道到底是什么动画了,但是这个方法是系统Framework中的,通常我们是无法能知道anim到底是什么。这时我们就需要用一些objc的一些底层API:Objc Runtime来解决了。

Objc Runtime

Objc Runtime是由一组处理Objctive-C动态语言运行时的API函数组成,这些函数都是一些比较底层的C函数。它有很多实用功能比如查看对象的成员,类/对象方法签名等等。这次我们要用的就是其中把对象方法调用替换的API。

1
void method_exchangeImplementations(Method m1, Method m2)

这个函数的功能就是把类/对象的方法m1和m2进行调换。如果执行了这个函数,那么在App运行过程中所有调用方法m1的指令,最终都会执行成了方法m2。

方法调换

有了Objc Rumtime的API,就可以很方便的将调用系统库中方法的代码,执行成我们自己的代码了。所以我们想要知道Layer中加入了什么方法,只要把addAnimation:forKey:这个方法调换成我们自己的方法就行了。下面的这段代码就实现了这个功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@implementation CALayer(Hacked)
 
+ (void)load{
    method_exchangeImplementations(class_getInstanceMethod([CALayer class], @selector(addAnimation:forKey:)), class_getInstanceMethod([CALayer class], @selector(hackedAddAnimation:forKey:)));
}
 
- (void)hackedAddAnimation:(CABasicAnimation *)anim forKey:(NSString *)key{
    [self hackedAddAnimation:anim forKey:key];
    if ([anim isKindOfClass:[CABasicAnimation class]]) {
        if ([anim.keyPath isEqualToString:@"transform"]) {
            if (anim.fromValue) {
                CATransform3D fromValue = [anim.fromValue CATransform3DValue];
                NSLog(@"From:%@",NSStringFromCGAffineTransform(CATransform3DGetAffineTransform(fromValue)));
            }
            if (anim.toValue) {
                CATransform3D toValue = [anim.toValue CATransform3DValue];
                NSLog(@"To:%@",NSStringFromCGAffineTransform(CATransform3DGetAffineTransform(toValue)));
            }
            if (anim.byValue) {
                CATransform3D byValue = [anim.byValue CATransform3DValue];
                NSLog(@"By:%@",NSStringFromCGAffineTransform(CATransform3DGetAffineTransform(byValue)));
            }
            NSLog(@"Duration:%.2f",anim.duration);
            NSLog(@"TimingFunction:%@",anim.timingFunction);
        }
    }
}
 
@end

下面来说明一下上面的代码,这段代码是CALayer做了一个Catalog处理。其中initialize是一个类的方法,是进程开始时初始化类时调用,一般只有类有加载这个方法就会第一个调用了。hackedAddAnimation:forKey:是要被调换的代码。在类的初始化方法initialize中(代码中的第5行)实现了CALayer的addAnimation:forKey:和hackedAddAnimation:forKey:方法的调换。在hackedAddAnimation:forKey:中首先直接调用了[self hackedAddAnimation:forKey:],也许你会问:这不死循环递归了么?其实不是,应为method_exchangeImplementations实现的是调换而不是替换,所以代码中调用addAnimation:forKey:运行时就成了调用hackedAddAnimation:forKey:。而代码中调用hackedAddAnimation:forKey:运行时成了调用addAnimation:forKey:。所以这里虽然写的是hackedAddAnimation:forKey:,实际上会调用系统Framework中的addAnimation:forKey:。这样做的目的是保证虽然我们把系统的方法改变了,我们还是调用系统的一次,以保持系统功能运行是正常的。在hackedAddAnimation:forKey:剩下的代码就只是把anim动画对象的各个属性的值打印出来了。
好了,把上面的这段代码粘贴到你的代码文件中。然后简单的写个UIAlertView弹出动画代码。

1
2
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Title" message:@"Message" delegate:nil cancelButtonTitle:@"Close" otherButtonTitles:nil];
[alert show];

编译并运行上面这段代码,你就会在控制台中看到下面这些日志:

1
2
3
4
5
6
7
8
9
2013-04-10 19:13:11.795 Test[10952:c07] From:[0.01, 0, 0, 0.01, 0, 0]
2013-04-10 19:13:11.796 Test[10952:c07] Duration:0.20
2013-04-10 19:13:11.796 Test[10952:c07] TimingFunction:easeInEaseOut
2013-04-10 19:13:11.999 Test[10952:c07] From:[1.1, 0, 0, 1.1, 0, 0]
2013-04-10 19:13:12.000 Test[10952:c07] Duration:0.10
2013-04-10 19:13:12.000 Test[10952:c07] TimingFunction:easeInEaseOut
2013-04-10 19:13:12.101 Test[10952:c07] From:[0.9, 0, 0, 0.9, 0, 0]
2013-04-10 19:13:12.101 Test[10952:c07] Duration:0.10
2013-04-10 19:13:12.101 Test[10952:c07] TimingFunction:easeInEaseOut

查看CGAffineTransformMakeScale函数的头文件你会看到:

1
2
3
4
5
/* Return a transform which scales by `(sx, sy)':
     t' = [ sx 0 0 sy 0 0 ] */
 
CG_EXTERN CGAffineTransform CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)
  CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

所以根据日志我们会发现这其实是3个关键帧动画,首先scale(缩放比例)从0.01放大到1.1,历时0.2秒;然后从1.1到0.9,历时0.1秒;那么最后就是从0.9到1.0(正常缩放比例),历时0.1秒。哈哈,那我们就简单的写个关键帧动画对象就可以表示UIAlertView的弹出动画效果了。

1
2
3
4
5
6
7
8
9
10
11
CAKeyframeAnimation *popAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
popAnimation.duration = 0.4;
popAnimation.values = @[[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.01f, 0.01f, 1.0f)],
                        [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.1f, 1.1f, 1.0f)],
                        [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.9f, 0.9f, 1.0f)],
                        [NSValue valueWithCATransform3D:CATransform3DIdentity]];
popAnimation.keyTimes = @[@0.0f, @0.5f, @0.75f, @1.0f];
popAnimation.timingFunctions = @[[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
                                 [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
                                 [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[anAlertAnimationView.layer addAnimation:popAnimation forKey:nil];

你可以把popAnimation加入到你想进行动画的任何View中的layer中这样就实现了UIAlertView一样的弹出动画效果。

结论

1. UIAlertView动画其实是由三部分动画组成:缩放比例变化0.01->1.1->0.9->1.0。每次变化的时间函数(控制加速度)都是EaseInEaseOut。
2. 在研究系统中调用函数的参数是我们可以用method_exchangeImplementations来hack到系统调用中去,但不要忘记调用系统本身的方法。否则容易导致App异常。当然,如果你是研究测试不怕crash,那随便。

UIAlertView弹出视图动画效果的更多相关文章

  1. 窗体 dialog 弹出时动画效果

    1.先创建 anim中的 xml  动画文件 <?xml version="1.0" encoding="utf-8"? >   <set x ...

  2. Mantis的附件图片实现预览/弹出层动画效果预览图片(LightBox2)的完美解决方案[Z]

    方法1: 在Mantis的配置文件中,加入此句,将这个值设的很大,就可以直接看到图片 1 $g_preview_attachments_inline_max_size=1000000; 效果如图 这个 ...

  3. iOS-一个弹出菜单动画视图开源项目分享

    相似于Tumblr公布button的弹出视图 使用非常easy: 初始化: @property (nonatomic, strong) XWMenuPopView *myMenuPopView; - ...

  4. iOS实现自定义的弹出视图(popView)

    前段时间,在项目中有个需求是支付完成后,弹出红包,实现这么一个发红包的功能.做了最后,实现的效果大致如下: 一.使用方法 整个ViewController的代码大致如下 // //  SecondVi ...

  5. 点击弹出 +1放大效果 -- jQuery插件

    20140110更新: <!doctype html> <html> <head> <meta charset="UTF-8"> & ...

  6. IOS弹出视图 LewPopupViewController

    LewPopupViewController是一款IOS弹出视图软件.iOS 下的弹出视图.支持iPhone/iPad. 软件截图 使用方法 弹出视图 1 2 3 4 5 PopupView *vie ...

  7. 基于jQuery鼠标点击弹出登陆框效果

    基于jQuery鼠标点击弹出登陆框效果.这是一款扁平样式风格的jQuery弹出层登陆框特效.效果图如下: 在线预览   源码下载 实现的代码. html代码: <input type=" ...

  8. 微信小程序(19)-- 从底部向上滑出的动画效果

    从底部向上滑出的动画效果: 用到了小程序的触摸事件bindtouchmove,以及创建一个annimation对象,完成动画操作之后使用animation这个对象的export()方法导出动画数据. ...

  9. iOS 仿看了吗应用、指南针测网速等常用工具、自定义弹出视图框架、图片裁剪、内容扩展等源码

    iOS精选源码 扩展内容的cell - folding-cell 一个近乎完整的可识别中国身份证信息的Demo 可自动快速... JPImageresizerView 仿微信的图片裁剪 带年月和至今以 ...

随机推荐

  1. ACM Changchun 2015 J. Chip Factory

    John is a manager of a CPU chip factory, the factory produces lots of chips everyday. To manage larg ...

  2. 使用MeidaStore.Audio获得手机中的音频文件

    MediaStore是安卓系统自带的多媒体系统数据库,他在每次开机时刷新一次,可以通过Cursor这个类对数据库进行访问与修改,修改之后需用广播强制刷新. 使用Cursor必须通过Context获得C ...

  3. .Net Task常见问题

    最近尝试使用一下Task,但是使用过程中因为API的不熟悉碰到了很多问题,不清楚什么时间来调用Task.Start(),具体该怎么使用等等. 如下所描述的Task.Start()方法均为实例方法. 1 ...

  4. SPOJ 375 树链剖分 QTREE - Query on a tree

    人生第一道树链剖分的题目,其实树链剖分并不是特别难. 思想就是把树剖成一些轻链和重链,轻链比较少可以直接修改,重链比较长,用线段树去维护. 貌似大家都是从这篇博客上学的. #include <c ...

  5. Python虚拟机之异常控制流(四)

    Python虚拟机中的异常控制流 先前,我们分别介绍了Python虚拟机之if控制流(一).Python虚拟机之for循环控制流(二)和Python虚拟机之while循环控制结构(三).这一章,我们来 ...

  6. 02 Java 的基本类型

    Java 的基本类型 Java 包括了八种基本类型,明细如下: Java 的基本类型都有对应的值域和默认值.byte,short,int,long,float以及double的值域依次扩大,前面的值域 ...

  7. [办公软件篇][3]windows软件安装

    http://www.jeffjade.com/2015/10/19/2015-10-18-Efficacious-win-software/

  8. dpkg: deb包的操作命令

    dpkg -i package.deb #安装包 dpkg -r package #删除包 dpkg -P package #删除包(包括配置文件) dpkg -L package #列出与该包关联的 ...

  9. 用echarts.js制作中国地图,点击对应的省市链接到指定页面

    这里使用的是ECharts 2,因为用EChart 3制作的地图上的省市文字标识会有重叠,推测是引入的地图文件china.js,绘制文字的坐标方面的问题,所以,这里还是使用老版本. ECharts 2 ...

  10. HDU-2768 Cat vs. Dog

    题意一开始是理解错的...结果就各种WA啦~ 对于两个观众,假如有某只宠物,一个人讨厌另一个人却喜欢,这两个人就是有矛盾的,连边. 最后求最小顶点覆盖.因为把这个覆盖点集去掉的话剩下的图中没有两个点是 ...