View编程指南(四)

《View Programming Guide for iOS》文档翻译

Posted by Ted on January 15, 2018

苹果官方文档View Programming Guide for iOS

五、动画

动画在用户界面的不同状态之间提供流畅的视觉转换。 在iOS中,动画广泛用于重新定位view,更改大小,将其从view层次结构中移除,并将其隐藏起来。 您可以使用动画将反馈传达给用户或实现有趣的视觉效果。

在iOS中,创建复杂的动画不需要您编写任何绘图代码。 本章介绍的所有动画技术都使用Core Animation提供的内置支持。 您只需触发动画并让Core Animation处理单个帧的渲染。 这使得创建复杂的动画非常容易,只需要几行代码。

哪些可以有动画?

UIKit和Core Animation都支持动画,但每种技术提供的支持水平都不相同。 在UIKit中,动画是使用UIView对象执行的。 view支持一组涵盖许多常见任务的基本动画。 例如,您可以对view的属性进行动画更改,或使用过渡动画将一组view替换为另一组view

Property Changes you can make
frame 修改这个属性来修改View的位置和大小 (如果View的 transform 没有包含identity transform, 那么取而代之,修改 bounds or center .)
bounds 修改这个属性来修改 view’s size.
center 修改这个属性来修改相对于父View坐标系统的中心点
transform 修改这个属性来修改相对于其中心点的移动缩放旋转. 这是2D层面的 (如果要3D层面需要使用 Core Animation.)
alpha 修改这个属性来修改 view的透明度.
backgroundColor 修改这个属性来修改View的背景颜色
contentStretch 修改这个属性来使内容更好地适应填充

动画view转换是一种让您对view hierarchy进行更改的方式,而不是view controller提供的view hierarchy。虽然您应该使用view controller来管理简洁的view hierarchy,但有时您可能需要替换全部或部分view hierarchy。在这些情况下,您可以使用基于view的转换来动画添加和删除view。 在你想要执行更复杂的动画的时候,或者UIView类不支持的动画中,你可以使用Core Animation和view的底层layer来创建动画。由于view和layer对象错综复杂地链接在一起,因此对view layer的更改会影响view本身。使用核心动画,您可以为您的view的layer设置以下类型的更改:

  • layer的大小和位置
  • 执行转换时使用的中心点
  • 转换到三维空间中的layer或其sublayer
  • 从layer分层结构中添加或删除layer
  • 相对于其他兄弟layer的Z层顺序
  • layer的shadow
  • layer的border(包括layer的边角是否圆整)
  • 在调整大小操作期间延伸的layer部分
  • layer的不透明度
  • 位于layer边界之外的子layer的剪切行为
  • layer的当前内容
  • layer的栅格化行为

注意,如果你的View用了一个自定义的layer对象,也就是这个对象没有与View相关联,你必须用Core Animation来修改它

用Block动画

有几种基于block的方法为动画block提供不同级别的配置。 这些方法是:

  • animateWithDuration:animations:
  • animateWithDuration:animations:completion:
  • animateWithDuration:delay:options:animations:completion:

由于这些是类方法,因此您使用它们创建的动画块不会绑定到单个view。 因此,您可以使用这些方法创建一个包含对多个view进行更改的动画

[UIView animateWithDuration:1.0 animations:^{
        firstView.alpha = 0.0;
        secondView.alpha = 1.0;
}];

前一个例子中的动画只运行一次,使用一个ease-in,ease-out的动画曲线。 如果要更改默认的动画参数,则必须使用animateWithDuration:delay:options:animations:completion:方法来执行动画。 该方法可以让您自定义以下动画参数:

  • 开始动画之前使用的延迟
  • 在动画中使用的时间曲线的类型
  • 动画应该重复的次数
  • 当动画到达最后时,动画是否会自动反转
  • 触摸事件是否在动画进行过程中传递到view
  • 动画是否应该中断任何正在进行的动画,或者在开始之前等到动画完成
- (IBAction)showHideView:(id)sender
{
    // Fade out the view right away
    [UIView animateWithDuration:1.0
        delay: 0.0
        options: UIViewAnimationOptionCurveEaseIn
        animations:^{
             thirdView.alpha = 0.0;
        }
        completion:^(BOOL finished){
            // Wait one second and then fade in the view
            [UIView animateWithDuration:1.0
                 delay: 1.0
                 options:UIViewAnimationOptionCurveEaseOut
                 animations:^{
                    thirdView.alpha = 1.0;
                 }
                 completion:nil];
        }];
}

重要提示:当涉及该属性的动画已在进行中时,更改属性的值不会停止当前动画。 而是当前的动画继续,新值会有动画。

如果您的应用程序在iOS 3.2及更早版本中运行,则必须使用UIView的beginAnimations:context:commitAnimations类方法来定义您的动画块。iOS4之后则应该用block动画

动画block嵌套

您可以通过嵌套其他动画blcok来为动画block的某些部分分配不同的时序和配置选项。 顾名思义,嵌套动画block是在现有动画block内创建的新动画block。 嵌套动画与任何父动画同时启动,但运行(大部分)与他们自己的配置选项。 默认情况下,嵌套动画会继承父级的持续时间和动画曲线,但即使这些选项可以根据需要被覆盖

[UIView animateWithDuration:1.0
        delay: 1.0
        options:UIViewAnimationOptionCurveEaseOut
        animations:^{
            aView.alpha = 0.0;
 
            // Create a nested animation that has a different
            // duration, timing curve, and configuration.
            [UIView animateWithDuration:0.2
                 delay:0.0
                 options: UIViewAnimationOptionOverrideInheritedCurve |
                          UIViewAnimationOptionCurveLinear |
                          UIViewAnimationOptionOverrideInheritedDuration |
                          UIViewAnimationOptionRepeat |
                          UIViewAnimationOptionAutoreverse
                 animations:^{
                      [UIView setAnimationRepeatCount:2.5];
                      anotherView.alpha = 0.0;
                 }
                 completion:nil];
 
        }
        completion:nil];

在这种情况下,两个view正在被淡化为完全透明,但是另一个view对象的透明度在最终隐藏之前来回地多次改变。 在嵌套动画块中使用的UIViewAnimationOptionOverrideInheritedCurve和UIViewAnimationOptionOverrideInheritedDuration键允许为第二个动画修改第一个动画的曲线和持续时间值。 如果这些键不存在,则将使用外部动画块的持续时间和曲线。

在View之间创建动画转换

view转换可帮助您隐藏与在view层次结构中添加,删除,隐藏或显示view相关的突然更改。 您使用view transitions来实现以下类型的更改:

  • 更改现有view的可见subview。 当您想对现有view进行相对较小的更改时,通常会选择此选项。
  • 用不同的view替换view层次结构中的一个view。 如果要替换跨越全部或大部分屏幕的view层次结构,通常选择此选项。

重要提示:view转换不应与view controller启动的转换相混淆,例如present view controller的呈现或将新view controller推到navigation堆栈上。 view转换仅影响view层次,而view - controller转换也改变活动view controller。 因此,对于view转换,如果你在初始化转换时保持活动,那么VC在转换结束时也是活动的

修改View的subviews

更改view的subview允许您对view进行适度更改。例如,您可以添加或删除subview以在两种不同状态之间切换superview。在动画完成时,显示相同的view,但其内容现在不同。

在iOS 4和更高版本中,使用transitionWithView:duration:options:animations:completion:方法为view启动过渡动画。在传递给此方法的动画块中,通常动画的唯一更改是与显示,隐藏,添加或删除子view相关的更改。将动画限制为该集合允许view创建view之前和之后版本的快照图像,并且在两个图像之间创建动画,这更高效。但是,如果您需要动画其他更改,则可以在调用方法时包含UIViewAnimationOptionAllowAnimatedContent选项。包含该选项可防止view创建快照,并直接动画化所有更改。

- (IBAction)displayNewPage:(id)sender
{
    [UIView transitionWithView:self.view
        duration:1.0
        options:UIViewAnimationOptionTransitionCurlUp
        animations:^{
            currentTextView.hidden = YES;
            swapTextView.hidden = NO;
        }
        completion:^(BOOL finished){
            // Save the old text and then swap the views.
            [self saveNotes:temp];
 
            UIView*    temp = currentTextView;
            currentTextView = swapTextView;
            swapTextView = temp;
        }];
}

替换一个View

- (IBAction)toggleMainViews:(id)sender {
    [UIView transitionFromView:(displayingPrimary ? primaryView : secondaryView)
        toView:(displayingPrimary ? secondaryView : primaryView)
        duration:1.0
        options:(displayingPrimary ? UIViewAnimationOptionTransitionFlipFromRight :
                    UIViewAnimationOptionTransitionFlipFromLeft)
        completion:^(BOOL finished) {
            if (finished) {
                displayingPrimary = !displayingPrimary;
            }
    }];
}

View和layer动画一起

应用程序可以根据需要自由混合基于view和基于layer的动画代码,但配置动画参数的过程取决于谁拥有layer。 更改view拥有的层与更改view本身相同,并且应用于layer属性的任何动画都尊重当前基于view的动画块的动画参数。 你自己创建的layer也是如此。 自定义layer对象会忽略基于view的动画块参数,而是使用默认的“核心动画”参数。

如果要为所创建的layer自定义动画参数,则必须直接使用Core Animation。 通常,使用Core Animation动画化layer包括创建一个CABasicAnimation对象或CAAnimation的其他具体子类。 然后,您将该动画添加到相应的layer。 您可以从基于view的动画块内部或外部应用动画。

[UIView animateWithDuration:1.0
    delay:0.0
    options: UIViewAnimationOptionCurveLinear
    animations:^{
        // Animate the first half of the view rotation.
        CGAffineTransform  xform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(-180));
        backingView.transform = xform;
 
        // Rotate the embedded CALayer in the opposite direction.
        CABasicAnimation*    layerAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
        layerAnimation.duration = 2.0;
        layerAnimation.beginTime = 0; //CACurrentMediaTime() + 1;
        layerAnimation.valueFunction = [CAValueFunction functionWithName:kCAValueFunctionRotateZ];
        layerAnimation.timingFunction = [CAMediaTimingFunction
                        functionWithName:kCAMediaTimingFunctionLinear];
        layerAnimation.fromValue = [NSNumber numberWithFloat:0.0];
        layerAnimation.toValue = [NSNumber numberWithFloat:DEGREES_TO_RADIANS(360.0)];
        layerAnimation.byValue = [NSNumber numberWithFloat:DEGREES_TO_RADIANS(180.0)];
        [manLayer addAnimation:layerAnimation forKey:@"layerAnimation"];
    }
    completion:^(BOOL finished){
        // Now do the second half of the view rotation.
        [UIView animateWithDuration:1.0
             delay: 0.0
             options: UIViewAnimationOptionCurveLinear
             animations:^{
                 CGAffineTransform  xform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(-359));
                 backingView.transform = xform;
             }
             completion:^(BOOL finished){
                 backingView.transform = CGAffineTransformIdentity;
         }];
}];