核心動畫(Core Animation)是iOS和OS X上圖形渲染和動畫的基礎設施焊唬,用于為應用程序的視圖和其他視覺元素設置動畫故痊。核心動畫本身不是繪圖系統(tǒng)顶瞳。它是用于在硬件中合成和操縱應用程序內(nèi)容的基礎設施。這種基礎設施的核心是層(layer)對象慨菱,用于管理和操縱您的內(nèi)容甜孤。一個圖層將您的內(nèi)容捕獲到位圖中,這些位圖可以通過圖形硬件輕松操作。在大多數(shù)應用程序中,圖層用作管理視圖內(nèi)容的一種方式,但也可以根據(jù)需要創(chuàng)建獨立圖層烤黍。
?基于layer層的繪圖與傳統(tǒng)的基于視圖的繪圖技術(shù)有很大的不同规哲。使用基于視圖的繪圖,對視圖本身的更改通常會導致調(diào)用視圖的drawRect:
方法以使用新參數(shù)重新繪制內(nèi)容。但是以這種方式繪制是昂貴的吕粹,因為它是使用主線程上的CPU完成的。Core Animation可以通過在硬件中操作緩存的位圖來實現(xiàn)相同或相似的效果,從而避免這種代價母市。
一浑槽、關于Layer的幾個重要屬性及方法
CALayer類中屬性和方法有很多,大多數(shù)和使用View的方式相同,在此不多贅述煮落,具體可以自行查看CALayer.h文件敞峭。下面主要介紹一下比較重要的屬性方法。
1蝉仇、屬性:
- position(CGPoint):位置旋讹,與view中的center差不多,默認情況是layer的中心轿衔。但是會受錨點(anchorPoint)影響而改變沉迹。
- zPosition(CGFloat):layer的position在父類(super layer)上的Z軸分量,默認為零害驹。
- anchorPoint(CGPoint):錨點鞭呕,使用的是以自身為參考的單位坐標系,默認為(0.5裙秋, 0.5)琅拌。當操縱層的屬性position或transform屬性時缨伊,錨點的影響最為明顯摘刑。position屬性總是相對于圖層的錨點指定,并且對于應用于圖層的任何轉(zhuǎn)換也會相對于錨點發(fā)生刻坊。
anchorPointZ(CGFloat):layer的anchorPoint在Z軸上的分量枷恕,默認為零。
-
transform(CATransform3D):作用于layer的3D變換谭胚。默認是單位矩陣CATransform3DIdentity徐块。
- 每個layer都有兩個變換矩陣屬性(
transform、sublayerTransform
)灾而,可以使用它們來操縱圖層及其內(nèi)容胡控。CALayer的transform屬性指定要既適用于層和其嵌入式子層的變換。通常旁趟,當要修改圖層本身時昼激,可以使用此屬性。sublayerTransform屬性定義了僅適用于子層的附加變換锡搜,最常用于向場景內(nèi)容添加透視視覺效果橙困。 - 因為Core Animation值可以在三維中指定,所以每個坐標點有四個值必須乘以四乘四個矩陣(見下圖)耕餐。核心動畫提供了一套全面的功能凡傅,用于創(chuàng)建縮放,轉(zhuǎn)換和旋轉(zhuǎn)矩陣以及進行矩陣比較(具體可見CATransform3D.h文件肠缔,里面方法的使用可以參考CATransform3D -> 3D變換
)夏跷。除了使用函數(shù)操縱變換之外哼转,Core Animation擴展了鍵值編碼支持,允許使用關鍵路徑修改變換槽华。
- 每個layer都有兩個變換矩陣屬性(
sublayerTransform(CATransform3D):當將內(nèi)容呈現(xiàn)到接收器的輸出中時释簿,將3D變換應用于“sublayers”數(shù)組的每個成員。 通常用作投影矩陣以將透視和其他觀看效果添加到模型硼莽。默認是單位矩陣庶溶。
sublayers(NSArray<CALayer >):子layer的集合。
masksToBounds(BOOL):是否沿著邊界裁剪懂鸵。
-
contents(id):layer的內(nèi)容由包含要顯示的視覺數(shù)據(jù)的位圖組成偏螺。您可以通過以下三種方式之一提供該位圖的內(nèi)容:
- 直接將圖像對象直接分配給圖層對象的
contents
屬性。(這種技術(shù)最適合從未或很少改變的圖層內(nèi)容匆光。注意必須為CGImageRef
類型) - 將一個委托對象分配給圖層套像,讓代理繪制圖層的內(nèi)容。(此技術(shù)最適合可能會周期性更改并可由外部對象(如視圖)提供的圖層內(nèi)容终息。)
- 定義一個layer的子類并覆蓋其繪圖方法夺巩,自己提供圖層內(nèi)容。(如果您要創(chuàng)建自定義層子類周崭,或者如果要更改圖層的基本繪圖行為柳譬,則此技術(shù)是適用的。)
- 直接將圖像對象直接分配給圖層對象的
contentsRect(CGRect):默認是{0续镇, 0美澳, 1,1}通俗來說就是將layer看成單位矩形摸航,取其中的一部分制跟。所有參數(shù)一般是[0-1]。
-
contentsGravity(NSString):決定分配給
contents
屬性圖像一什么方式呈現(xiàn)酱虎,一般分配給該屬性的值分為兩類:- 基于位置的重力常數(shù)允許將圖像定向到圖層邊界矩形的特定邊緣或角落雨膨,而 不縮放圖像。具體見 PS-1读串。
- 基于縮放的重力常數(shù)允許來拉伸圖像聊记。具體見 PS-2。
contentsScale(CGFloat):默認值是1.0爹土,屬性主要作用是適應Retina屏與非Retina屏的甥雕,如果是Retina屏則設置為2.0。一般可以這樣設置
[UIScreen mainScreen].scale
胀茵。contentsCenter(CGFloat):默認是{0社露, 0, 1琼娘,1}峭弟,在contentsRect的基礎上確定縮放比例附鸽,主要決定因子是前兩個元素,決定了原圖縱橫/現(xiàn)圖縱橫的比例瞒瘸,如:0.2則原圖被放大五倍?辣浮!情臭!
opaque(BOOL):由
-drawInContext
提供的圖層內(nèi)容是完全不透明的省撑。 默認為NO。opacity(float):layer的透明度俯在,默認是1竟秫。
還有一些常見的屬性:如frame
, hidden
, backgroundColor
, cornerRadius
, borderWidth
, borderColor
, shadowColor
,shadowOpacity
, shadowOffset
, shadowRadius
, shadowPath
等。
除此之外跷乐,核心動畫對它所屬的CAAnimation和CALayer類擴展了NSKeyValueCoding的協(xié)議 --- 必須使用setValue:forKeyPath:
和valueForKeyPath:
方法來設置和獲取這些字段肥败,并增加了關鍵路徑對CGPoint
,CGRect
愕提,CGSize
馒稍,和CATransform3D
類型的支持(表-1)。所以可以直接使用KVC對其 屬性 進行賦取值浅侨,在這里需要特別注意對transform等結(jié)構(gòu)體來使用keyPath的情形(表-2)
表-1
C類 | 包裝類 |
---|---|
CGPoint /CGSize/CGRect/CATransform3D | NSValue |
表-2
字段路徑 | 包裝類及描述 |
---|---|
transform.translation | NSValue(包含CGSize數(shù)據(jù)類型) , 在x和y軸上平移的量 |
transform. translation.x | NSNunber , 沿x軸平移 |
transform. translation.y | NSNunber , 沿y軸平移 |
transform. translation.z | NSNunber , 沿z軸平移 |
transform. scale | NSNunber , xyz三個比例因子的平均值 |
transform. scale.x | NSNunber , 沿x軸縮放 |
transform. scale.y | NSNunber , 沿y軸縮放 |
transform. scale.z | NSNunber , 沿z軸縮放 |
transform. rotation | NSNunber , 沿z軸旋轉(zhuǎn)的弧度纽谒,與transform. rotation.z相同 |
transform. rotation.x | NSNunber , 沿x軸旋轉(zhuǎn)的弧度 |
transform. rotation.y | NSNunber , 沿y軸旋轉(zhuǎn)的弧度 |
transform. rotation.z | NSNunber , 沿z軸旋轉(zhuǎn)的弧度 |
*** | *** |
position | NSValue |
position.x | NSNunber |
position.y | NSNunber |
*** | *** |
bounds/frame | NSValue |
bounds.origin | 同position |
bounds. size | NSValue |
bounds. size.width | NSNumber |
bounds. size.height | NSNumber |
PS-1:基于位置的重力常數(shù):
CA_EXTERN NSString * const kCAGravityCenter
CA_EXTERN NSString * const kCAGravityTop
CA_EXTERN NSString * const kCAGravityBottom
CA_EXTERN NSString * const kCAGravityLeft
CA_EXTERN NSString * const kCAGravityRight
CA_EXTERN NSString * const kCAGravityTopLeft
CA_EXTERN NSString * const kCAGravityTopRight
CA_EXTERN NSString * const kCAGravityBottomLeft
CA_EXTERN NSString * const kCAGravityBottomRight
PS-2:基于縮放的重力常數(shù):
CA_EXTERN NSString * const kCAGravityResize
CA_EXTERN NSString * const kCAGravityResizeAspect
CA_EXTERN NSString * const kCAGravityResizeAspectFill
2、方法:
+ (nullable id)defaultValueForKey:(NSString *)key;
:在使用KVC時為鍵提供默認值仗颈。一般進行復寫來覆蓋原有方法進行默認值設置佛舱。- (void)display;
:重新加載圖層的內(nèi)容。 用法:如果實現(xiàn)了委托方法挨决,默認會調(diào)用displayLayer:委托方法。否則订歪,display方法會調(diào)用drawInContext方法脖祈,然后更新圖層的“contents”屬性。但是一般不主動調(diào)用刷晋!- (void)setNeedsDisplay;
盖高、- (void)setNeedsDisplayInRect:(CGRect)r;
:與上面的方法差不多,但是可以主動調(diào)用眼虱。如果設置了rect喻奥,則只有該層的該區(qū)域無效。- (void)displayIfNeeded;
:繪圖系統(tǒng)在需要時自動調(diào)用捏悬,如果已經(jīng)調(diào)用了setNeedsDisplay撞蚕,該方法無效。- (void)drawInContext:(CGContextRef)ctx;
默認的display方法會創(chuàng)建一個視圖圖形上下文并將其傳遞給drawInContext:方法,與[UIView drawRect:]方法相似过牙。
代理方法:
- (void)displayLayer:(CALayer *)layer;
如果實現(xiàn)了代理同時定義了此方法則甥厦,-display方法會調(diào)用此方法纺铭,該實現(xiàn)負責創(chuàng)建位圖并將其分配給圖層的contents屬性。- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)cox;
-drawInContext調(diào)用此方法刀疙,創(chuàng)建一個圖形上下文來繪制該位圖舶赔,然后調(diào)用委托方法來填充位圖。如果-displayLayer: 方法存在谦秧,則該方法不調(diào)用竟纳,無效。- (void)layerWillDraw:(CALayer *)layer
:新加的方法疚鲤,如果定義蚁袭,則由-display方法的默認實現(xiàn)調(diào)用。 允許代理在-drawLayer之前配置影響內(nèi)容的任何圖層狀態(tài)石咬。同樣揩悄,如果-displayLayer: 方法存在,則該方法不調(diào)用鬼悠,無效删性。
所以綜以上方法總結(jié)layer方法響應鏈有兩種:
- [layer setNeedDisplay] / [layer displayIfNeed] -> [layer display] -> [layerDelegate displayLayer:] 。
- [layer setNeedDisplay] / [layer displayIfNeed] -> [layer display] -> [layer drawInContext:] -> [layerDelegate drawLayer: inContext:]焕窝。
關于動畫的方法:
(void)addAnimation:(CAAnimation *)anim forKey:(nullable NSString *)key;
(void)removeAllAnimations;
(void)removeAnimationForKey:(NSString *)key;
示例代碼:
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
_blueLayer = [CALayer layer];
[_blueLayer setValue:(__bridge id)[UIColor blueColor].CGColor forKeyPath:@"backgroundColor"];
[_blueLayer setValue:[NSValue valueWithCGPoint:CGPointZero] forKeyPath:@"anchorPoint"];
[_blueLayer setValue:[NSValue valueWithCGRect:CGRectMake(0, 0, 1, 1)] forKeyPath:@"contentsRect"];
[_blueLayer setValue:(__bridge id)[[UIImage imageNamed:@"boy"] CGImage] forKeyPath:@"contents"];
[_blueLayer setValue:[NSValue valueWithCGRect:CGRectMake(50, 100, 200, 350)] forKeyPath:@"frame"];
_blueLayer.delegate = self;
[self.view.layer addSublayer:_blueLayer];
}
- (void)displayLayer:(CALayer *)layer
{
if (once) {
[_blueLayer setValue:[NSValue valueWithCGRect:CGRectMake(0, 0, 1, 0.7)] forKeyPath:@"contentsRect"];
[_blueLayer setValue:(__bridge id)[[UIImage imageNamed:@"boy"] CGImage] forKeyPath:@"contents"];
}else{
[_blueLayer setValue:[NSValue valueWithCGRect:CGRectMake(0, 0, 1, 1)] forKeyPath:@"contentsRect"];
[_blueLayer setValue:(__bridge id)[[UIImage imageNamed:@"girl"] CGImage] forKeyPath:@"contents"];
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[_blueLayer setValue:kCAGravityResizeAspect forKeyPath:@"contentsGravity"];
if (once) {
once = NO;
[UIView animateWithDuration:10.0 animations:^{
[_blueLayer setValue:[NSNumber numberWithFloat:100] forKeyPath:@"transform.translation.x"];
[_blueLayer setValue:[NSNumber numberWithFloat:100] forKeyPath:@"transform.translation.y"];
}];
}else{
[UIView animateWithDuration:10.0 animations:^{
[_blueLayer setValue:[NSValue valueWithCATransform3D:CATransform3DIdentity] forKeyPath:@"transform"];
}];
once = YES;
}
[_blueLayer setNeedsDisplay];
}
- (void)dealloc
{
// 在這里代理一定要置空蹬挺!否則控制器無法釋放
_blueLayer.delegate = nil;
}