上篇提到CoreAnimation是在圖形層之上捌刮,我們先看下CoreAnimation在框架中的位置:
CoreAnimation屬于QuartzCore框架平窘,Quartz原本是macOS的Darwin核心之上的繪圖技術(shù)。在iOS中,我們所看到的視圖UIView是通過(guò)QuartzCore中的CALayer顯示出來(lái)的兰珍,我們討論的動(dòng)畫(huà)效果也是加在這個(gè)CALayer上的锉矢。
圖層類是CoreAnimation的基礎(chǔ)梯嗽,它提供了一套抽象概念。CALayer是整個(gè)圖層類的基礎(chǔ)沽损,它是所有核心動(dòng)畫(huà)圖層類的父類慷荔。
本篇主要談?wù)凜ALayer(圖層類)和CAAnimation(動(dòng)畫(huà)類)的內(nèi)容和類關(guān)系,以及它們實(shí)現(xiàn)的一個(gè)重要的協(xié)議CAMediaTiming缠俺。
1.CALayer
為什么UIView要加一層Layer來(lái)負(fù)責(zé)顯示呢显晶?我們知道QuartzCore是跨iOS和macOS平臺(tái)的,而UIView屬于UIKit是iOS開(kāi)發(fā)使用的壹士,在macOS中對(duì)應(yīng)AppKit里的NSView磷雇。這是因?yàn)閙acOS是基于鼠標(biāo)指針操作的系統(tǒng),與iOS的多點(diǎn)觸控有本質(zhì)的區(qū)別躏救。雖然iOS在交互上與macOS有所不同唯笙,但在顯示層面卻可以使用同一套技術(shù)螟蒸。
// UIView中與layer相關(guān)的屬性方法
@interface UIView : UIResponder <NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace, UIFocusItem, CALayerDelegate>
/* layer的class,默認(rèn)為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實(shí)際上都是它的layer,我們先通過(guò)CALayer中幾何相關(guān)的屬性來(lái)認(rèn)識(shí)下它:
- bounds:圖層的bounds是一個(gè)CGRect的值苞慢,指定圖層的大小(bounds.size)和原點(diǎn)(bounds.origin)
/* The bounds of the layer. Defaults to CGRectZero. Animatable. */
@property CGRect bounds;
- position:指定圖層的位置(相對(duì)于父圖層而言)
/* 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:錨點(diǎn)指定了position在當(dāng)前圖層中的位置诵原,坐標(biāo)范圍0~1。position點(diǎn)的值是相對(duì)于父圖層的挽放,而這個(gè)position到底位于當(dāng)前圖層的什么地方绍赛,是由錨點(diǎn)決定的。(默認(rèn)在圖層的中心辑畦,即錨點(diǎn)為(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:指定圖層的幾何變換吗蚌,類型為上篇說(shuō)過(guò)的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,就是說(shuō)我們可以通過(guò)改變這些屬性來(lái)實(shí)現(xiàn)動(dòng)畫(huà)纯出。默認(rèn)地蚯妇,我們修改這些屬性都會(huì)導(dǎo)致圖層從舊值動(dòng)畫(huà)顯示為新值,稱為隱式動(dòng)畫(huà)暂筝。(注意箩言,修改UIView自帶的layer(root layer)是沒(méi)有隱式動(dòng)畫(huà)的)
還有一個(gè)屬性比較特殊,那就是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的注釋里面是沒(méi)有Animatable的乖杠。事實(shí)上分扎,我們可以理解為圖層的frame并不是一個(gè)真實(shí)的屬性:當(dāng)我們讀取frame時(shí),會(huì)根據(jù)圖層position胧洒、bounds畏吓、anchorPoint和transform的值計(jì)算出它的frame;而當(dāng)我們?cè)O(shè)置frame時(shí)卫漫,圖層會(huì)根據(jù)anchorPoint改變position和bounds菲饼。也就是說(shuō)frame本身并沒(méi)有被保存。
圖層不但給自己提供可視化的內(nèi)容和管理動(dòng)畫(huà)列赎,而且充當(dāng)了其他圖層的容器類宏悦,構(gòu)建圖層層次結(jié)構(gòu)
圖層樹(shù)類似于UIView的層次結(jié)構(gòu),一個(gè)view實(shí)例擁有父視圖(superView)和子視圖(subView)包吝;同樣一個(gè)layer也有父圖層(superLayer)和子圖層(subLayer)饼煞。我們可以直接在view的layer上添加子layer達(dá)到一些顯示效果,但這些單獨(dú)的layer無(wú)法像UIView那樣進(jìn)行交互響應(yīng)诗越。
2.CAAnimation
CALayer提供以下方法來(lái)管理動(dòng)畫(huà):
- (void)addAnimation:(CAAnimation *)anim forKey:(nullable NSString *)key;
- (void)removeAllAnimations;
- (void)removeAnimationForKey:(NSString *)key;
- (nullable NSArray<NSString *> *)animationKeys;
- (nullable CAAnimation *)animationForKey:(NSString *)key;
CAAnimation是動(dòng)畫(huà)基類砖瞧,我們常用的CABasicAnimation和CAKeyframeAnimation都繼承于CAPropertyAnimation即屬性動(dòng)畫(huà)。屬性動(dòng)畫(huà)通過(guò)改變layer的可動(dòng)畫(huà)屬性(位置嚷狞、大小等)實(shí)現(xiàn)動(dòng)畫(huà)效果块促。CABasicAnimation可以看做有兩個(gè)關(guān)鍵幀的CAKeyframeAnimation荣堰,通過(guò)插值形成一條通過(guò)各關(guān)鍵幀的動(dòng)畫(huà)路徑。但CABasicAnimation更加靈活一些:
@interface CABasicAnimation : CAPropertyAnimation
/* 我們可以通過(guò)下面三個(gè)值來(lái)規(guī)定CABasicAnimation的動(dòng)畫(huà)起止?fàn)顟B(tài)
* 這三個(gè)屬性都是可選的竭翠,通常給定其中一個(gè)或者兩個(gè)振坚,以下是官方建議的使用方式
* * 給定fromValue和toValue,將在兩者之間進(jìn)行插值 *
* * 給定fromValue和byValue斋扰,將在fromValue和fromValue+byValue之間插值 *
* * 給定byValue和toValue渡八,將在toValue-byValue和toValue之間插值 *
* * 僅給定fromValue,將在fromValue和當(dāng)前值之間插值 *
* * 僅給定toValue褥实,將在當(dāng)前值和toValue之間插值 *
* * 僅給定byValue呀狼,將在當(dāng)前值和當(dāng)前值+byValue之間插值 * */
@property(nullable, strong) id fromValue;
@property(nullable, strong) id toValue;
@property(nullable, strong) id byValue;
@end
在CAKeyframeAnimation中裂允,除了給定各關(guān)鍵幀之外還可以指定關(guān)鍵幀之間的時(shí)間和時(shí)間函數(shù):
@interface CAKeyframeAnimation : CAPropertyAnimation
@property(nullable, copy) NSArray *values;
@property(nullable, copy) NSArray<NSNumber *> *keyTimes;
/* 時(shí)間函數(shù)有線性损离、淡入、淡出等簡(jiǎn)單效果绝编,還可以指定一條三次貝塞爾曲線 */
@property(nullable, copy) NSArray<CAMediaTimingFunction *> *timingFunctions;
@end
到這我們已經(jīng)能夠感覺(jué)到僻澎,所謂動(dòng)畫(huà)實(shí)際上就是在不同的時(shí)間顯示不同畫(huà)面,時(shí)間在走進(jìn)而形成連續(xù)變化的效果十饥。所以窟勃,動(dòng)畫(huà)的關(guān)鍵就是對(duì)時(shí)間的控制。
3.CAMediaTiming
CAMediaTiming是CoreAnimation中一個(gè)非常重要的協(xié)議渡紫,CALayer和CAAnimation都實(shí)現(xiàn)了它來(lái)對(duì)時(shí)間進(jìn)行管理足删。協(xié)議定義了8個(gè)屬性僻焚,通過(guò)它們來(lái)控制時(shí)間,這些屬性大都見(jiàn)名知意:
@protocol CAMediaTiming
@property CFTimeInterval beginTime;
@property CFTimeInterval duration;
@property float speed;
/* timeOffset時(shí)間的偏移量汁咏,用它可以實(shí)現(xiàn)動(dòng)畫(huà)的暫停、繼續(xù)等效果 */
@property CFTimeInterval timeOffset;
@property float repeatCount;
@property CFTimeInterval repeatDuration;
/* autoreverses為true時(shí)時(shí)間結(jié)束后會(huì)原路返回作媚,默認(rèn)為false */
@property BOOL autoreverses;
/* fillMode填充模式攘滩,有4種,見(jiàn)下 */
@property(copy) NSString *fillMode;
@end
NSString * const kCAFillModeForwards; // 向前填充纸泡,結(jié)束后保持狀態(tài)
NSString * const kCAFillModeBackwards; // 向后填充漂问,開(kāi)始之前維持開(kāi)始狀態(tài)
NSString * const kCAFillModeBoth; // 前后填充,同時(shí)保持前后狀態(tài)
NSString * const kCAFillModeRemoved; // 無(wú)填充女揭,結(jié)束后移除蚤假,fillMode默認(rèn)為這個(gè)值
下面這張圖形象的說(shuō)明了這些屬性是如何靈活的進(jìn)行動(dòng)畫(huà)時(shí)間控制的:
這張圖的出處,以及有關(guān)CAMediaTiming的時(shí)間控制吧兔,可以參考Controlling Animation Timing和iOS CoreAnimation專題——原理篇(四)動(dòng)畫(huà)時(shí)間控制磷仰,里面介紹的非常詳細(xì)。需要注意的是掩驱,CALayer也實(shí)現(xiàn)了CAMediaTiming協(xié)議芒划,也就是說(shuō)如果我們將layer的speed設(shè)置為2冬竟,那么加到這個(gè)layer上的動(dòng)畫(huà)都會(huì)以兩倍速執(zhí)行。
本篇從圖層民逼、動(dòng)畫(huà)和時(shí)間控制的關(guān)系上簡(jiǎn)單認(rèn)識(shí)了CALayer泵殴、屬性動(dòng)畫(huà)和動(dòng)畫(huà)時(shí)間控制,了解屬性動(dòng)畫(huà)是根據(jù)時(shí)間在各關(guān)鍵幀之間進(jìn)行插值拼苍,隨時(shí)間連續(xù)改變layer的某動(dòng)畫(huà)屬性來(lái)實(shí)現(xiàn)的笑诅。
參考資料
對(duì)CoreGraphics和QuartzCore的理解
Quartz 2D 繪圖技術(shù)
Core Animation基礎(chǔ)介紹、簡(jiǎn)單使用CALayer以及多種動(dòng)畫(huà)效果
通過(guò)CALayer讓你的APP動(dòng)起來(lái)
Controlling Animation Timing
iOS CoreAnimation專題——原理篇(四)動(dòng)畫(huà)時(shí)間控制