在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. 安装liteIDE on mac

    download and install: http://sourceforge.net/projects/liteide/files/ 解决不能编译,没有自动完成的问题: http://stacko ...

  2. BZOJ 4425: [Nwerc2015]Assigning Workstations分配工作站

    难度在于读题 #include<cstdio> #include<algorithm> #include<queue> using namespace std; p ...

  3. excludeFromRecents标签

    Android:excludeFromRecents控制在不在recent列表中显示. true时不显示:false显示,默认. 运行如下activity后,不会显示在recent列表中. <a ...

  4. 【原创】Mysql中事务ACID实现原理

    引言 照例,我们先来一个场景~ 面试官:"知道事务的四大特性么?" 你:"懂,ACID嘛,原子性(Atomicity).一致性(Consistency).隔离性(Isol ...

  5. luogu3370 【模板】字符串哈希

    #include <algorithm> #include <iostream> #include <cstring> #include <cstdio> ...

  6. 大数据学习——scala入门练习

    package com /** * Created by ZX on 2015/11/6. */ object VariableDemo { def main(args: Array[String]) ...

  7. [python工具][2]sublime的快捷键

    十.Sublime Text 快捷键列表 快捷键按类型分列如下: 1.通用  ↑↓← →    上下左右移动光标 Alt    调出菜单 Ctrl + Shift + P    调出命令板(Comma ...

  8. SpriteKit-(SKNode)

    1.初始化 + (instancetype)node; + (nullable instancetype)nodeWithFileNamed:(NSString*)filename; 2.返回边界边框 ...

  9. ACM程序设计选修课——1024: 末位零(求末尾0的方法+可有可无的快速幂)

    1024: 末位零 Time Limit: 1 Sec  Memory Limit: 32 MB Submit: 60  Solved: 11 [Submit][Status][Web Board] ...

  10. [codevs2185]最长公共上升子序列

    [codevs2185]最长公共上升子序列 试题描述 熊大妈的奶牛在小沐沐的熏陶下开始研究信息题目.小沐沐先让奶牛研究了最长上升子序列,再让他们研究了最长公共子序列,现在又让他们要研究最长公共上升子序 ...