iOS CAAnimation(三)核心動畫基礎

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的一些可控制屬性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章