在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-ICPC 2018 徐州赛区网络预赛 I. Characters with Hash

    Mur loves hash algorithm, and he sometimes encrypt another one's name, and call him with that encryp ...

  2. Leetcode 145. 二叉树的后序遍历

    题目链接 https://leetcode-cn.com/problems/binary-tree-postorder-traversal/description/ 题目描述 给定一个二叉树,返回它的 ...

  3. HDU 4781 Assignment For Princess 构造

    题意: 构造一个\(N(10 \leq N \leq 80)\)个顶点\(M(N+3 \leq M \leq \frac{N^2} {7})\)条边的有向图,要满足如下条件: 每条边有一个\([1,M ...

  4. Struts2理解——转发和重定向

        转发和重定向设置:         <action name="deptAction" class="com.syaccp.erp.action.DeptA ...

  5. Java生产者消费者模式

    为什么要使用生产者和消费者模式 在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程.在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能 ...

  6. hibernate基础工具findBySQL学习

    public List<Map<String,Object>> findBySQL(String sql,Map<String,Object> param,int ...

  7. python调用C/C++动态链接库和jython

    总结(非原创) Python调用C库比较简单,不经过任何封装打包成so,再使用python的ctypes调用即可. 1. C语言文件:pycall.c #include <stdio.h> ...

  8. 【Luogu】P2598狼和羊的故事(最小割转最大流)

    题目链接 最小割水题.入点向白点连边,白点向白点.黑点和空点连边,空点向空点和黑点连边,黑点向黑点和汇点连边.然后跑最大流即可. 话说Fd最近怎么光做水题啊……一点用都没有……qwq 我太菜了,做完一 ...

  9. BZOJ2653 middle 【二分 + 主席树】

    题目 一个长度为n的序列a,设其排过序之后为b,其中位数定义为b[n/2],其中a,b从0开始标号,除法取下整.给你一个 长度为n的序列s.回答Q个这样的询问:s的左端点在[a,b]之间,右端点在[c ...

  10. Golang指针

    学过C语言的老司机都知道,指针就是一个变量,用于存储另一个变量的内存地址. 那么什么是变量呢?在现代计算机体系结构中所有的需要执行的信息代码都需要存储在内存中,为了管理存储在内存的数据,内存是划分为不 ...