SECTION 4

现在我们已经了解了Core Animation的基础并且使用了优秀的框架JNWSpringAnimation来模仿弹簧系统,是时候来开始写一些真实的示例代码了。

仿制一个iOS AlertView

重现一个熟悉的界面元素是一个很好的熟悉动画开发的方式。首先,让我们创建我们自己的标准iOS警告视图。这是内置的警告视图的样子。


SECTION 4 - 图1


在本指南之前的章节中,我解释了分解一个动画的各个组成部分有多么重要,这样你就可以准确地构建它。仅仅说“警告框动画进入屏幕”是不够的,你需要准确地知道发生了什么。让我们来分解这个动画。

  1. 屏幕随着渐入的一层半透明灰覆盖变暗。
  2. 警告框从完全透明以及比1.0倍大的大小开始,并动画至100%不透明和1.0倍大小。
  3. 消失的时候,它会淡出为完全透明并且比例会动画减小到比1.0要小。
  4. 阴暗的覆盖层淡出并消失。

在我们进入详细的代码之前,让我们看看我们要完成的警告框是什么样子的。


SECTION 4 - 图2


首先让我们创建一个简单的有白色背景的应用窗口。这是在应用的delegate类中,并且代码会在app完成启动的时候就立即运行。你可以在Alert View 1 Xcode工程中参考代码。

  1. - (BOOL)application:(UIApplication *)application
  2. didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  3. // Construct the main window for this application
  4. self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
  5. // All additional code in this example will go right here
  6. self.window.backgroundColor = [UIColor whiteColor];
  7. [self.window makeKeyAndVisible];
  8. return YES;
  9. }

在这一步,我们有一个UIWindow,其位置和方向可以准确地填充屏幕,并且背景色被设为了白色。如果我们现在立马运行它,它只会在模拟器中(或者你的手机,如果连接了的话)运行一个空的、白色的应用屏幕。现在来创建我们的覆盖层,将其添加到屏幕上,并将透明度设为0.0,因为我们现在不想显示它。

  1. UIView *overlayView = [[UIView alloc] initWithFrame:self.window.bounds];
  2. overlayView.backgroundColor = [UIColor blackColor];
  3. overlayView.alpha = 0.0f;
  4. [self.window addSubview:overlayView];

这个覆盖层是一个简单的UIView,填充了整个主窗口对象。这意味着它会被放置在窗口的左上角,并且其宽和高会匹配窗口,从而覆盖所有的内容。为了显示我现在有的内容,如果我提高覆盖层的不透明度,这就是看起来的样子。


SECTION 4 - 图3


现在让我们着手我们的警告框界面,为了便于在这个demo中实现,会仅仅是一个简单的图片而不是一个有label和按钮的纯代码的界面。让我们开始创建这个界面。

  1. CGFloat alertDimension = 250;
  2. CGRect alertViewFrame = CGRectMake(
  3. self.window.bounds.size.width/2 - alertDimension/2,
  4. self.window.bounds.size.height/2 - alertDimension/2,
  5. alertDimension, alertDimension);
  6. UIView *alertView = [[UIView alloc] initWithFrame:alertViewFrame];

首先,我们需要创建一个UIView对象来作为我们的虚拟警告框,并将其位置设为屏幕的正中央。这是通过将全屏幕的宽和高除以2并减去警告框的宽和高的一半完成的。我喜欢设置一个对象的frame到它完成动画后的最终位置,然后通过操作它的transform属性来调整它的大小或者位置。通过这种方式,当添加动画时,比起重新计算它的CGRectframe,我可以移除transform上已完成的操作。这也是为什么如果我想要它变成1.5倍,比起动画它的整个frame,不得不计算在像素层面它的位置和大小是多少,我更喜欢以好的、简单的增加来动画一个视图的transform.scale,而前一种方式是很痛苦的。保持简单。

是时候设置一些关键属性了。

  1. alertView.backgroundColor = [UIColor colorWithPatternImage:
  2. [UIImage imageNamed:@"alert_box"]];
  3. alertView.alpha = 0.0f;
  4. alertView.transform = CGAffineTransformMakeScale(1.2, 1.2);
  5. alertView.layer.cornerRadius = 10;

在这段代码中我们做了四件事:

  1. 设置backgroundColor属性为一个图片,这是我创建的看起来像是一个好看、简单的警告框视图的图片。
  2. 设置alpha为0,这样警告框就不会立马可见,直到我们想要它动画进入。
  3. 通过CGAffineTransformMakeScale()函数在变换矩阵中仅仅操作比例值来设置它的transform属性来让比例更大些。
  4. 通过设置视图layer的cornerRadius属性来形成圆角。

让我们给它加一点阴影来完成我们警告框的配置。

  1. alertView.layer.shadowColor = [UIColor blackColor].CGColor;
  2. alertView.layer.shadowOffset = CGSizeMake(0, 5);
  3. alertView.layer.shadowOpacity = 0.3f;
  4. alertView.layer.shadowRadius = 10.0f;
  5. [self.window addSubview:alertView];

如果我将alpha值调回1.0并移除比例增加的变换然后截屏,这就是它看起来的样子。


SECTION 4 - 图4


是时候添加一些动画了。为了警告框的显示,如我之前所说,我们想要覆盖层从完全透明(不可见)变成半透明。我们还想要添加两个动画到警告框中:将不透明度从0.0动画到1.0,以及将比例从大于1.0动画到1.0。这就是iOS 7的警告框做的事情,所以我们要模仿它。

首先让我们处理两个不透明度的动画(覆盖层和警告框视图),因为不透明度动画一般不需要任何高级的弹簧动作,让我们使用一些简单的基于block的UIView动画。

  1. // 淡入灰色的封盖层和警告框视图
  2. [UIView animateWithDuration:.3 delay:0 options:UIViewAnimationOptionCurveEaseInOut
  3. animations:^{
  4. overlayView.alpha = 0.3f;
  5. alertView.alpha = 1.0f;
  6. } completion:NULL];

我们同时在一个block中动画覆盖层和警告框视图的不透明度。这是因为我想要覆盖层和警告框在同一个动画和同样的时间中呈现给用户,所以为什么不一起动画它们呢?我将持续时间调整到比三分之一秒略少。我是通过尝试很多时间、运行动画、并做出对这个类型动画合适的选择来得出这个时间的。当显示一个重要的信息给用户时,比如警告框,使用一个柔和的动画时间是比较好的,这样实际的过渡会显得更重要。不要太快地显示出来,一个稍缓慢的时间会让信息显得更有分量和势头,且用户应该关注。

现在是时候动画警告框的比例了。这次我确实想用一个更加高级的弹簧动作来让进入比起上面例子中基于block的简单的淡入动画更有趣。在标准iOS警告款视图中,苹果公司没有弹动警告框,而是使用了一个缓慢衰减的动画来慢慢到达最终值。我们会协调弹簧动画的damping和stiffness属性来达到这样的效果。

  1. // Scale-animate in the alert view
  2. JNWSpringAnimation *scale = [JNWSpringAnimation animationWithKeyPath:@"transform.scale"];
  3. scale.damping = 14;
  4. scale.stiffness = 14;
  5. scale.mass = 1;
  6. scale.fromValue = @(1.2);
  7. scale.toValue = @(1.0);
  8. [alertView.layer addAnimation:scale forKey:scale.keyPath];
  9. alertView.transform = CGAffineTransformMakeScale(1.0, 1.0);

这个动画的关键路径是“transform.scale”,因为这就是layer上我们想要操作的属性。还记得我们第一次创建这个UIView并设其transform属性为CGAffineTransformMakeScale(1.2, 1.2)么?这就是我们开始的的fromValue,即当前的比例尺寸,我们要将其动画回1.0的比例,这是正常的尺寸和大小。

这就是现在动画看起来的样子。


SECTION 4 - 图5


很好,警告框已经准确地处于屏幕的中间,并且有我想要的动画。现在让我们开发消失的动画。

就如我们起初显示警告框并且确保它不会出现的太快一样,当警告框消失时我们需要思考一下时间应该是什么样的。我不知道你怎么想,但当我关闭一个警告框时,我想要立即回到我之前被打断的内容里去,所以基于此,我总是喜欢以比显示它更快的速度来清楚它。没必要让动画两端的时间保持对称,如果对用户有意义的话,可以调整动画的时间。

  1. // 淡出覆盖层和警告框
  2. [UIView animateWithDuration:.15 delay:0 options:UIViewAnimationOptionCurveEaseInOut
  3. animations:^{
  4. overlayView.alpha = 0.0f;
  5. alertView.alpha = 0.0f;
  6. } completion:NULL];

因为我们在回转我们的初始动画,我们现在需要将覆盖层和警告框视图的不透明度退回到0。同样,因为我想要这两个同时动画,所以我将它们放到同一个基于block动画中。注意这个淡出动画的时间只有淡入动画的一般长。我们想要让警告框离开屏幕的时候显得很爽利,让持续时间变短则可以完成这一需求。

接下来我们需要在其淡出到0不透明度的同时缩小警告框。

  1. // 警告框的缩小动画
  2. JNWSpringAnimation *scaleOut = [JNWSpringAnimation
  3. animationWithKeyPath:@"transform.scale"];
  4. scaleOut.damping = 11;
  5. scaleOut.stiffness = 11;
  6. scaleOut.mass = 1;
  7. scaleOut.fromValue = @(1.0);
  8. scaleOut.toValue = @(0.7);
  9. [alertView.layer addAnimation:scaleOut forKey:scaleOut.keyPath];
  10. alertView.transform = CGAffineTransformMakeScale(0.7, 0.7);

内置的iOS警告框会在淡出时缩小一点点,所以我们在这里也做同样的事情。比例值0.7只是我观察内置的警告框后得出的,并且看起来还不错。

这里是完整的动画:


SECTION 4 - 图6


构建更有想象力的警告框视图

现在我们基本重现了标准的iOS 7警告框视图,让我们娱乐一下,构建一些有不同类型动作的自定义的警告框视图。这里是一个警告框的例子,有着位置和比例的动画,并且其出现和消失的动画都是在屏幕的底部。


SECTION 4 - 图7


为了完成这个动画,支撑警告框的UIView和我们之前的例子的设置基本一致,但这一次我们需要更新它的transform属性来进行translation和scale的更改。

  1. alertView.transform = CGAffineTransformMakeScale(.25, .25);
  2. alertView.transform = CGAffineTransformTranslate(alertView.transform, 0, 600);

这会设置transform在X和Y方向上都变为0.25的比例,接着会对translation进行更变,将其放置到屏幕的底部。你也可以这样做来达到同样的效果。

  1. CGAffineTransform viewTransform = CGAffineTransformConcat(
  2. CGAffineTransformMakeScale(.25, .25), CGAffineTransformMakeTranslation(0, 600));
  3. alertView.transform = viewTransform;

现在UIView就已经被设为在动画开始前比例变小并且处于屏幕的底部了,我们可以开始下一步了。我们会将警告框的比例拉回1.0,并将其位置改回开始的位置,即屏幕的中央。我们依然会同时淡出覆盖层、淡入警告框。

  1. // 同时处理覆盖层和警告框
  2. UIView animateWithDuration:.3 delay:0 options:UIViewAnimationOptionCurveEaseInOut
  3. animations:^{
  4. overlayView.alpha = 0.2f;
  5. alertView.alpha = 1.0f;
  6. } completion:NULL];
  7. // 将警告框的比例动画从0.25变为1.0
  8. JNWSpringAnimation *scale =
  9. [JNWSpringAnimation animationWithKeyPath:@"transform.scale"];
  10. scale.damping = 12;
  11. scale.stiffness = 12;
  12. scale.mass = 1;
  13. scale.fromValue = @(.25);
  14. scale.toValue = @(1.0);
  15. [alertView.layer addAnimation:scale forKey:scale.keyPath];
  16. alertView.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.0, 1.0);
  17. // 将Y方向的位置从600动画回0
  18. // 将其放回原来的位置
  19. JNWSpringAnimation *translate = [JNWSpringAnimation
  20. animationWithKeyPath:@"transform.translation.y"];
  21. translate.damping = 15;
  22. translate.stiffness = 15;
  23. translate.mass = 1;
  24. translate.fromValue = @(600);
  25. translate.toValue = @(0);
  26. [alertView.layer addAnimation:translate forKey:translate.keyPath];
  27. alertView.transform = CGAffineTransformTranslate(alertView.transform, 0, 0);

如果你细心的话就会发现这两个动画的弹簧属性非常的相似。它们都有匹配damping的stiffness,意味着它是一种没有弹性的指数衰减类型的动作。然而scale动画的值比translation动画的值要低,意味着scale会慢一点点。这是因为我想要所有的动画大致在同时结束,因为translation动画比scale动画移动更多的值,所以它需要移动的快一点点来匹配scale动画的速度。这只会略微被注意到,但如果某个动画比另一个结束得早,绝对会看起来很奇怪。

对于收回的动画,警告框会收缩并且跳回屏幕的底部。如其他例子一样,我想要警告框有一个比起显示到用户面前时更快的动作。

  1. // 一起淡化覆盖层和警告框
  2. [UIView animateWithDuration:.2 delay:0 options:UIViewAnimationOptionCurveEaseInOut
  3. animations:^{
  4. overlayView.alpha = 0.0f;
  5. alertView.alpha = 0.0f;
  6. } completion:NULL];
  7. // 动画将警告框的比例从1.0变为0.5
  8. JNWSpringAnimation *scale =
  9. [JNWSpringAnimation animationWithKeyPath:@"transform.scale"];
  10. scale.damping = 17;
  11. scale.stiffness = 17;
  12. scale.mass = 1;
  13. scale.fromValue = @(1.0);
  14. scale.toValue = @(0.5);
  15. [alertView.layer addAnimation:scale forKey:scale.keyPath];
  16. alertView.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.5, 0.5);
  17. // 动画将位置从0变回屏幕底部
  18. JNWSpringAnimation *translate = [JNWSpringAnimation
  19. animationWithKeyPath:@"transform.translation.y"];
  20. translate.damping = 4;
  21. translate.stiffness = 4;
  22. translate.mass = 1;
  23. translate.fromValue = @(0);
  24. translate.toValue = @(600);
  25. [alertView.layer addAnimation:translate forKey:translate.keyPath];
  26. alertView.transform = CGAffineTransformTranslate(alertView.transform, 0, 600);

警告框消失的动画速度和之前非常的不一样:translation动画比scale动画要慢很多。原因是当translation动画移动得scale动画慢时,你会在警告框落回屏幕底部前看到更多的scale动画。我认为这种方式是一个很好的视觉效果,因为我加强了警告框退出的效果。

如果我们加快translation动画,使其damping和stiffness值和scale动画一样,这就是它看起来的样子。


SECTION 4 - 图8


与慢一点的过渡相比较…


SECTION 4 - 图9


现在让我们为我们的警告框视图创建一个不同类型的动作,从屏幕的中央出来并带有一些弹性动画来获取用户的注意。这就是它看起来的样子。


SECTION 4 - 图10


这是一个更简单的动画,因为我们只动画了警告框transform的一个属性,即scale。我们设置它的初始scale为0来建立我们的警告框视图。

  1. lofter 2016/6/30 9:15:45
  2. alertView.transform = CGAffineTransformMakeScale(0, 0);

和之前一样,我们想要给覆盖层和警告框一个淡化的动画,不过这一次我们会用弹性的出现来动画警告框的scale。

  1. JNWSpringAnimation *scale = [JNWSpringAnimation
  2. animationWithKeyPath:@"transform.scale"];
  3. scale.damping = 32;
  4. scale.stiffness = 450;
  5. scale.mass = 2.4;
  6. scale.fromValue = @(0);
  7. scale.toValue = @(1.0);
  8. [alertView.layer addAnimation:scale forKey:scale.keyPath];
  9. alertView.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.0, 1.0);

这里的弹簧的damping、stiffness和mass属性和我们用来创建平稳衰减到最终值的动作的属性非常不同。这些值会一直使用JNWSpringAnimation Mac app直到它们有了正确地弹性,不太快也不太强力。

觉得使用JNWSpringAnimation和自然的动作来构建动画界面和棒吗?非常好,是时候开始构建一些在第一节里显示的动画例子了。