iOS CAAnimation(三)核心动画基础

本文详细介绍了iOS中的Core Animation,它是一个用于动画化应用视图和其他视觉元素的图形渲染和动画基础设施。Core Animation自动处理大部分动画帧的绘制工作,提供高性能和流畅的动画。核心在于layer对象,用于管理内容和操作。文章探讨了layer的特性、如何为layer提供内容、调整其属性,并总结了layer的重要地位及其在动画中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

layer在核心动画中的地位:

先看一下这个经典的结构图,Core Animation的位置在UIKit/AppKit层之下。

Core Animation is a graphics rendering and animation infrastructure available on both iOS and OS X that you use to animate the views and other visual elements of your app. With Core Animation, most of the work required to draw each frame of an animation is done for you. All you have to do is configure a few animation parameters (such as the start and end points) and tell Core Animation to start. Core Animation does the rest, handing most of the actual drawing work off to the onboard graphics hardware to accelerate the rendering. This automatic graphics acceleration results in high frame rates and smooth animations without burdening the CPU and slowing down your app.

Core Animation is not a drawing system itself. It is an infrastructure for compositing and manipulating your app’s content in hardware. At the heart of this infrastructure are layer objects, which you use to manage and manipulate your content. A layer captures your content into a bitmap that can be manipulated easily by the graphics hardware. 

 

引用官方文档里对Core Animation的介绍来看。我们可以使用核心动画使我们app中的视觉元素动起来,而且大部分绘制工作已经为我们准备好了,我们只需要配置一些参数即可,其余部分将由Core Animation完成,它将大部分图形绘制工作交给图形硬件以加快渲染速度。这种自动图形加速功能可实现高帧率和流畅的动画效果,而不会给CPU造成负担并降低app的运行速度。  

Core Animation本身不是绘制系统,它是在硬件中合成和操作应用content的基础组件,而这个基础组件的核心是layer层,我们可以用layer来管理和操作content。layer会捕获content并生成位图(Bitmap),这样图形硬件就可以轻松地利用生成的位图(Bitmap)去做一些绘制工作。

Core Animation provides a general purpose system for animating views and other visual elements of your app. Core Animation is not a replacement for your app’s views. Instead, it is a technology that integrates with views to provide better performance and support for animating their content. It achieves this behavior by caching the contents of views into bitmaps that can be manipulated directly by the graphics hardware. In some cases, this caching behavior might require you to rethink how you present and manage your app’s content, but most of the time you use Core Animation without ever knowing it is there. In addition to caching view content, Core Animation also defines a way to specify arbitrary visual content, integrate that content with your views, and animate it along with everything else.

You use Core Animation to animate changes to your app’s views and visual objects. Most changes relate to modifying the properties of your visual objects. For example, you might use Core Animation to animate changes to a view’s position, size, or opacity. When you make such a change, Core Animation animates between the current value of the property and the new value you specify. You would typically not use Core Animation to replace the content of a view 60 times a second, such as in a cartoon. Instead, you use Core Animation to move a view’s content around the screen, fade that content in or out, apply arbitrary graphics transformations to the view, or change the view’s other visual attributes.

Core Animation通过将视图的内容缓存到可以由图形硬件直接操作的位图(Bitmap)中来实现此行为。

建议不要使用Core Animation进行每秒60次替换视图内容的工作,比如说卡通。而是使用Core Animation在屏幕上移动视图的内容,淡入或淡出该内容,对视图应用任意图形转换或更改视图的其他视觉属性。

既然说layer是核心内容,那我们来看看官方文档中关于layer的说法:

Layer objects are 2D surfaces organized in a 3D space and are at the heart of everything you do with Core Animation. Like views, layers manage information about the geometry, content, and visual attributes of their surfaces. Unlike views, layers do not define their own appearance. A layer merely manages the state information surrounding a bitmap. The bitmap itself can be the result of a view drawing itself or a fixed image that you specify. For this reason, the main layers you use in your app are considered to be model objects because they primarily manage data. This notion is important to remember because it affects the behavior of animations.

这里再次重申了layer是我们使用Core Animation的核心,layer管理着bitmap的状态信息。

Most layers do not do any actual drawing in your app. Instead, a layer captures the content your app provides and caches it in a bitmap, which is sometimes referred to as the backing store. When you subsequently change a property of the layer, all you are doing is changing the state information associated with the layer object. When a change triggers an animation, Core Animation passes the layer’s bitmap and state information to the graphics hardware, which does the work of rendering the bitmap using the new information, as shown in Figure 1-1. Manipulating the bitmap in hardware yields much faster animations than could be done in software.

大多数layer不会进行实际绘制工作,它们只是捕获当前应用的content并缓存为bitmap。

当我们改变layer的属性时,我们只是在改变layer的状态信息。

当改变触发了动画,CoreAnimation会将 layer缓存起来的bitmap 和 关联的状态信息 交给图形硬件,图形硬件会用新的状态信息对bitmap进行渲染。直接在硬件中对bitmap进行操作比在软件中完成同样的任务要快的多。

Because it manipulates a static bitmap, layer-based drawing differs significantly from more traditional view-based drawing techniques. With view-based drawing, changes to the view itself often result in a call to the view’s drawRect: method to redraw content using the new parameters. But drawing in this way is expensive because it is done using the CPU on the main thread. Core Animation avoids this expense by whenever possible by manipulating the cached bitmap in hardware to achieve the same or similar effects.

这里对比了基于layer层的绘制和基于view层的绘制的区别:

基于view层的绘制,改变view内容会调用drawRect:方法,这个方法会在主线程上进行,并且消耗CPU性能。

基于layer层的绘制,操作的是静态bitmap,Core Animation通过直接在硬件上操作缓存bitmap来实现同样或类似的效果,同时避免这样的浪费。

所以对性能有要求的时,尽量少用drawRect方法呀,会触发离屏渲染,还会占据额外的内存。

Layers make use of both point-based coordinate systems and unit coordinate systems to specify the placement of content. Which coordinate system is used depends on the type of information being conveyed. Point-based coordinates are used when specifying values that map directly to screen coordinates or must be specified relative to another layer, such as for the layer’s position property. Unit coordinates are used when the value should not be tied to screen coordinates because it is relative to some other value. For example, the layer’s anchorPoint property specifies a point relative to the bounds of the layer itself, which can change.

Among the most common uses for point-based coordinates is to specify the size and position of the layer, which you do using the layer’s bounds and position properties. The bounds defines the coordinate system of the layer itself and encompasses the layer’s size on the screen. The position property defines the location of the layer relative to its parent’s coordinate system. Although layers have a frame property, that property is actually derived from the values in the bounds and position properties and is used less frequently.

这里告诉了我们frame是起源于bounds和position,在上一篇我们也研究过了。

同时这里告诉我们在layer层应该使用position、bounds、anchorPoint而不是frame,他们之间的关系在第一篇也提到了。

layer层结构

接下来看一下layer层的结构:

An app using Core Animation has three sets of layer objects. Each set of layer objects has a different role in making the content of your app appear onscreen:

  • Objects in the model layer tree (or simply “layer tree”) are the ones your app interacts with the most. The objects in this tree are the model objects that store the target values for any animations. Whenever you change the property of a layer, you use one of these objects.

  • Objects in the presentation tree contain the in-flight values for any running animations. Whereas the layer tree objects contain the target values for an animation, the objects in the presentation tree reflect the current values as they appear onscreen. You should never modify the objects in this tree. Instead, you use these objects to read current animation values, perhaps to create a new animation starting at those values.

  • Objects in the render tree perform the actual animations and are private to Core Animation.

layer层有三个子层,每层负责着不同的工作:

1.模型树(model layer tree):负责存储动画数据。

2.呈现树(presentation tree):负责根据模型层中的动画数据将动画展示在屏幕上(是对模型树的copy)。建议我们不要修改该层上的对象,但是可以利用读到的状态数据去做其他事。

3.渲染树(render tree): 该层是CoreAnimation私有的,负责执行实际动画。

For every object in the layer tree, there is a matching object in the presentation and render trees, as shown in Figure 1-10. As was previously mentioned, apps primarily work with objects in the layer tree but may at times access objects in the presentation tree. Specifically, accessing the presentationLayer property of an object in the layer tree returns the corresponding object in the presentation tree. You might want to access that object to read the current value of a property that is in the middle of an animation.

这里告诉我们,在模型树中的每一个对象,呈现树和渲染树中都有一个与之匹配的对象。

app主要和模型树一起工作,但是当我们需要得到动画中间的某个状态时,我们需要去访问呈现树中的相关对象。

Important: You should access objects in the presentation tree only while an animation is in flight. While an animation is in progress, the presentation tree contains the layer values as they appear onscreen at that instant. This behavior differs from the layer tree, which always reflects the last value set by your code and is equivalent to the final state of the animation.

有一个重要提示:当动画正在进行中时,呈现树中的内容会反应屏幕上的真实状态,而在模型树中,总是会反应动画结束后的值。

In addition to the layers associated with your views, you can also create layer objects that do not have a corresponding view. You can embed these standalone layer objects inside of any other layer object in your app, including those that are associated with a view. You typically use standalone layer objects as part of a specific optimization path. For example, if you wanted to use the same image in multiple places, you could load the image once and associate it with multiple standalone layer objects and add those objects to the layer tree. Each layer then refers to the source image rather than trying to create its own copy of that image in memory.

 

不同类型的layer提供特殊的行为:

前面文章提到,我们重写一个View的+layerClass方法可以给该view重新指派一个layer,系统为我们提供了很多专属功能的layer,我们可以直接使用,使用专属的layer可以有更好的性能:

为layer层提供内容:

Layers are data objects that manage content provided by your app. A layer’s content consists of a bitmap containing the visual data you want to display. You can provide the content for that bitmap in one of three ways:

  • Assign an image object directly to the layer object’s contents property. (This technique is best for layer content that never, or rarely, changes.)

  • Assign a delegate object to the layer and let the delegate draw the layer’s content. (This technique is best for layer content that might change periodically and can be provided by an external object, such as a view.)

  • Define a layer subclass and override one of its drawing methods to provide the layer contents yourself. (This technique is appropriate if you have to create a custom layer subclass anyway or if you want to change the fundamental drawing behavior of the layer.)

layer会根据我们提供给它content的内容缓存成bitmap,那么我们想要展示什么内容就可以提供给layer的content,这里有三种方式给content赋值:

1.直接给layer的contents属性赋值;

Because a layer is just a container for managing a bitmap image, you can assign an image directly to the layer’s contents property. Assigning an image to the layer is easy and lets you specify the exact image you want to display onscreen. The layer uses the image object you provide directly and does not attempt to create its own copy of that image. This behavior can save memory in cases where your app uses the same image in multiple places.

The image you assign to a layer must be a CGImageRef type. (In OS X v10.6 and later, you can also assign an NSImage object.) When assigning images, remember to provide an image whose resolution matches the resolution of the native device. For devices with Retina displays, this might also require you to adjust the contentsScale property of the image. For information on using high-resolution content with your layers, see Working with High-Resolution Images.

///* An object providing the contents of the layer, typically a CGImageRef,
// * but may be something else. (For example, NSImage objects are
// * supported on Mac OS X 10.6 and later.) Default value is nil.
// * Animatable. */
@property(nullable, strong) id contents;

给layer的contents属性直接赋值,图片的格式必须是CGImageRef格式的(iOS平台),layer会直接使用我们提供的图片而不会创建图片的副本,当我们在多个地方使用同样的图片时可以节省内存。

2.分配一个代理对象,让代理对象来负责对layer的content进行绘制;

If the content of your layer changes dynamically, you can use a delegate object to provide and update that content when needed. At display time, the layer calls the methods of your delegate to provide the needed content:

  • If your delegate implements the displayLayer: method, that implementation is responsible for creating a bitmap and assigning it to the layer’s contents property.

  • If your delegate implements the drawLayer:inContext: method, Core Animation creates a bitmap, creates a graphics context to draw into that bitmap, and then calls your delegate method to fill the bitmap. All your delegate method has to do is draw into the provided graphics context.

The delegate object must implement either the displayLayer: or drawLayer:inContext: method. If the delegate implements both the displayLayer: and drawLayer:inContext: method, the layer calls only the displayLayer: method.

我们可以通过协议中的- (void)displayLayer:(CALayer *)layer 方法来给layer的contents直接赋值,这在第一篇的最后也提到了。

- (void)displayLayer:(CALayer *)layer {
    // Check the value of some state property
    if (self.displayYesImage) {
        // Display the Yes image
        layer.contents = [someHelperObject loadStateYesImage];
    }
    else {
        // Display the No image
        layer.contents = [someHelperObject loadStateNoImage];
    }
}

也可以通过- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx 方法来创建一个图形上下文绘制到CoreAnimation创建的bitmap上。

-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
    CGMutablePathRef thePath = CGPathCreateMutable();
    CGPathMoveToPoint(thePath,NULL,15.0f,15.f);
    CGPathAddCurveToPoint(thePath,
                          NULL,
                          15.f,250.0f,
                          295.0f,250.0f,
                          295.0f,15.0f);
    CGContextBeginPath(ctx);
    CGContextAddPath(ctx, thePath);
    CGContextSetLineWidth(ctx, 5);
    CGContextStrokePath(ctx);
    // Release the path
    CFRelease(thePath);
}

For layer-backed views with custom content, you should continue to override the view’s methods to do your drawing. A layer-backed view automatically makes itself the delegate of its layer and implements the needed delegate methods, and you should not change that configuration. Instead, you should implement your view’s drawRect: method to draw your content.

view自带一个layer,并且会自动设置代理和实现必要的协议方法,如果我们想要自定义view内容,我们应该重写view的drawRect:方法,在这个方法中绘制content(前面文章说到-(void)drawLayer: inContext:会调用view的drawRect方法)。

如果两个方法都实现了,则只会调用- (void)displayLayer:(CALayer *)layer;这个方法。

 3.自定义一个layer子类重写绘制方法来提供contents;

When subclassing, you can use either of the following techniques to draw your layer’s content:

  • Override the layer’s display method and use it to set the contents property of the layer directly.

  • Override the layer’s drawInContext: method and use it to draw into the provided graphics context.

Which method you override depends on how much control you need over the drawing process. The display method is the main entry point for updating the layer’s contents, so overriding that method puts you in complete control of the process. Overriding the display method also means that you are responsible for creating the CGImageRef to be assigned to the contents property. If you just want to draw content (or have your layer manage the drawing operation), you can override the drawInContext: method instead and let the layer create the backing store for you.

当我们用layer的子类时,我们可以选择重写两个方法中的一个,选择哪一个方法,取决于我们想要对绘制进程进行控制的程度,display:方法需要我们负责创建CGImageRef并分配给contents,这样我们就对整个过程完全控制了。如果我们只是想管理绘制操作,我们就可以重写drawInContext:方法,让layer为我们创建backing store。

调整layer的content:

1.contentsGravity

When you assign an image to the contents property of a layer, the layer’s contentsGravity property determines how that image is manipulated to fit the current bounds. By default, if an image is bigger or smaller than the current bounds, the layer object scales the image to fit within the available space. If the aspect ratio of the layer’s bounds is different than the aspect ratio of the image, this can cause the image to be distorted. You can use the contentsGravity property to ensure that your content is presented in the best way possible.

The values you can assign to the contentsGravity property are divided into two categories:

  • The position-based gravity constants allow you to pin your image to a particular edge or corner of the layer’s bounds rectangle without scaling the image.

  • The scaling-based gravity constants allow you to stretch the image using one of several options, some of which

  • preserve the aspect ratio and some of which do not.

///* A string defining how the contents of the layer is mapped into its
 //* bounds rect. Options are `center', `top', `bottom', `left',
 //* `right', `topLeft', `topRight', `bottomLeft', `bottomRight',
 //* `resize', `resizeAspect', `resizeAspectFill'. The default value is
 //* `resize'. Note that "bottom" always means "Minimum Y" and "top"
 //* always means "Maximum Y". */
@property(copy) CALayerContentsGravity contentsGravity;

这个属性控制了contents的根据bounds调整尺寸的方式。

///* Defines the scale factor applied to the contents of the layer. If
// * the physical size of the contents is '(w, h)' then the logical size
 //* (i.e. for contentsGravity calculations) is defined as '(w /
 //* contentsScale, h / contentsScale)'. Applies to both images provided
// * explicitly and content provided via -drawInContext: (i.e. if
// * contentsScale is two -drawInContext: will draw into a buffer twice
// * as large as the layer bounds). Defaults to one. Animatable. */
@property CGFloat contentsScale

2.contentsScale

Layers do not have any inherent knowledge of the resolution of the underlying device’s screen. A layer simply stores a pointer to your bitmap and displays it in the best way possible given the available pixels. If you assign an image to a layer’s contents property, you must tell Core Animation about the image’s resolution by setting the layer’s contentsScale property to an appropriate value. The default value of the property is 1.0, which is appropriate for images intended to be displayed on standard resolution screens. If your image is intended for a Retina display, set the value of this property to 2.0.

Changing the value of the contentsScale property is only necessary if you are assigning a bitmap to your layer directly. A layer-backed view in UIKit and AppKit automatically sets the scale factor of its layer to an appropriate value based on the screen resolution and the content managed by the view.

layer层不知道设备屏幕分辨率的任何信息,layer层只是存储指向位图(bitmap)的指针,并在给定可用像素的情况下以最佳方式显示它。如果将图像分配给图层的contents属性,则必须通过将图层的contentsScale属性设置为适当的值来告知Core Animation该图像的分辨率。该属性的默认值是1.0,这适用于打算在标准分辨率屏幕上显示的图像。如果您的图像打算用于Retina显示屏,请将此属性的值设置为2.0

3.cornerRadius

A layer can display a filled background and a stroked border in addition to its image-based contents. The background color is rendered behind the layer’s contents image and the border is rendered on top of that image, as shown in Figure 2-3. If the layer contains sublayers, they also appear underneath the border. Because the background color sits behind your image, that color shines through any transparent portions of your image.

layer中的背景颜色渲染在contents的背后,border渲染在contents的前面。

If you set your layer’s background color to an opaque color, consider setting the layer’s opaque property to YES. Doing so can improve performance when compositing the layer onscreen and eliminates the need for the layer’s backing store to manage an alpha channel. You must not mark a layer as opaque if it also has a nonzero corner radius, though.

因为View的opaque属性默认值是YES,而layer的opaque属性默认是NO,所以当我们给layer使用不透明背景时,我们最好将layer的opaque设置为YES,这样可以提高在屏幕上合成图层的性能,无需在layer的backing store中管理透明通道,但是如果layer设置了corner radius则标记layer opaque为YES也无效。

如果设置了layer的corner radius,则会在显示之前就四个角的圆角设置好。(这样会触发离屏渲染)。

4.shadows

The CALayer class includes several properties for configuring a shadow effect. A shadow adds depth to the layer by making it appear as if it is floating above its underlying content. This is another type of visual adornment that you might find useful in specific situations for your app. With layers, you can control the shadow’s color, placement relative to the layer’s content, opacity, and shape.

The opacity value for layer shadows is set to 0 by default, which effectively hides the shadow. Changing the opacity to a nonzero value causes Core Animation to draw the shadow. Because shadows are positioned directly under the layer by default, you might also need to change the shadow’s offset before you can see it. I

    view.layer.shadowOpacity = 0.5;
    view.layer.shadowColor = [UIColor blueColor].CGColor;
    view.layer.shadowOffset = CGSizeMake(10, 10);

我们可以通过layer的这三个属性来给layer添加阴影效果,shadowOpacity默认值是0,当我们设置其为非0值时,就会启用CoreAnimation开始绘制阴影,需要注意的是如果layer的maskToBounds如果设置为YES,就会将阴影部分剪裁掉。

When adding shadows to a layer, the shadow is part of the layer’s content but actually extends outside the layer’s bounds rectangle. As a result, if you enable the masksToBounds property for the layer, the shadow effect is clipped around the edges. If your layer contains any transparent content, this can cause an odd effect where the portion of the shadow directly under your layer is still visible but the part extending beyond your layer is not. If you want a shadow but also want to use bounds masking, you use two layers instead of one. Apply the mask to the layer containing your content and then embed that layer inside a second layer of the exact same size that has the shadow effect enabled.

如果我们同时想要maskToBounds和阴影效果,可以使用两个layer层。

在layerA层上设置好阴影,在同样大小的layerB层上设置好maskToBounds,然后将layerB添加在layerA上即可。

    layerA.frame = CGRectMake(100, 200, 200, 200);
    layerA.backgroundColor = [UIColor greenColor].CGColor;
    layerA.shadowOpacity = 0.5;
    layerA.shadowColor = [UIColor blueColor].CGColor;
    layerA.shadowOffset = CGSizeMake(10, 10);
    
    layerB.backgroundColor = [UIColor clearColor];
    layerB.layer.borderWidth = 3.0;
    layerB.layer.cornerRadius = 5.0;
    layerB.layer.masksToBounds = YES;

    [layerA addSublayer:layerB];
    [self.view.layer addSublayer:layerA];

总结:

这篇文章我们一共总结了layer在核心动画中的地位、layer的结构、如何给layer的contents赋值、layer的一些可控制属性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值