上篇提到CoreAnimation是在圖形層之上,我們先看下CoreAnimation在框架中的位置:
CoreAnimation屬于QuartzCore框架,Quartz原本是macOS的Darwin核心之上的繪圖技術。在iOS中,我們所看到的視圖UIView是通過QuartzCore中的CALayer顯示出來的,我們討論的動畫效果也是加在這個CALayer上的。
圖層類是CoreAnimation的基礎,它提供了一套抽象概念。CALayer是整個圖層類的基礎,它是所有核心動畫圖層類的父類。
本篇主要談談CALayer(圖層類)和CAAnimation(動畫類)的內容和類關系,以及它們實現的一個重要的協議CAMediaTiming。
1.CALayer
為什么UIView要加一層Layer來負責顯示呢?我們知道QuartzCore是跨iOS和macOS平臺的,而UIView屬于UIKit是iOS開發使用的,在macOS中對應AppKit里的NSView。這是因為macOS是基于鼠標指針操作的系統,與iOS的多點觸控有本質的區別。雖然iOS在交互上與macOS有所不同,但在顯示層面卻可以使用同一套技術。
// UIView中與layer相關的屬性方法
@interface UIView : UIResponder <NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace, UIFocusItem, CALayerDelegate>
/* layer的class,默認為CALayer,可以用自定義的layer */
#if UIKIT_DEFINE_AS_PROPERTIES
@property(class, nonatomic, readonly) Class layerClass; // default is [CALayer class]. Used when creating the underlying layer for the view.
#else
+ (Class)layerClass; // default is [CALayer class]. Used when creating the underlying layer for the view.
#endif
/* view的leyer,view是layer的代理 */
@property(nonatomic,readonly,strong) CALayer *layer; // returns view's layer. Will always return a non-nil value. view is layer's delegate
可以想象下我們看到的view實際上都是它的layer,我們先通過CALayer中幾何相關的屬性來認識下它:
- bounds:圖層的bounds是一個CGRect的值,指定圖層的大小(bounds.size)和原點(bounds.origin)
/* The bounds of the layer. Defaults to CGRectZero. Animatable. */
@property CGRect bounds;
- position:指定圖層的位置(相對于父圖層而言)
/* The position in the superlayer that the anchor point of the layer's
* bounds rect is aligned to. Defaults to the zero point. Animatable. */
@property CGPoint position;
- anchorPoint:錨點指定了position在當前圖層中的位置,坐標范圍0~1。position點的值是相對于父圖層的,而這個position到底位于當前圖層的什么地方,是由錨點決定的。(默認在圖層的中心,即錨點為(0.5,0.5) )
/* Defines the anchor point of the layer's bounds rect, as a point in
* normalized layer coordinates - '(0, 0)' is the bottom left corner of
* the bounds rect, '(1, 1)' is the top right corner. Defaults to
* '(0.5, 0.5)', i.e. the center of the bounds rect. Animatable. */
@property CGPoint anchorPoint;
- transform:指定圖層的幾何變換,類型為上篇說過的CATransform3D
/* A transform applied to the layer relative to the anchor point of its
* bounds rect. Defaults to the identity transform. Animatable. */
@property CATransform3D transform;
這些屬性的注釋最后都有一句Animatable,就是說我們可以通過改變這些屬性來實現動畫。默認地,我們修改這些屬性都會導致圖層從舊值動畫顯示為新值,稱為隱式動畫。(注意,修改UIView自帶的layer(root layer)是沒有隱式動畫的)
還有一個屬性比較特殊,那就是layer的frame:
/* Unlike NSView, each Layer in the hierarchy has an implicit frame
* rectangle, a function of the `position', `bounds', `anchorPoint',
* and `transform' properties. When setting the frame the `position'
* and `bounds.size' are changed to match the given frame. */
@property CGRect frame;
注意到frame的注釋里面是沒有Animatable的。事實上,我們可以理解為圖層的frame并不是一個真實的屬性:當我們讀取frame時,會根據圖層position、bounds、anchorPoint和transform的值計算出它的frame;而當我們設置frame時,圖層會根據anchorPoint改變position和bounds。也就是說frame本身并沒有被保存。
圖層不但給自己提供可視化的內容和管理動畫,而且充當了其他圖層的容器類,構建圖層層次結構
圖層樹類似于UIView的層次結構,一個view實例擁有父視圖(superView)和子視圖(subView);同樣一個layer也有父圖層(superLayer)和子圖層(subLayer)。我們可以直接在view的layer上添加子layer達到一些顯示效果,但這些單獨的layer無法像UIView那樣進行交互響應。
2.CAAnimation
CALayer提供以下方法來管理動畫:
- (void)addAnimation:(CAAnimation *)anim forKey:(nullable NSString *)key;
- (void)removeAllAnimations;
- (void)removeAnimationForKey:(NSString *)key;
- (nullable NSArray<NSString *> *)animationKeys;
- (nullable CAAnimation *)animationForKey:(NSString *)key;
CAAnimation是動畫基類,我們常用的CABasicAnimation和CAKeyframeAnimation都繼承于CAPropertyAnimation即屬性動畫。屬性動畫通過改變layer的可動畫屬性(位置、大小等)實現動畫效果。CABasicAnimation可以看做有兩個關鍵幀的CAKeyframeAnimation,通過插值形成一條通過各關鍵幀的動畫路徑。但CABasicAnimation更加靈活一些:
@interface CABasicAnimation : CAPropertyAnimation
/* 我們可以通過下面三個值來規定CABasicAnimation的動畫起止狀態
* 這三個屬性都是可選的,通常給定其中一個或者兩個,以下是官方建議的使用方式
* * 給定fromValue和toValue,將在兩者之間進行插值 *
* * 給定fromValue和byValue,將在fromValue和fromValue+byValue之間插值 *
* * 給定byValue和toValue,將在toValue-byValue和toValue之間插值 *
* * 僅給定fromValue,將在fromValue和當前值之間插值 *
* * 僅給定toValue,將在當前值和toValue之間插值 *
* * 僅給定byValue,將在當前值和當前值+byValue之間插值 * */
@property(nullable, strong) id fromValue;
@property(nullable, strong) id toValue;
@property(nullable, strong) id byValue;
@end
在CAKeyframeAnimation中,除了給定各關鍵幀之外還可以指定關鍵幀之間的時間和時間函數:
@interface CAKeyframeAnimation : CAPropertyAnimation
@property(nullable, copy) NSArray *values;
@property(nullable, copy) NSArray<NSNumber *> *keyTimes;
/* 時間函數有線性、淡入、淡出等簡單效果,還可以指定一條三次貝塞爾曲線 */
@property(nullable, copy) NSArray<CAMediaTimingFunction *> *timingFunctions;
@end
到這我們已經能夠感覺到,所謂動畫實際上就是在不同的時間顯示不同畫面,時間在走進而形成連續變化的效果。所以,動畫的關鍵就是對時間的控制。
3.CAMediaTiming
CAMediaTiming是CoreAnimation中一個非常重要的協議,CALayer和CAAnimation都實現了它來對時間進行管理。協議定義了8個屬性,通過它們來控制時間,這些屬性大都見名知意:
@protocol CAMediaTiming
@property CFTimeInterval beginTime;
@property CFTimeInterval duration;
@property float speed;
/* timeOffset時間的偏移量,用它可以實現動畫的暫停、繼續等效果 */
@property CFTimeInterval timeOffset;
@property float repeatCount;
@property CFTimeInterval repeatDuration;
/* autoreverses為true時時間結束后會原路返回,默認為false */
@property BOOL autoreverses;
/* fillMode填充模式,有4種,見下 */
@property(copy) NSString *fillMode;
@end
NSString * const kCAFillModeForwards; // 向前填充,結束后保持狀態
NSString * const kCAFillModeBackwards; // 向后填充,開始之前維持開始狀態
NSString * const kCAFillModeBoth; // 前后填充,同時保持前后狀態
NSString * const kCAFillModeRemoved; // 無填充,結束后移除,fillMode默認為這個值
下面這張圖形象的說明了這些屬性是如何靈活的進行動畫時間控制的:
這張圖的出處,以及有關CAMediaTiming的時間控制,可以參考Controlling Animation Timing和iOS CoreAnimation專題——原理篇(四)動畫時間控制,里面介紹的非常詳細。需要注意的是,CALayer也實現了CAMediaTiming協議,也就是說如果我們將layer的speed設置為2,那么加到這個layer上的動畫都會以兩倍速執行。
本篇從圖層、動畫和時間控制的關系上簡單認識了CALayer、屬性動畫和動畫時間控制,了解屬性動畫是根據時間在各關鍵幀之間進行插值,隨時間連續改變layer的某動畫屬性來實現的。
參考資料
對CoreGraphics和QuartzCore的理解
Quartz 2D 繪圖技術
Core Animation基礎介紹、簡單使用CALayer以及多種動畫效果
通過CALayer讓你的APP動起來
Controlling Animation Timing
iOS CoreAnimation專題——原理篇(四)動畫時間控制