在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. German Collegiate Programming Contest 2018​ B. Battle Royale

    Battle Royale games are the current trend in video games and Gamers Concealed Punching Circles (GCPC ...

  2. kettle-批量同步表数据

    一.实验目标 利用kettle实现从mysql数据库中的dbf库批量同步表到dbm库(全量同步) 二.实验环境 dbf  库中表f1.f2.f3  .f1中1条数据,f2中100条数据,f3中2条数据 ...

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

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

  4. flask-用户资料

    首先创建User模型 class User(UserMixin,db.Model): __tablename__ = 'users' #.. name = db.Column(db.String(64 ...

  5. Mongodb 删除记录里的某个字段

    //例如要把User表中address字段删除 db.User.update({},{$unset:{'address':''}},false, true)

  6. 第五部分 linux 软件安装RPM SRPM与YUM

    第五部分  linux  软件安装RPM    SRPM与YUM   软件管理员简介 RPM与DPKG两大主流 rpm: redhat       centos     suse    命令:yum ...

  7. 九度oj 题目1355:扑克牌顺子

    题目描述: LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)...他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他 ...

  8. POJ 1323 Game Prediction

    Game Prediction Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 8956   Accepted: 4269 D ...

  9. 【bzoj4031】[HEOI2015]小Z的房间 矩阵树定理

    题目描述 你突然有了一个大房子,房子里面有一些房间.事实上,你的房子可以看做是一个包含n*m个格子的格状矩形,每个格子是一个房间或者是一个柱子.在一开始的时候,相邻的格子之间都有墙隔着. 你想要打通一 ...

  10. 【bzoj1965】 [Ahoi2005]SHUFFLE 洗牌 欧拉定理

    题目描述 为了表彰小联为Samuel星球的探险所做出的贡献,小联被邀请参加Samuel星球近距离载人探险活动. 由于Samuel星球相当遥远,科学家们要在飞船中度过相当长的一段时间,小联提议用扑克牌打 ...