Xamarin.iOS 中的核心动画

本文介绍 Core Animation 框架,其中介绍了如何在 UIKit 中实现高性能、流畅的动画,以及如何直接将其用于较低级别的动画控件。

iOS 提供核心动画,用于为应用程序中的视图提供动画支持。 iOS 中所有超流畅的动画(例如,滚动表格和在不同视图之间轻扫)的表现都非常出色,因为它们在内部依赖于核心动画。

核心动画框架和核心图形框架可协同工作,创建漂亮的动画 2D 图形。 事实上,核心动画甚至可以在 3D 空间中变换 2D 图形,创造出令人惊叹的电影体验。 但是,若要创建真正的 3D 图形,需要使用 OpenGL ES 这样的的内容,对于游戏则使用 MonoGame 这样的 API,尽管 3D 不在本文介绍范围内。

核心动画

iOS 使用核心动画框架来创建动画效果,例如在视图之间过渡、滑动菜单和滚动效果等等。 有两种处理动画的方法:

  • 通过 UIKit,其中包括基于视图的动画以及控制器之间的动画过渡。
  • 通过核心动画,它直接分层,可实现精细控制。

使用 UIKit 动画

UIKit 提供了几个功能,使得可轻松地向应用程序添加动画。 尽管它在内部使用核心动画,但它会将其抽象化,所以你只使用视图和控制器。

本部分讨论 UIKit 动画功能,包括:

  • 控制器之间的过渡
  • 视图之间的过渡
  • 查看属性动画

视图控制器转换

UIViewController 对通过 PresentViewController 方法在视图控制器之间进行过渡提供内置支持。 使用 PresentViewController 时,可以选择性地将到第二个控制器的过渡动画化。

例如,假设一个应用程序有两个控制器,其中触摸第一个控制器中的按钮会调用 PresentViewController 来显示第二个控制器。 若要控制用于显示第二个控制器的过渡动画,只需设置其 ModalTransitionStyle 属性即可,如下所示:

SecondViewController vc2 = new SecondViewController {
  ModalTransitionStyle = UIModalTransitionStyle.PartialCurl
};

在本例中,使用了 PartialCurl 动画,尽管有其他几个动画可用,其中包括:

  • CoverVertical - 从屏幕底部向上滑动
  • CrossDissolve - 旧视图淡出,新视图淡入
  • FlipHorizontal - 从右到左水平翻转。 关闭时,过渡从右到左翻转。

若要对过渡进行动画处理,请将 true 作为第二个参数传递给 PresentViewController

PresentViewController (vc2, true, null);

以下屏幕截图显示了在 PartialCurl 的情况下过渡的呈现效果:

此屏幕截图显示了 PartialCurl 转换

查看过渡

除了控制器之间的过渡外,UIKit 还支持在视图之间进行动画过渡,将一个视图交换成另一个视图。

例如,假设你有一个带有 UIImageView 的控制器,其中点击图像会显示第二个 UIImageView。 若要对图像视图的超级视图进行动画处理以过渡到第二个图像视图,只需调用 UIView.Transition,再向它传递 toViewfromView 即可,如下所示:

UIView.Transition (
  fromView: view1,
  toView: view2,
  duration: 2,
  options: UIViewAnimationOptions.TransitionFlipFromTop |
    UIViewAnimationOptions.CurveEaseInOut,
  completion: () => { Console.WriteLine ("transition complete"); });

UIView.Transition 还采用一个 duration 参数来控制动画的运行时间,采用 options 来指定要使用的动画和缓动函数等内容。 此外,还可以指定一个完成事件处理程序,当动画完成时调用它。

以下屏幕截图显示了使用 TransitionFlipFromTop 时图像视图之间的动画过渡:

此屏幕截图显示使用 TransitionFlipFromTop 时图像视图之间的动画切换

查看属性动画

UIKit支持在 UIView 类上免费创建各种属性的动画,包括:

  • Frame
  • 边界
  • 中心
  • Alpha
  • 转换
  • Color

这些动画通过在传递给静态 UIView.Animate 方法的 NSAction 委托中指定属性更改来隐式发生。 例如,以下代码对 UIImageView 的中心点进行动画处理:

pt = imgView.Center;

UIView.Animate (
  duration: 2,
  delay: 0,
  options: UIViewAnimationOptions.CurveEaseInOut |
    UIViewAnimationOptions.Autoreverse,
  animation: () => {
    imgView.Center = new CGPoint (View.Bounds.GetMaxX ()
      - imgView.Frame.Width / 2, pt.Y);},
  completion: () => {
    imgView.Center = pt; }
);

这会导致图像的动画效果是在屏幕顶部来回移动,如下所示:

图像的动画效果是在屏幕顶部来回移动作为输出

Transition 方法一样,Animate 支持设置持续时间以及缓动函数。 此示例还使用了 UIViewAnimationOptions.Autoreverse 选项,这会导致动画效果是从值回到初始值。 但是,代码还会在完成事件处理程序中将 Center 回退到其初始值。 当动画随着时间的推移内插属性值时,属性的实际模型值始终是已设置的最终值。 在此示例中,该值位于超级视图右侧附近的一个点。 如果不将 Center 设置为初始点(即在设置 Autoreverse 的情况下动画完成的位置),图像将在动画完成后弹回到右侧,如下图所示:

如果不将中心设置为初始点,图像将在动画完成后回退到右侧

使用核心动画

UIView 动画允许大量功能,由于易于实现,因此应尽可能地使用这些动画。 如前所述,UIView 动画使用核心动画框架。 但是,某些内容是无法用 UIView 动画完成的,例如,对无法使用视图进行动画的其他属性创建动画,或者沿非线性路径内插。 在需要精细控制的情况下,也可以直接使用核心动画。

图层

使用核心动画时,动画通过图层发生,这些图层的类型是 CALayer 图层在概念上类似于视图,因为有一个图层层次结构,就像有视图层次结构一样。 实际上,图层支持视图,支持添加视图来进行用户交互。 可以通过视图的 Layer 属性访问任何视图的图层。 事实上,在 UIViewDraw 方法中使用的上下文实际上是从图层创建的。 在内部,支持 UIView 的图层的委托设置为视图本身,也就是调用 Draw。 因此,在绘制到 UIView 图层时,你实际上正在绘制到它的图层。

图层动画可以是隐式的,也可以是显式的。 隐式动画是声明性的。 只需声明应更改哪些图层属性,动画就可以工作了。 另一方面,显式动画是通过添加到图层中的动画类创建的。 显式动画允许对动画的创建方式进行额外的控制。 以下部分将更深入地探讨隐式动画和显式动画。

隐式动画

要将图层属性动画化,一种方法是通过隐式动画。 UIView 动画会创建隐式动画。 但是,也可以直接针对图层创建隐式动画。

例如,以下代码从图像中设置图层的 Contents、设置边框宽度和颜色,并将该图层添加为视图层的子图层:

public override void ViewDidLoad ()
{
  base.ViewDidLoad ();

  layer = new CALayer ();
  layer.Bounds = new CGRect (0, 0, 50, 50);
  layer.Position = new CGPoint (50, 50);
  layer.Contents = UIImage.FromFile ("monkey2.png").CGImage;
  layer.ContentsGravity = CALayer.GravityResize;
  layer.BorderWidth = 1.5f;
  layer.BorderColor = UIColor.Green.CGColor;

  View.Layer.AddSublayer (layer);
}

若要为图层添加隐式动画,只需将属性更改包装在 CATransaction 中即可。 这允许在视图动画中不能动画化的属性,例如 BorderWidthBorderColor,如下所示:

public override void ViewDidAppear (bool animated)
{
  base.ViewDidAppear (animated);

  CATransaction.Begin ();
  CATransaction.AnimationDuration = 10;
  layer.Position = new CGPoint (50, 400);
  layer.BorderWidth = 5.0f;
  layer.BorderColor = UIColor.Red.CGColor;
  CATransaction.Commit ();
}

此代码还将图层的 Position 动画化,这是从超图层坐标左上角测量的图层定位点的位置。 图层的定位点是图层坐标系中的一个归一化点。

下图显示了位置和定位点:

此图显示了位置和定位点

当示例运行时,PositionBorderWidthBorderColor 的动画效果如下面的屏幕截图所示:

运行示例时,Position、BorderWidth 和 BorderColor 动画如下所示

显式动画

除了隐式动画外,核心动画还包括各种从 CAAnimation 中继承的类,用于封装随后显式添加到图层的动画。 通过这些类可对动画进行精细控制,例如修改动画的起始值、对动画分组以及指定关键帧以允许非线性路径。

以下代码演示了显式动画的示例,该动画使用前面(在隐式动画部分中)所示的 CAKeyframeAnimation 图层:

public override void ViewDidAppear (bool animated)
{
  base.ViewDidAppear (animated);

  // get the initial value to start the animation from
  CGPoint fromPt = layer.Position;

  /* set the position to coincide with the final animation value
  to prevent it from snapping back to the starting position
  after the animation completes*/
  layer.Position = new CGPoint (200, 300);

  // create a path for the animation to follow
  CGPath path = new CGPath ();
  path.AddLines (new CGPoint[] { fromPt, new CGPoint (50, 300), new CGPoint (200, 50), new CGPoint (200, 300) });

  // create a keyframe animation for the position using the path
  CAKeyFrameAnimation animPosition = (CAKeyFrameAnimation)CAKeyFrameAnimation.FromKeyPath ("position");
  animPosition.Path = path;
  animPosition.Duration = 2;

  // add the animation to the layer.
  /* the "position" key is used to overwrite the implicit animation created
  when the layer positino is set above*/
  layer.AddAnimation (animPosition, "position");
}

此代码通过创建一个用于定义关键帧动画的路径来更改图层的 Position。 请注意,图层的 Position 设置为动画中 Position 的最终值。 如果没有此值,图层会突然回到动画之前的 Position,因为动画只更改了表示值,没有更改实际的模型值。 通过将模型值设置为动画的最终值,图层将在动画结束时保留在原位。

以下屏幕截图显示了包含通过指定路径动画的图像的图层:

此屏幕截图显示了包含通过指定路径进行动画处理的图像的层

总结

本文介绍了通过核心动画框架提供的动画功能。 我们了解了核心动画,展示它在 UIKit 中如何为动画提供支持,以及如何直接用于较低级别的动画控制。