參考文檔:https://pan.baidu.com/s/1HaQRu8c8bNfKTSbxF5__Sw
相同內(nèi)容網(wǎng)頁版:https://www.kancloud.cn/manual/ios/97798
Core Animation不單是用來做動畫的扁誓,實際上它是從Layer Kit這么一個不怎么和動畫有關(guān)的名字演變而來嘱么,所以做動畫只是Core Animation特性的冰山一角播急。
Core Animation是一個復(fù)合引擎踱讨,它的職責(zé)就是盡可能快地組合屏幕上不同的可視內(nèi)容咏瑟,這個內(nèi)容是被分解成獨立的圖層捣卤,存儲在一個叫做視圖樹的體系之中祠挫。于是這個樹形成了UIKit以及在iOS應(yīng)用程序當(dāng)中你所能在屏幕上看見的一切的基礎(chǔ)班利。
CALayer
CALayer類在概念上和UIVIew類似侨把,同樣是一些被層級關(guān)系樹管理的矩形塊犀变,同樣也可以包含一些內(nèi)容(例如圖片、文本或者背景色)秋柄,管理子圖層的位置获枝。它們有一些方法和屬性用來做動畫和變換。和UIView最大的區(qū)別是CALayer不能處理用戶交互骇笔。
平行的層級關(guān)系
每一個UIView都有一個CALayer示例的圖層屬性省店,也就是所謂的backing layer,視圖的職責(zé)就是創(chuàng)建并管理這個圖層笨触,以確保當(dāng)子視圖在層級關(guān)系中添加或者被移除的時候懦傍,它們關(guān)聯(lián)的圖層也同樣對應(yīng)在層級關(guān)系樹中有相同的操作。
實際上這些背后關(guān)聯(lián)的圖層才是真正用來在屏幕上顯示和做動畫芦劣,UIView僅僅是對它的一個封裝粗俱,提供了一些iOS類似于處理觸摸的具體功能,以及Core Animation底層方法的高級接口虚吟。
之所以iOS提供兩個平行的層級關(guān)系寸认,在于要做職責(zé)分離,這樣也能避免很多重復(fù)的代碼串慰。實際上這里不是兩個層級關(guān)系偏塞,而是四個,視圖層級邦鲫、圖層樹灸叼、呈現(xiàn)樹和渲染樹。
圖層的能力
UIView沒有暴露出來的CALayer的功能:
- 陰影庆捺、圓角古今、帶顏色的邊框
- 3D變換
- 非矩形范圍
- 透明遮罩
- 多級非線性動畫
CALayer的contents屬性
這個屬性被定義為id,意味著它可以是任何類型的對象滔以,在這種情況下沧卢,你可以給contents屬性賦任何值,你的app仍然能夠編譯通過醉者。但是但狭,在實踐中,如果你給contents賦的不是CGImage撬即,那么你得到的圖層將是空白立磁。
實際上,真正賦值給contents的類型應(yīng)該是CGImageRef剥槐,它是一個指向CGImage結(jié)構(gòu)的指針唱歧。UIImage有一個CGImage屬性,它返回一個“CGImage”粒竖,如果你想把這個值直接賦值給CALayer的contents颅崩,那你將會得到一個編譯錯誤。因為CGImageRef并不是一個真正的Cocoa對象蕊苗,而是一個Core Foundation類型沿后。所以在將UIImage的CGImage屬性值賦值給contents屬性的時候要通過bridged關(guān)鍵字轉(zhuǎn)換
layer.contents = (_bridge id)image.CGImage;
CALayer的contentsGravity屬性
UIView大多數(shù)視覺相關(guān)的屬性比如contentMode,對這些屬性的操作其實是對對應(yīng)圖層的操作朽砰。CALayer與contentMode對應(yīng)的屬性叫做contentsGravity尖滚,它是一個NSString類型。contentsGravity可選的常量值有以下一些:
- contentsGravity
- kCAGravityTop
- kCAGravityBottom
- kCAGravityLeft
- kCAGravityRight
- kCAGravityTopLeft
- kCAGravityTopRight
- kCAGravityBottomLeft
- kCAGravityBottomRight
- kCAGravityResize
- kCAGravityResizeAspect
- kCAGravityResizeAspectFill
CALayer的contentsScale屬性
contentsScale屬性定義了寄宿圖的像素尺寸和視圖大小的比例瞧柔,默認情況下它是一個值為1.0的浮點數(shù)漆弄。該值越大,顯示的寄宿圖的視覺效果越小造锅。contentsScale的目的并不那么明顯撼唾,它并不是總會對屏幕上的寄宿圖有影響。例如倒谷,如果layer.contentsGravity = kCAGravityResizeAspect;
則contentsScale屬性不起效果。如果layer.contentsGravity = kCAGravityCenter;
則contentsScale對寄宿圖的顯示有影響肺素。
contentsScale屬性其實屬于支持高分辨率(又稱Hi-DPI或者Retina)屏幕機制的一部分恨锚。它用來判斷在繪制圖層的時候應(yīng)該為寄宿圖創(chuàng)建的空間大小,和需要顯示的圖片的拉伸度(假設(shè)并沒有設(shè)置contentsGravity屬性)倍靡。UIView有一個類似的功能猴伶,但非常少用到的contentScaleFactor屬性。
如果contentScale設(shè)置為1.0塌西,將會以每個點1個像素繪制圖片他挎,如果設(shè)置為2.0,則會以每個點2個像素繪制圖片捡需,這就是我們熟知的Retina屏办桨。
layer.contentsScale = image.scale;
如果如此設(shè)置跷车,則會根據(jù)圖片是1倍圖充边、2倍圖或者3倍圖進行相應(yīng)繪制怀估。
當(dāng)用代碼的方式來處理寄宿圖的時候条获,需要進行如下設(shè)置,否則圖片在Retina設(shè)備上就顯示的不正確
layer.contentsScale = [UIScreen mainScreen].scale
CALayer的maskToBounds屬性
圖層的maskToBounds相當(dāng)于UIView的clipsToBounds
CALayer的contentsRect屬性
contentsRect屬性允許我們在圖層邊框里顯示寄宿圖的一子域殊霞。和bounds摧阅、frame不同,contentRect不是按點來計算的绷蹲,它使用單位坐標(biāo)棒卷,單位坐標(biāo)指定在0~1之間,是一個相對寄宿圖尺寸的值
例如祝钢,如果設(shè)置layer.contentsRect = CGRectMake(0, 0, 0.5, 0.5);
,則效果如下:
CALayer的contentsCenter屬性
contentsCenter屬性只有在圖片被拉伸后才會起作用比规,contentsCenter可以用來定義全面拉伸的范圍。
如果contentsCenter屬性是上圖中間的藍色方框拦英,那么當(dāng)這個圖片被拉伸后蜒什,contentsCenter屬性定義的區(qū)域會被全面拉伸(也就是從四個方向進行放大或者縮小)龄章,而被這個方框分割后的其他方格會按照下圖所表示的進行橫向或者縱向的拉伸吃谣,或者某些方框根本不拉伸!這就是contentsCenter屬性的意義做裙。
其相當(dāng)于xib中如下位置的配置
CALayerDelegate
如果設(shè)置了代理岗憋,則當(dāng)需要被重繪的時候,CALayer會請求它的代理給它一個寄宿圖來顯示锚贱。它通過調(diào)用下面的方來來做到:
- (void)displayLayer:(CALayer *)layer;
如果過代理想要直接設(shè)置contents屬性的話仔戈,就可以在上面的代理方法中實現(xiàn)。如果代理不實現(xiàn)-displayLayer:
方法拧廊,CALayer會轉(zhuǎn)而嘗試調(diào)用下面的方法:
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
Demo
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
CALayer *blueLayer = [CALayer layer];
blueLayer.frame = CGRectMake(150, 150, 100, 100);
blueLayer.backgroundColor = [UIColor blueColor].CGColor;
blueLayer.delegate = self;
//ensure that layer backing image use correct scale
blueLayer.contentsScale = [UIScreen mainScreen].scale;
[self.view.layer addSublayer:blueLayer];
//force layer to redraw监徘。如果不實現(xiàn)下面代碼,不會調(diào)用代理方法
[blueLayer display];
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
//draw a thick red circle
CGContextSetLineWidth(ctx, 10.0f);
CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
CGContextStrokeEllipseInRect(ctx, layer.bounds);
}
- 當(dāng)UIView創(chuàng)建它的宿主圖層的時候吧碾,它就會自動把圖層的delegate設(shè)置為它自己凰盔,并提供一個-displayLayer:的實現(xiàn)。
- 不同于UIView倦春,當(dāng)圖層顯示在屏幕上的時候户敬,CALayer不會自動重繪它的內(nèi)容,它把重繪的決定權(quán)交給來開發(fā)者
- 盡管上面的demo沒有使用masksToBounds屬性睁本,繪制的那個圓仍然沿邊界被裁剪了尿庐。因為通過CALayerDelegate繪制寄宿圖的時候,并沒有對超出邊界外的內(nèi)容提供繪制支持
UIView和CALayer布局
UIView有三個比較重要的布局屬性:fame呢堰,bounds和center抄瑟,CALayer對應(yīng)地叫做frame,bounds和position枉疼。center和position都代表了相對于父圖層anchorPoint所在的位置皮假。
當(dāng)操縱視圖的frame鞋拟,實際上是該表了視圖下方的CALayer的frame,不能夠獨立于圖層之外改變視圖的frame惹资。
對于視圖或者圖層來說严卖,frame其實是一個虛擬的屬性,是更具bounds布轿,position和transform計算而來。所以當(dāng)其中任何一個值發(fā)生改變来颤,frame都會變化汰扭。相反,改變frame的值同樣會影響到它們當(dāng)中的值福铅。
當(dāng)對圖層做變換的時候萝毛,比如旋轉(zhuǎn)或者縮放,frame實際上代表覆蓋在圖層旋轉(zhuǎn)之后的整個軸對齊的矩形區(qū)域滑黔,也就是說frame的寬高可能和bounds的寬高不再一致了笆包。
錨點anchorPoint
圖層的anchorPoint通過position來控制它的frame的位置,可以認為anchorPoint是用來移動圖層的把柄略荡。anchorPoint用單位坐標(biāo)來描述庵佣,也就是圖層的相對坐標(biāo),圖層左上角是{0,0}汛兜,右下角是{1,1}巴粪,默認坐標(biāo)是{0.5,0.5}。anchorPoint可以通過指定x和y值小于0或者大于1粥谬,使得它被放置在圖層范圍之外肛根。
當(dāng)anchorPoint改變的時候,position屬性保持不變漏策,但是frame卻改變了派哲,實際上就是將anchorPoint點移動到原圖層的中心點。示例如下:
Z坐標(biāo)軸
和UIView嚴(yán)格的二維坐標(biāo)系不同掺喻,CALayer存在于一個三維空間當(dāng)中芭届。除了position和anchorPoint屬性行之外,CALayer還有另外兩個屬性zPosition和anchorPointZ巢寡,二者都是Z軸上描述圖層位置的浮點類型喉脖。通過增加圖層的zPosition,可以把圖層前置抑月,于是它就在所有其他圖層的前面了(或者至少是小于它的zPosition值的圖層的前面)树叽。zPosition并不需要增加太多,一般增加一個像素就可以把對應(yīng)的圖層前置了谦絮。
Hit Testing
CALayer并不關(guān)心任何響應(yīng)鏈?zhǔn)录馑校圆荒苤苯犹幚碛|摸事件或者手勢洁仗,但通過-containspoint:和-hitTest:方法可以實現(xiàn)類似效果。
-containspoint:接受一個本圖層坐標(biāo)系下的CGPoint性锭,如果這個點在圖層的frame范圍內(nèi)就返回YES赠潦。所以使用的時候需要把觸摸坐標(biāo)轉(zhuǎn)換成對應(yīng)圖層坐標(biāo)系下的坐標(biāo)。
-hitTest:方法同樣接受一個CGPoint類型參數(shù)草冈,它返回圖層本身她奥,或者包含這個坐標(biāo)點的葉子節(jié)點圖層。
注意:當(dāng)調(diào)用圖層的-hitTest:方法的時候怎棱,測算順序嚴(yán)格依賴于圖層樹當(dāng)中的圖層順序哩俭,上面提到的zPosition屬性可以改變屏幕上圖層的順序,當(dāng)不能改變事件傳遞的順序拳恋。
自動布局
如果想要隨意控制CALayer的布局凡资,就需要手動操作。最簡單的方法就是使用CALayerDelegate的入校函數(shù):
- (void)layoutSublayerOfLayer:(CALayer *)layer;
當(dāng)圖層的bounds發(fā)生改變谬运,或者圖層的-setneedsLayout方法被調(diào)用的時候隙赁,這個函數(shù)將會被執(zhí)行。這使得你可以手動地重新擺放或者調(diào)整子圖層的大小梆暖,但是不能像UIView的autoresizingMask和constraints屬性做到自適應(yīng)屏幕旋轉(zhuǎn)伞访。這也是為什么最好使用視圖而不是單獨的圖層來構(gòu)建應(yīng)用程序的另一個重要原因。
視覺效果
圓角
通過CALayer的cornerRadius和masksToBounds屬性就可以實現(xiàn)圓角功能
圖層邊框
通過CALayer的borderWidth和borderColor屬性就可以實現(xiàn)邊框功能
陰影
給shadowOpacity屬性一個大于默認值(也就是0)的值式廷,陰影就可以顯示在任意圖層之下咐扭。shadowOpacity是一個必須在0.0(不可見)和1.0(完全不透明)之間的浮點數(shù)。如果設(shè)置為1.0滑废,將會顯示一個有輕微模糊的黑色陰影稍微在圖層之上蝗肪。
通過shadowColor、shadowOffset和shadowRadius三個屬性可以控制陰影的表現(xiàn)蠕趁。shadowColor控制陰影的顏色薛闪;shadowOffset控制陰影的方向和距離;shadowRadius控制陰影的模糊度,當(dāng)它的值是0的時候俺陋,陰影就喝視圖一樣有一個非常明確的邊界線豁延,當(dāng)值越來越大的時候,邊界線看上去就會越來越模糊和自然腊状。
陰影裁剪問題
如果設(shè)置masksToBounds為YES诱咏,陰影無法顯示出來,解決方法是另外用一個layer實現(xiàn)陰影缴挖。
shadowPath屬性
通過shadowPath屬性可以自定義陰影形狀袋狞,demo如下:
self.layerView1.layer.shadowOpacity = 0.5f;
self.layerView2.layer.shadowOpacity = 0.5f;
self.layerView1.backgroundColor = [UIColor clearColor];
self.layerView2.backgroundColor = [UIColor clearColor];
//create a square shadow
CGMutablePathRef squarePath = CGPathCreateMutable();
CGPathAddRect(squarePath, NULL, self.layerView1.bounds);
self.layerView1.layer.shadowPath = squarePath;
CGPathRelease(squarePath);
//create a circular shadow
CGMutablePathRef circlePath = CGPathCreateMutable();
CGPathAddEllipseInRect(circlePath, NULL, self.layerView2.bounds);
self.layerView2.layer.shadowPath = squarePath;
CGPathRelease(squarePath);
如果是矩形或者圓之類的,用CGPath會相當(dāng)簡單明了,但是如果是更加復(fù)雜的一些圖形苟鸯,UIBezierPath類會更適合同蜻。
圖層蒙板
CALayer有一個屬性叫做mask,這個屬性本身就是一個CALayer類型早处。它類似于一個字圖層湾蔓,相對于父圖層(即擁有該屬性的圖層)布局。不同于那些繪制在父圖層中的字圖層砌梆,mask圖層定義了父圖層的部分可見區(qū)域默责。
mask圖層的color屬性是無關(guān)緊要的,真正重要的是圖層的輪廓咸包,mask圖層實心的部分會被保留下來傻丝,其他的則會被拋棄。
Demo
@interface SecondViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imgView;
@end
@implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
CALayer *maskLayer = [CALayer layer];
maskLayer.frame = self.imgView.bounds;
UIImage *maskImage = [UIImage imageNamed:@"u6594"];
maskLayer.contents = (__bridge id)maskImage.CGImage;
self.imgView.image = [UIImage imageNamed:@"u6414"];
self.imgView.layer.mask = maskLayer;
}
@end
CALayer蒙板圖層真正厲害的地方在于蒙板圖不局限于靜態(tài)圖诉儒。任何有圖層構(gòu)成的都可以作為mask屬性,這意味著蒙板可以通過代碼甚至是動畫實時生成亏掀。
拉伸過濾
CALayer提供三種拉伸過濾算法:
- kCAFilterLinear
- kCAFilterNearest
- kCAFilterTrilinear
minificationFilter和magnificationFilter默認的過濾器都是kCAFilterLinear忱反,這個過濾器采用雙線性濾波算法。kCAFilterNearest優(yōu)點在于保留像素的差異滤愕,使用于少斜線或是曲線輪廓的圖片温算。kCAFilterTrilinear和kCAFilterLinear非常相似,適用于多斜線或曲線輪廓的圖片间影。
組透明
CALayer對應(yīng)于UIView的alpha屬性的是opacity屬性注竿,這兩個屬性都是影響子層級的。也就是說魂贬,如果你給一個圖層設(shè)置了opacity屬性巩割,那它的字圖層都會受此影響。
可以通過設(shè)置shouldRasterize主 sing來實現(xiàn)組透明的效果付燥,如果它被設(shè)置為yes宣谈,在應(yīng)用透明度之前,圖層及其字圖層都會被 整合成一個整體的圖片键科,這樣就沒有透明度混合的問題了闻丑。
為了啟用shouldRasterize屬性,需要設(shè)置resterizationScale屬性勋颖。默認情況下嗦嗡,所有圖層拉伸都是1.0,所以如果你使用了shouldRasterize屬性饭玲,需要確保resterizationScale屬性匹配屏幕侥祭,以防止出現(xiàn)Retina屏幕像素化的問題。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
UIButton *button2 = [self customButton];
button2.center = CGPointMake(250, 150);
button2.alpha = 0.5;
[self.view addSubview:button2];
button2.layer.shouldRasterize = YES;
button2.layer.rasterizationScale = [UIScreen mainScreen].scale;
}
- (UIButton *)customButton
{
CGRect frame = CGRectMake(0, 0, 150, 50);
UIButton *button = [[UIButton alloc] initWithFrame:frame];
button.backgroundColor = [UIColor whiteColor];
button.layer.cornerRadius = 10;
frame = CGRectMake(20, 10, 110, 30);
UILabel *label = [[UILabel alloc] initWithFrame:frame];
label.text = @"Hello world";
label.textAlignment = NSTextAlignmentCenter;
[button addSubview:label];
return button;
}
變換,CGAffineTransform卑硫、CATransform3D
UIView的transform類型是CGAffineTransform徒恋,CALayer的transform類型是CATransform3D,CALayer的affineTransform屬性對應(yīng)的是CGAffineTransform
CGAffineTransform的實質(zhì)
struct CGAffineTransform {
CGFloat a, b, c, d;
CGFloat tx, ty;
};
混合變換
CGAffineTransformConcat(CGAffineTransform t1,
CGAffineTransform t2)
3D變換
透視投影
在真實世界中欢伏,當(dāng)物體遠離我們的時候入挣,有事視覺的原因看是來會變小,理論上遠離我們的視圖的邊要比靠近視角的邊更短硝拧,但實際上3D變換中并沒有發(fā)生這種情況径筏,因為3D變換中依然保持平行。在等距離投影中障陶,遠處的物體和近處的物體保持同樣的縮放比例滋恬。
CATransform3D的透視效果通過一個矩陣中的一個很簡單的元素控制:m34 。m34的默認值是0抱究,我們可以通過設(shè)置m34為-1.0/d來應(yīng)用透視效果恢氯,d代表了想象中視覺相機和屏幕之間的距離,以像素為單位鼓寺。
滅點
當(dāng)在透視角度繪圖的時候勋拟,遠離相機視覺的物體將會變小變遠,當(dāng)遠離到一個極限距離妈候,它們可能就縮成一個點敢靡,于是所有的物體最后都匯聚消失在同一個點。
在現(xiàn)實中苦银,這個點通常是視圖的中心啸胧,于是為了在應(yīng)用中創(chuàng)建擬真效果的透視,這個點應(yīng)該聚在屏幕中心幔虏,或者至少是包含所有3D對象的視圖中心纺念。
Core Animation定義了這個點位于變換圖層的anchorPoint(通常位于圖層的中心,但也有例外)想括。這就是說柠辞,當(dāng)圖層發(fā)生變換時,這個點永遠位于圖層變換之前anchorPoint的位置主胧。
當(dāng)改變一個圖層的position叭首,你也改變了它的滅點,做3D變換的時候要時刻記住這一點踪栋,當(dāng)你視圖通過調(diào)整m34來讓它更加有3D效果焙格,應(yīng)該首先把它放置于屏幕中央,然后通過平移來把它移動到指定位置(而不是直接改變它的position)夷都,這樣所有的3D圖層都共享一個滅點眷唉。
sublayerTransform
如果有多個視圖或者圖層,每個都做3D變換,那就需要分別設(shè)置相同的m34值冬阳,并且確保在變換之前都在屏幕中央共享同一個position蛤虐。
CALayer有一個屬性叫做sublayerTransform,它也是CATransform3D類型肝陪,但和對一個圖層的變換不同驳庭,它影響到所有子圖層。這意味著可以一次性對包含這些圖層的容器做變換氯窍,于是所有的字圖層都自動繼承了這個變換方法饲常。
相較而言,通過在一個地方設(shè)置透視變換會很方便狼讨,同時它會帶著另一個顯著的優(yōu)勢:滅點被設(shè)置在容器圖層的中心贝淤,從而不需要再對字圖層分別設(shè)置了,這意味著可以隨意使用position和frame來放置字圖層政供,而不需要把它們放置在屏幕中點播聪,然后為了保證統(tǒng)一的滅點用變換來做平移。
背面
圖層是雙面繪制的布隔,反面顯示的是正面的一個鏡像圖片犬耻。圖層的雙面繪制會造成資源浪費,在看不見圖層背面的情況下执泰,為什么還要浪費GPU來繪制它們呢。
CALayer有一個叫做doubleSided的屬性來控制圖層的背面是否要繪制渡蜻,這是一個Bool類型术吝,默認為YES,如果設(shè)置為NO茸苇,那么當(dāng)圖層正面從相機視覺消失的時候排苍,它將不會被繪制。
扁平化
盡管Core Animation圖層存在于3D控件之內(nèi)学密,但它們并不都是在同一個3D空間淘衙,每個圖層的3D場景其實都是扁平化的,當(dāng)你從正面觀察一個圖層腻暮,看到的實際上由子圖層創(chuàng)建的想象出來的3D場景彤守,但當(dāng)你傾斜這個圖層,你會發(fā)現(xiàn)實際上這個3D場景僅僅是被繪制在圖層的表面哭靖。所以單純對一個視圖的layer層做3D變換并不會影響到子視圖
專用圖層
CAShapeLayer
CAShapeLayer是一個通過矢量圖形而不是bitmap來繪制的圖層子類具垫。你指定諸如顏色和線寬燈屬性,用CGPath來定義想要繪制的圖形试幽,最后CAShapeLayer就自動渲染出來了筝蚕。當(dāng)然,也可以用Core Graphics直接向原始的CALayer的內(nèi)容中繪制一個路徑,相比之下起宽,使用CAShapeLayer有以下優(yōu)點:
- 渲染快速洲胖。CAShapeLayer使用了硬件加速,繪制同一圖形會比Core Graphics快很多
- 高效使用內(nèi)容坯沪。一個CAShapeLayer不需要向普通CALayer一樣創(chuàng)建一個寄宿圖形绿映,所以無論有多大,都不會占用太多的內(nèi)存屏箍。
- 不會被圖層邊界裁剪掉绘梦。一個CAShapeLayer可以在邊界之外繪制。
- 不會出現(xiàn)像素化赴魁。當(dāng)你給CAShapeLayer做3D變換時卸奉,它不像一個有寄宿圖的普通圖層一樣變得像素化。
Demo
UIBezierPath *path = [[UIBezierPath alloc] init];
[path moveToPoint:CGPointMake(175, 100)];
[path addArcWithCenter:CGPointMake(150, 100) radius:25 startAngle:0 endAngle:2 * M_PI clockwise:YES];
[path moveToPoint:CGPointMake(150, 125)];
[path addLineToPoint:CGPointMake(150, 175)];
[path addLineToPoint:CGPointMake(125, 225)];
[path moveToPoint:CGPointMake(150, 175)];
[path addLineToPoint:CGPointMake(175, 225)];
[path moveToPoint:CGPointMake(100, 150)];
[path addLineToPoint:CGPointMake(200, 150)];
CAShapeLayer *shapLayer = [CAShapeLayer layer];
shapLayer.strokeColor = [UIColor whiteColor].CGColor;
shapLayer.fillColor = [UIColor clearColor].CGColor;
shapLayer.lineWidth = 5;
shapLayer.lineJoin = kCALineJoinRound;
shapLayer.lineCap = kCALineCapRound;
shapLayer.path = path.CGPath;
[self.outerView.layer addSublayer:shapLayer];
圓角
創(chuàng)建圓角矩形颖御,其實就是人工繪制單獨的直線和弧度榄棵,但是事實上UIBezierPath有自動繪制圓角矩形的構(gòu)造方法。Demo如下潘拱,繪制一個有三個圓角一個直角的矩形:
CGRect rect = CGRectMake(50, 50, 100, 100);
CGSize radii = CGSizeMake(20, 20);
UIRectCorner corners = UIRectCornerTopRight | UIRectCornerTopLeft | UIRectCornerBottomLeft;
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:radii];
CAShapeLayer *shapLayer = [CAShapeLayer layer];
shapLayer.strokeColor = [UIColor whiteColor].CGColor;
shapLayer.fillColor = [UIColor clearColor].CGColor;
shapLayer.lineWidth = 5;
shapLayer.lineJoin = kCALineJoinRound;
shapLayer.lineCap = kCALineCapRound;
shapLayer.path = path.CGPath;
[self.outerView.layer addSublayer:shapLayer];
如果想依照圖形來裁剪視圖內(nèi)容疹鳄,可以把CAShapeLayer作為視圖的宿主圖層,而不是添加一個字視圖芦岂。
CATextLayer
CATextLayer以圖層的形式包含來UILabel幾乎所有的繪制特性瘪弓,并且額外提供了一些心特性。
CATextLayer的string屬性并不是NSString類型禽最,而是id類型腺怯,所以既可以用NSString也可以用NSAttributeString來指定文本
Demo:
//create a text layer
CATextLayer *textLayer = [CATextLayer layer];
textLayer.contentsScale = [UIScreen mainScreen].scale;
textLayer.frame = self.outerView.bounds;
[self.outerView.layer addSublayer:textLayer];
//set text attributes
textLayer.foregroundColor = [UIColor blackColor].CGColor;
textLayer.alignmentMode = kCAAlignmentJustified;
textLayer.wrapped = YES;
//choose a font
UIFont *font = [UIFont systemFontOfSize:15];
//set layer font
CFStringRef fontName = (__bridge CFStringRef)font.fontName;
CGFontRef fontRef = CGFontCreateWithFontName(fontName);
textLayer.font = fontRef;
textLayer.fontSize = font.pointSize;
CGFontRelease(fontRef);
//choose some text
NSString *text = @"辣椒開始的法律監(jiān)督減肥了空間阿里的開發(fā)價值,川无。春夏女快放假了哦 i 將凱迪拉克你的呛占,v來肯德基法拉第健身房";
//set layer text
textLayer.string = text;
行距和字距
由于繪制的實現(xiàn)機制不同(Core Text和WebKit),用CATextLayer渲染和用UILabel渲染出的文本行距和字距也不盡相同懦趋。
UILabel的替代品
CATextLayer比UILabel有著更好的性能表現(xiàn)晾虑,但是如果是給UILabel添加一個CATextLayer子圖層,由于UILabel仍然會使用-drawRect:方法創(chuàng)建空寄宿圖層仅叫,而且由于CALayer不支持自動縮放和自動布局帜篇,字圖層并不是主動跟蹤視圖邊界的大小,所以每次視圖大小被更改诫咱,就不得不手動更新子圖層的邊界坠狡。
每一個UIView都是寄宿在一個CALayer的示例上,這個圖層由視圖自動創(chuàng)建和管理遂跟。繼承UIView逃沿,并重寫+layerClass方法可以返回一個不同的圖層子類婴渡。UIView會在初始化的時候調(diào)用+layerClass方法,然后用它返回類型來創(chuàng)建宿主圖層凯亮。
CATextLayer作為宿主圖層边臼,視圖會自動設(shè)置contentsScale屬性。
自定義Label Demo CATextLayer的frame會隨著label的frame改變
#import "LayerLabel.h"
@implementation LayerLabel
+ (Class)layerClass
{
return [CATextLayer class];
}
- (CATextLayer *)textLayer
{
return (CATextLayer *)self.layer;
}
- (void)setup
{
self.text = self.text;
self.textColor = self.textColor;
self.font = self.font;
[self textLayer].alignmentMode = kCAAlignmentJustified;
[self textLayer].wrapped = YES;
[self.layer display];
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self setup];
}
return self;
}
- (void)awakeFromNib
{
[super awakeFromNib];
[self setup];
}
- (void)setText:(NSString *)text
{
super.text = text;
[self textLayer].string = text;
}
- (void)setTextColor:(UIColor *)textColor
{
super.textColor = textColor;
[self textLayer].foregroundColor = textColor.CGColor;
}
- (void)setFont:(UIFont *)font
{
super.font = font;
CFStringRef fontName = (__bridge CFStringRef)font.fontName;
CGFontRef fontRef = CGFontCreateWithFontName(fontName);
[self textLayer].font = fontRef;
[self textLayer].fontSize = font.pointSize;
CGFontRelease(fontRef);
}
CATransformLayer
CATransformLayer不同于普通的CALayer假消,它不能顯示自己的內(nèi)容柠并,只有存在了一個作用于子圖層的變換,它才真正存在富拗。CATransformLayer并不平面化它的字圖層臼予,所以它能夠用于構(gòu)造一個層級的3D結(jié)構(gòu),比如我的手臂示例啃沪。
Demo
- (void)viewDidLoad {
[super viewDidLoad];
[self create];
}
- (void)create
{
CATransform3D pt = CATransform3DIdentity;
pt.m34 = -1.0 / 500.0;
self.testImageView.layer.sublayerTransform = pt;
CATransform3D c1t = CATransform3DIdentity;
c1t = CATransform3DTranslate(c1t, -100, 0, 0);
CALayer *cube1 = [self cubeWithTransform:c1t];
[self.testImageView.layer addSublayer:cube1];
CATransform3D c2t = CATransform3DIdentity;
c2t = CATransform3DTranslate(c2t, 100, 0, 0);
c2t = CATransform3DRotate(c2t, -M_PI_4, 1, 0, 0);
c2t = CATransform3DRotate(c2t, -M_PI_4, 0, 1, 0);
CALayer *cube2 = [self cubeWithTransform:c2t];
[self.testImageView.layer addSublayer:cube2];
}
- (CALayer *)faceWithTransform:(CATransform3D)transform {
CALayer *face = [CALayer layer];
face.frame = CGRectMake(-50, -50, 100, 100);
CGFloat red = (rand()/(double)INT_MAX);
CGFloat green = (rand()/(double)INT_MAX);
CGFloat blue = (rand()/(double)INT_MAX);
face.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1].CGColor;
face.transform = transform;
return face;
}
- (CALayer *)cubeWithTransform:(CATransform3D)transform {
CATransformLayer *cube = [CATransformLayer layer];
CATransform3D ct = CATransform3DMakeTranslation(0, 0, 50);
[cube addSublayer:[self faceWithTransform:ct]];
ct = CATransform3DMakeTranslation(50, 0, 0);
ct = CATransform3DRotate(ct, M_PI_2, 0, 1, 0);
[cube addSublayer:[self faceWithTransform:ct]];
ct = CATransform3DMakeTranslation(0, -50, 0);
ct = CATransform3DRotate(ct, M_PI_2, 1, 0, 0);
[cube addSublayer:[self faceWithTransform:ct]];
ct = CATransform3DMakeTranslation(0, 50, 0);
ct = CATransform3DRotate(ct, M_PI_2, 1, 0, 0);
[cube addSublayer:[self faceWithTransform:ct]];
ct = CATransform3DMakeTranslation(-50, 0, 0);
ct = CATransform3DRotate(ct, M_PI_2, 0, 1, 0);
[cube addSublayer:[self faceWithTransform:ct]];
ct = CATransform3DMakeTranslation(0, 0, -50);
ct = CATransform3DRotate(ct, M_PI_2, 0, 1, 0);
[cube addSublayer:[self faceWithTransform:ct]];
CGSize containerSize = self.testImageView.bounds.size;
cube.position = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0);
cube.transform = transform;
return cube;
}
CAGradientLayer
CAGradientLayer是用來生成兩種或多種顏色平滑漸變的粘拾。用Core Graphics復(fù)制一個CAGrandientLayer并將內(nèi)容繪制到一個普通圖層的寄宿圖也是有可能的,但是CAGradientLayer的真正好處是繪制使用了硬件加速创千。
Demo
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = _testImageView.bounds;
[self.testImageView.layer addSublayer:gradientLayer];
gradientLayer.colors = @[(__bridge id)[UIColor redColor].CGColor,(__bridge id)[UIColor yellowColor].CGColor,(__bridge id)[UIColor blueColor].CGColor];
//如果不設(shè)置locations屬性缰雇,則各種顏色平均分布
gradientLayer.locations = @[@0.0,@0.25,@0.5];
//startPoint 和 endPoint是單位坐標(biāo)
gradientLayer.startPoint = CGPointMake(0, 0);
gradientLayer.endPoint = CGPointMake(1, 1);
CAReplicatorLayer
CAReplicatorLayer的目的是為了高效生成許多相似的圖層,它會繪制一個多個圖層的字圖層追驴,沒在每個復(fù)制體上應(yīng)用不同的變換械哟。
變換時會逐步增加的,每一個實例都是相對于前一個實例布局殿雪。instanceCount屬性指定了圖層需要重復(fù)多少次暇咆,instanceTransform指定一個CATransform3D 3D變換
Demo旋轉(zhuǎn)時繞CAReplicatorLayer中心點旋轉(zhuǎn)
CAReplicatorLayer *replicator = [CAReplicatorLayer layer];
replicator.frame = self.outerView.bounds;
[self.outerView.layer addSublayer:replicator];
replicator.instanceCount = 10;
//apply a transform for each instance
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DRotate(transform, M_PI/5.0, 0, 0, 1);
replicator.instanceTransform = transform;
//apply a color shift for each instance
replicator.instanceBlueOffset = -0.1;
replicator.instanceGreenOffset = -0.1;
//create a sublayer and place it inside the replicator
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(107.5, 0, 50.0, 50.0);
layer.backgroundColor = [UIColor greenColor].CGColor;
CATextLayer *textLayer = [CATextLayer layer];
textLayer.frame = CGRectMake(0, 0, 50, 50);
textLayer.string = @"xy";
[layer addSublayer:textLayer];
[replicator addSublayer:layer];
反射
使用CAReplicatorLayer并應(yīng)用一個負比例變換于一個復(fù)制圖層,你就可以創(chuàng)建指定視圖內(nèi)容的景象圖片丙曙。
自定義反射view Demo
#import "ReflectionView.h"
@implementation ReflectionView
+ (Class)layerClass
{
return [CAReplicatorLayer class];
}
- (void)setup
{
CAReplicatorLayer *repLayer = (CAReplicatorLayer *)self.layer;
repLayer.instanceCount = 2;
CATransform3D transform = CATransform3DIdentity;
CGFloat verticalOffset = self.bounds.size.height + 2;
transform = CATransform3DTranslate(transform, 0, verticalOffset, 0);
transform = CATransform3DScale(transform, 1, -1, 0);
repLayer.instanceTransform = transform;
repLayer.instanceAlphaOffset = -0.6;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self setup];
}
return self;
}
- (void)awakeFromNib
{
[super awakeFromNib];
[self setup];
}
@end
CAScrollLayer
CAScrollLayer實現(xiàn)圖層的滾動爸业,不過需要自定義手勢。
CATileLayer
CATileLayer可以解決載入大圖的性能問題河泳。CATileLayer將大圖分解成小片,然后將它們大度按需載入年栓。
為了能夠從CATiledLayer中受益拆挥,我們需要預(yù)先把圖片切成許多小一些的圖片(可以用代碼完成這件事)。如果是在運行時讀入這個那個圖片并裁切某抓,那CATileLayer的所有性能優(yōu)點就損失殆盡了。CATileLayer的默認小圖大小是256*256.
CATileLayer可以很好的和UIScrollView結(jié)合使用备禀。除了設(shè)置圖層和滑動視圖以適配整個圖片大小纽乱,我們真正要做的就是實現(xiàn)-drawLayer:incontext:方法薯嗤,當(dāng)需要載入新的小圖時繁仁,CATileLayer就會調(diào)用這個方法桥爽。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
CATiledLayer *tileLayer = [CATiledLayer layer];
tileLayer.delegate = self;
[self.scrollView.layer addSublayer:tileLayer];
self.scrollView.contentSize = tileLayer.frame.size;
[tileLayer setNeedsDisplay];
}
- (void)drawLayer:(CATiledLayer *)layer inContext:(CGContextRef)ctx
{
//determine tile coordinate
CGRect bounds = CGContextGetClipBoundingBox(ctx);
NSInteger x = floor(bounds.origin.x / layer.tileSize.width);
NSInteger y = floor(bounds.origin.y / layer.tileSize.height);
//load tile image
NSString *imageName = [NSString stringWithFormat:@"picture_%ld_%ld",x,y];
UIImage *tileImage = [UIImage imageNamed:imageName];
UIGraphicsPushContext(ctx);
[tileImage drawInRect:bounds];
UIGraphicsPopContext();
}
小圖并不是以Retina的分辨率顯示的缕碎,為了以屏幕的原生分辨率來渲染CATileLayer栅贴,我們需要設(shè)置圖層的contentScale來匹配UIScreen的scale屬性:
tileLayer.contentsScale = [UIScreen mainScreen].scale;
tileSize是以像素為單位,而不是點宠页,所以增大了contentsScale就自動有了默認的小圖尺寸(現(xiàn)在是128128的點而不是256256)供填,所以不需要手工更新小圖的尺寸或者在Retina分辨率下指定一個不同的小圖晌端,我們需要做的是適應(yīng)小圖渲染代碼以對應(yīng)安排scale的變化
//determine tile coordinate
CGRect bounds = CGContextGetClipBoundingBox(ctx);
CGFloat scale = [UIScreen mainScreen].scale;
NSInteger x = floor(bounds.origin.x / layer.tileSize.width * scale);
NSInteger y = floor(bounds.origin.y / layer.tileSize.height * scale);
通過這個方法糾正scale也意味著圖片將以原先一半的大小渲染在Retina設(shè)備上。
CAEmitterLayer
CAEmitterLayer是一個高性能的粒子引擎演痒,被用來創(chuàng)建實時例子動畫如:煙霧亲轨、火蓄坏、雨等類似效果
CAEmitterLayer看上去像是許多CAEmitterCell的容器牡整,這些CAEmitterCell定義了一個例子效果。我們需要為不同的例子效果定義一個或多個CAEmitterCell作為模版躬窜,同時CAEmitterLayer負責(zé)基于這些模版實例化一個粒子流讯嫂。一個CAEmitterCell類似于一個CALayer:它有一個contents屬性可以定義為一個CGImage蹦锋,另外還有一些可設(shè)置屬性控制著變現(xiàn)和行為。
CAEmitterLayer *emitter = [CAEmitterLayer layer];
emitter.frame = self.outerView.bounds;
[self.outerView.layer addSublayer:emitter];
//configure emitter
emitter.renderMode = kCAEmitterLayerAdditive;
emitter.emitterPosition = CGPointMake(emitter.frame.size.width/2.0, CGRectGetHeight(emitter.frame)/2.0);
//create a particle template
CAEmitterCell *cell = [CAEmitterCell new];
cell.contents = (__bridge id)([UIImage imageNamed:@"u3264"].CGImage);
cell.birthRate = 150;
cell.lifetime = 5.0;
cell.color = [UIColor redColor].CGColor;
cell.alphaSpeed = -0.4;
cell.velocity = 50;
cell.velocityRange = 50;
cell.emissionRange = M_PI * 2.0;
//add particle template to emitter
emitter.emitterCells = @[cell];
CAEmitterCell的屬性基本上可以分為三種:
- 這種粒子的某一屬性的初始值莉掂。比如,color屬性置頂了一個可以混合圖片內(nèi)容顏色的混合顏色
- 粒子某一屬性的變化范圍千扔。比如emissionRange屬性值是M_PI * 2.0憎妙,這意味著粒子可以從360度任意位置發(fā)射出去
- 指定值在時間線上的變化。比如曲楚,上面例子中厘唾,我們將alphaSpeed設(shè)置為-0.4,就是說例子的透明度每秒就是較少0.4洞渤,這就是發(fā)射出去后逐漸消失的效果阅嘶。
CAEmitterLayer的屬性它自己控制著整個例子系統(tǒng)的位置和形狀,一些屬性比如birthRate载迄、lifetime和celocity讯柔,這些屬性在CAEmitterCell中也有。這些屬性會以相乘的方式作用在一起护昧,這樣就可以用一個值來加速或者擴大整個例子系統(tǒng)魂迄。其他需要注意的屬性有如下這些:
- preservesDepth,是否將3D例子系統(tǒng)平面化到一個圖層(默認值)或者可以在3D空間中混合其他的圖層
- renderMode惋耙,控制著在視覺上粒子圖片是如何混合的捣炬。上面例子中kCAEmitterLayerAdditive實現(xiàn)的效果為:合并例子重疊部分的亮度使得看上去更亮。
CAEAGLLayer
OpenGL提供了Core Animation的基礎(chǔ)绽榛,它是底層的C接口湿酸。iOS5中,蘋果引入了一個新的框架叫做GLKit灭美,它去掉了一些設(shè)置OpenGL的復(fù)雜性推溃,提供了一個叫做CLKView的UIView子類,幫你處理大部分的設(shè)置和繪制工作届腐。前提是各種各樣的OpenGL繪圖緩沖的底層可配置項仍需要你用CAEAGLLayer完成铁坎,它是CALayer的一個子類蜂奸,用來顯示任意的OpenGL圖形。
@interface SecondViewController ()<CALayerDelegate>
@property (nonatomic, weak) IBOutlet UIView *glView;
@property (nonatomic, strong) EAGLContext *glContext;
@property (nonatomic, strong) CAEAGLLayer *glLayer;
@property (nonatomic, assign) GLuint framebuffer;
@property (nonatomic, assign) GLuint colorRenderbuffer;
@property (nonatomic, assign) GLint framebufferWidth;
@property (nonatomic, assign) GLint framebufferHeight;
@property (nonatomic, strong) GLKBaseEffect *effect;
@end
@implementation SecondViewController
- (void)setUpBuffers
{
//set up frame buffer
glGenFramebuffers(1, &_framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
//set up color render buffer
glGenRenderbuffers(1, &_colorRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderbuffer);
[self.glContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.glLayer];
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_framebufferWidth);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_framebufferHeight);
//check success
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
NSLog(@"Failed to make complete framebuffer object: %i", glCheckFramebufferStatus(GL_FRAMEBUFFER));
}
}
- (void)tearDownBuffers
{
if (_framebuffer) {
//delete framebuffer
glDeleteFramebuffers(1, &_framebuffer);
_framebuffer = 0;
}
if (_colorRenderbuffer) {
//delete color render buffer
glDeleteRenderbuffers(1, &_colorRenderbuffer);
_colorRenderbuffer = 0;
}
}
- (void)drawFrame {
//bind framebuffer & set viewport
glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
glViewport(0, 0, _framebufferWidth, _framebufferHeight);
//bind shader program
[self.effect prepareToDraw];
//clear the screen
glClear(GL_COLOR_BUFFER_BIT); glClearColor(0.0, 0.0, 0.0, 1.0);
//set up vertices
GLfloat vertices[] = {
-0.5f, -0.5f, -1.0f, 0.0f, 0.5f, -1.0f, 0.5f, -0.5f, -1.0f,
};
//set up colors
GLfloat colors[] = {
0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
};
//draw triangle
glEnableVertexAttribArray(GLKVertexAttribPosition);
glEnableVertexAttribArray(GLKVertexAttribColor);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 0, vertices);
glVertexAttribPointer(GLKVertexAttribColor,4, GL_FLOAT, GL_FALSE, 0, colors);
glDrawArrays(GL_TRIANGLES, 0, 3);
//present render buffer
glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);
[self.glContext presentRenderbuffer:GL_RENDERBUFFER];
}
- (void)viewDidLoad
{
[super viewDidLoad];
//set up context
self.glContext = [[EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:self.glContext];
//set up layer
self.glLayer = [CAEAGLLayer layer];
self.glLayer.frame = self.glView.bounds;
[self.glView.layer addSublayer:self.glLayer];
self.glLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking:@NO, kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8};
//set up base effect
self.effect = [[GLKBaseEffect alloc] init];
//set up buffers
[self setUpBuffers];
//draw frame
[self drawFrame];
}
- (void)viewDidUnload
{
[self tearDownBuffers];
[super viewDidUnload];
}
- (void)dealloc
{
[self tearDownBuffers];
[EAGLContext setCurrentContext:nil];
}
@end
AVPlayerLayer
AVPlayerLayer不是Core Animation框架的一部分硬萍,其是AVFoundation的一部分扩所。AVPlayerLayer與Core Animation緊密地結(jié)合在一起,提供一個CALayer子類來顯示自定義的內(nèi)容類型朴乖。
AVPlayerLayer是用來在iOS上播放視頻的祖屏,它是高級接口如MPMoviePlayer的底層實現(xiàn),提供了顯示視頻的底層控制买羞。
NSString *urlPath = [[NSBundle mainBundle] pathForResource:@"aaa" ofType:@"mp4"];
AVPlayerItem *item = [AVPlayerItem playerItemWithURL:[NSURL fileURLWithPath:urlPath]];
self.player = [AVPlayer playerWithPlayerItem:item];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
playerLayer.frame = CGRectMake(0, 0, CGRectGetWidth([UIScreen mainScreen].bounds), 200);
[_bgView.layer addSublayer:playerLayer];
[self.player play];
隱式動畫
事務(wù)
當(dāng)改變CALayer的一個可做動畫的屬性赐劣,它并不能立刻在屏幕上體現(xiàn)出來。相反哩都,它是從先前的值平滑過渡到新的值魁兼,這一切都是默認的行為。這就是所謂的隱式動畫漠嵌。
當(dāng)改變一個屬性Core Animation是如何判斷動畫類型和持續(xù)時間的呢咐汞?實際上動畫執(zhí)行的時間取決于當(dāng)前事務(wù)的設(shè)置,動畫類型取決于圖層行為儒鹿。
事務(wù)實際上是Core Animation用來包含一系列屬性動畫集合的機制化撕,任何用指定事務(wù)去改變可以做動畫的圖層屬性都不會立刻發(fā)生變化,而是當(dāng)事務(wù)一旦提交的時候開始用一個動畫過渡到新值约炎。
事務(wù)是通過CATransaction類來做管理植阴,這個類設(shè)計有些奇怪,不像你從它命名預(yù)期的那樣去管理一個簡單的事務(wù)圾浅,而是管理類一疊你不能訪問的事務(wù)掠手。CATransaction沒有屬性或者實例方法,并且也不能用+alloc和-init方法創(chuàng)建狸捕。但可以用+begin和+commit分別來入椗绺耄或者出棧。
任何可以做動畫的圖層屬性都會被添加到棧頂?shù)氖聞?wù)灸拍,你可以通過+setAnimationDuration:方法設(shè)置當(dāng)前事務(wù)的動畫時間做祝,或者通過+animationDuration方法來獲取值(默認0.25秒)。
Demo
- (void)viewDidLoad
{
[super viewDidLoad];
//set up context
self.colorLayer = [CALayer layer];
self.colorLayer.frame = self.colorView.bounds;
[self.colorView.layer addSublayer:self.colorLayer];
}
- (IBAction)buttonOnClickAction:(id)sender {
//begin a new transaction
[CATransaction begin];
//set the animation duration to 1 second
[CATransaction setAnimationDuration:1.0];
CGFloat red = arc4random() /(CGFloat)INT_MAX;
CGFloat green = arc4random() /(CGFloat)INT_MAX;
CGFloat blue = arc4random() /(CGFloat)INT_MAX;
self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1].CGColor;
//commit the transaction
[CATransaction commit];
}
實際上鸡岗,UIView的+beginAnimations: context:和+commitAnimations之間所有視圖或者圖層屬性的改變而做的動畫都是由于設(shè)置了CATransaction的原因混槐。UIView的+animateWithDuration: animations:方法內(nèi)部也會自動調(diào)用CATransaction的+begin和+commit方法
給事務(wù)設(shè)置CompletionBlock
[CATransaction begin];
//set the animation duration to 1 second
[CATransaction setAnimationDuration:1.0];
//add the spin animation on completion
[CATransaction setCompletionBlock:^{
}];
CGFloat red = arc4random() /(CGFloat)INT_MAX;
CGFloat green = arc4random() /(CGFloat)INT_MAX;
CGFloat blue = arc4random() /(CGFloat)INT_MAX;
self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1].CGColor;
//commit the transaction
[CATransaction commit];
圖層行為
Core Animation通常對CALayer的所有屬性(可動畫的屬性)做動畫,但是UIView把和它關(guān)聯(lián)的圖層的這個特性關(guān)閉了轩性。
當(dāng)CALayer的屬性被修改的時候声登,它會調(diào)用-actionForKey:方法,傳遞屬性的名稱。剩下的操作都是CALayer的頭文件中有詳細的說明捌刮,實質(zhì)上是如下幾步:
- 圖層首先檢測它是否有委托,并且是否實現(xiàn)CALayerDelegate協(xié)議指定的-actionForLayer:forkey:方法舒岸。如果有绅作,直接調(diào)用并返回結(jié)果。
- 如果沒有委托蛾派,或者委托沒有實現(xiàn)-actionForLayer:forkey:方法俄认,圖層接著檢查包含屬性名稱對應(yīng)映射的actions字典。
- 如果actions字典沒有包含對應(yīng)的屬性洪乍,那么圖層接著在它的style字典接著搜索屬性名眯杏。
- 最后,如果在style里面也找不到對應(yīng)的行為壳澳,那么圖層將會直接調(diào)用定義了每個屬性的標(biāo)準(zhǔn)行為的-defaultActionForKey:方法岂贩。
所以一輪完整的搜索結(jié)束之后,-actionForKey:要么返回空(這種情況下將不會有動畫發(fā)生)巷波,要么是CAAction協(xié)議對應(yīng)的對象萎津,最后CALayer拿這個結(jié)果去和先前和當(dāng)前的值做動畫。
UIView對它關(guān)聯(lián)的圖層禁用隱式動畫的實現(xiàn)方法就是:當(dāng)不在一個動畫塊的實現(xiàn)中抹镊,-actionForKey:方法返回空锉屈,但是在動畫block范圍之內(nèi),它就返回一個非空值垮耳。
//test layer action when outside of animation block,返回空值
NSLog(@"Outside: %@",[self.colorView actionForLayer:self.colorView.layer forKey:@"xxx"]);
//bing animation block
[UIView beginAnimations:nil context:nil];
//test layer action when inside of animation block,返回非空值
NSLog(@"Inside: %@",[self.colorView actionForLayer:self.colorView.layer forKey:@"xxx"]);
//end animation block
[UIView commitAnimations];
呈現(xiàn)與模型
每個圖層屬性的顯示值都被存儲在一個叫做呈現(xiàn)圖層的獨立圖層當(dāng)中颈渊,它可以通過-presentationLayer方法來訪問,這個呈現(xiàn)圖層實際上是模型圖層的復(fù)制终佛,但是它的屬性值代表了任何指定時刻當(dāng)前外觀效果俊嗽。換句話說,你可以通過呈現(xiàn)圖層的值來獲取當(dāng)前屏幕上真正顯示出來的值铃彰。
前面提到除來圖層樹之外還有呈現(xiàn)樹乌询。呈現(xiàn)樹就是通過圖層樹中所有圖層的呈現(xiàn)圖層形成的。
顯示動畫
像所有NSObject子類一樣豌研,CAAnimation實現(xiàn)KVC協(xié)議妹田,于是你可以用-setValue:forKey:和-valueForKey:方法來存取屬性。但是CAAnimation有一個不同的性能鹃共,它更像一個NSDictionary鬼佣,可以讓你隨意設(shè)置鍵值對,即使和你使用的動畫類所聲明的屬性并不匹配霜浴。這意味著可以對動畫用任意類型打標(biāo)簽晶衷,從而在代理方法中識別動畫。
關(guān)鍵幀動畫
CAKeyframeAnimation是另一種UIKit沒有暴露出來但功能強大的累,和CABasicAnimation類似晌纫,CAKeyframeAnimation同樣是CAProperyAnimation的一個子類税迷,它依然作用于單一的一個屬性,但是和CABasicAnimation不一樣的是锹漱,它不限制于設(shè)置一個起始和結(jié)束的值箭养,而是可以根據(jù)一連串隨意的值來做動畫。
動畫組CAAnimationGroup
CABasicAnimation和CAKeyframeAnimation僅僅作用于單獨的屬性哥牍,而CAAnimationGroup可以把這些動畫組合在一起毕泌。CAAnimationGroup是另一個繼承于CAAnimation的子類,它添加了一個animations數(shù)組的屬性嗅辣,用來組合別的動畫撼泛。關(guān)鍵代碼如下:
CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
groupAnimation.animations = @[animation1,animation2];
groupAnimation.duration = 4.0;
[self.colorView.layer addAnimation:groupAnimation forKey:nil];
過渡
有時候?qū)τ趇OS應(yīng)用程序來說,希望通過屬性動畫來對比較難做的動畫的布局進行一些改變澡谭,比如交換一段文本和圖片愿题,或者用一段網(wǎng)格視圖來替換,等等蛙奖。屬性動畫只對圖層的可動畫屬性起作用抠忘,所以如果要改變一個不能動畫的屬性(比如圖片),或者從層級關(guān)系匯總添加或者移除圖層外永,屬性動畫將不起作用崎脉。
于是就有了過渡的概念,過渡并不像屬性動畫那樣平滑地在兩個值之間動畫伯顶,而是影響到整個圖層的變化囚灼。過渡動畫首先展示之前的圖層外觀,然后通過一個交換過渡到了新的外觀祭衩。
為了創(chuàng)建一個過渡動畫灶体,我們將使用CATransition,同樣是另一個CAAnimation的子類掐暮,和別的子類不同蝎抽,CATransition有一個type和subtype來表示變換效果。type屬性是一個NSString類型路克,可以被設(shè)置成如下類型:
kCATransitionFade(默認) 淡入淡出
kCATransitionMoveIn 從頂部滑動進入
kCATransitionPush 從邊緣的一側(cè)滑動進來
kCATransitionReveal 把原始的圖層滑動出去來顯示新的外觀
后面三種過渡類型都有一個默認的動畫方向樟结,它們都從左側(cè)滑入,但可以通過subtype來控制它們的方向精算,提供如下四種類型:
kCATransitionFromRight
kCATransitionFromLeft
kCATransitionFromTop
kCATransitionFromBottom
自定義動畫
UIView的+transitionFromView: toView: duration: options: completion:和+transitionWithView: duration: options: animations: completion:方法提供了Core Animation的過渡特性瓢宦。UIView過渡方法中options參數(shù)可以由如下常量指定:
UIViewAnimationOptionTransitionNone 默認
UIViewAnimationOptionTransitionFlipFromLeft ,
UIViewAnimationOptionTransitionFlipFromRight ,
UIViewAnimationOptionTransitionCurlUp ,
UIViewAnimationOptionTransitionCurlDown ,
UIViewAnimationOptionTransitionCrossDissolve ,
UIViewAnimationOptionTransitionFlipFromTop ,
UIViewAnimationOptionTransitionFlipFromBottom ,
除了UIViewAnimationOptionTransitionCrossDissolve之外,剩下的值和CATransition類型完全沒關(guān)系
通過使用CALayer的-renderInContext:方法把它繪制到Core Graphics的上下文中捕獲當(dāng)前內(nèi)容的圖片灰羽,然后在另外的視圖中顯示出來驮履。如果把這個截圖置于原始視圖之上鱼辙,就可以遮住真實視圖的所有變化,于是自定義一個過渡效果玫镐。
在動畫過程中取消動畫
終止指定的動畫:
- (void)removeAnimationForKey:(NSString *)key;
移除所有動畫:
- (void)removeAllAnimations;
動畫一旦被移除倒戏,圖層的外觀就立刻更新到當(dāng)前的模型層的值。一般來說恐似,動畫在結(jié)束之后被自動移除杜跷,除非設(shè)置removedOnCompletion為NO。如果設(shè)置動畫結(jié)束之后不被自動移除蹂喻,那么當(dāng)它不需要的時候要手動移除它,否則它會一直存在于內(nèi)存中捂寿,直到圖層被銷毀口四。
圖層時間
CAMediaTiming協(xié)議
在“顯式動畫”中提到過的duration就是CAMediaTiming的屬性之一,duration為將要進行的動畫的一次迭代指定了時間秦陋。CAMediaTiming另外一個屬性叫做repeatCount蔓彩,代表動畫重復(fù)的迭代次數(shù)。完整的動畫時間是duration * repeatCount驳概。
創(chuàng)建重復(fù)動畫的另一種方式是使用repeatDuration屬性赤嚼,它讓動畫重復(fù)一個指定的時間,而不是指定次數(shù)顺又。甚至可以設(shè)置autoreverses屬性更卒,使得每次間隔交替循環(huán)過程中自動回放。
將repeatDuration或者repeatCount設(shè)置為INFINITY可以實現(xiàn)無限循環(huán)
相對時間
關(guān)于Core Animation的時間是相對的稚照,每個動畫都有它自己描述的時間蹂空,可以獨立地加速、延時或者偏移果录。
beginTime指定了動畫開始之前的延遲時間上枕,這里的延遲從動畫添加到可見圖層的那一刻開始測量,默認是0
speed是一個時間的倍數(shù)弱恒,默認1.0辨萍,減少它會減慢圖層/動畫的時間,增加它會加快速度返弹。如果speed為2.0锈玉,那么對于一個duration為1的動畫,實際上在0.5秒的時候就已經(jīng)完成了义起。
timeOffset是讓動畫快進到某一時間點嘲玫。例如,對于一個持續(xù)1秒的動畫來說并扇,設(shè)置timeOffset為0.5意味著動畫將從一半的地方開始去团。
和beginTime不同的是timeOffset并不受speed的影響,所以如果你把speed設(shè)置為2.0,吧timeOffset設(shè)置為0.5土陪,那么對于duration為1的動畫昼汗,動畫將從動畫最后結(jié)束的地方開始。
層級關(guān)系時間
每個動畫和圖層在時間上都有它的層級概念鬼雀,相對于它的父親來測量顷窒。對圖層調(diào)整時間將會影響到它本身和字圖層的動畫,但不會影響到父圖層源哩。
對CALayer或者CAGroupAnimation調(diào)整duration和repeatCount/repeatDuration屬性并不會影響到子動畫鞋吉。但是beginTime,timeOffset和speed屬性將會影響到子動畫励烦。
全局時間和本地時間
Core Animation有一個全局時間的概念谓着,也就是所謂的馬赫時間。馬赫時間在設(shè)備上所有進程都是全局的坛掠,但是在不同設(shè)備上并不是全局的赊锚。可以使用如下方法訪問馬赫時間:
CFTimeInterval time = CACurrentMediaTime();
這個函數(shù)返回的惡值其實無關(guān)緊要(它返回了設(shè)備自從上次啟動后的秒數(shù))屉栓,它真實的作用在于對動畫時間測量提供一個相對值舷蒲。
注意:當(dāng)設(shè)備休眠的時候馬赫時間會暫停,也就是所有CAAnimations(基于馬赫時間)同樣會暫停友多。
每個CALayer和CAAnimation實例都有自己本地時間的概念牲平,是根據(jù)父圖層/動畫層級關(guān)系中的beginTime,timeOffset和speed屬性計算域滥。CALayer提供了如下方法轉(zhuǎn)換不同圖層之間的本地時間:
- (CFTimeInterval)convertTime:(CFTimeInterval)t fromLayer:(nullable CALayer *)l;
- (CFTimeInterval)convertTime:(CFTimeInterval)t toLayer:(nullable CALayer *)l;
暫停欠拾、倒回和快進
給圖層添加一個CAAnimation實際上是給動畫對象做一個不可改變的拷貝,所以對原始動畫對象屬性的修改對真實的動畫并沒有作用骗绕。相反藐窄,使用-animationForKey:來檢索圖層正在進行的動畫可以返回正確的動畫對象,但是修改它會拋出異常酬土。
一個簡單的方法是利用CAMediaTiming來暫停圖層本身荆忍。如果把圖層的speed設(shè)置為0,它會暫停任何添加到圖層上的動畫撤缴,類似的刹枉,設(shè)置speed大于1.0將會快進,設(shè)置成一個負值將會倒回動畫屈呕。
手動動畫
timeOffset一個很有用的功能在于它可以讓你手動控制動畫進程微宝,通過設(shè)置speed為0,可以禁用動畫的自動播放虎眨,讓后使用timeOffset來來回回顯示動畫序列蟋软。
緩沖
CAMediaTimingFunction
使用緩沖方程式的方法是設(shè)置CAAnimation的timingFunction屬性镶摘,是CAMediaTimingFunction類的一個對象。如果行抗改變隱式動畫的基石函數(shù)岳守,同樣也可以使用CATransaction的+setAnimationTimingfunction:方法凄敢。
最簡單的創(chuàng)建CAMediaTimingFunction的方式是調(diào)用+ (instancetype)functionWithName:(NSString *)name;方法,這里傳入如下幾個常量之一:
kCAMediaTimingFunctionLinear(默認)勻速
kCAMediaTimingFunctionEaseIn 慢到快
kCAMediaTimingFunctionEaseOut 快到慢
kCAMediaTimingFunctionEaseInEaseOut 慢快慢
kCAMediaTimingFunctionDefault 和kCAMediaTimingFunctionEaseInEaseOut類似湿痢,只是加速和減速的過程都稍微有些慢涝缝。
CAMediaTimingFunction使用了一個叫做三次貝塞爾曲線的函數(shù),它只可以產(chǎn)出指定緩沖函數(shù)的子集譬重。一個三次貝塞爾曲線通過四個點來定義拒逮,第一個和最后一個點代表了曲線的起點和終點,剩下中間兩個點叫做控制點臀规,因為它們控制了曲線的形狀滩援,貝塞爾曲線控制點其實是位于曲線之外的點,也就是說曲線并不一定要穿過它們以现『菰梗可以把它們想象成吸引經(jīng)過它們的磁鐵约啊。
CAKeyframeAnimation有個timingFunctions屬性是CAMediaTimingFunction對象數(shù)組邑遏。可以用于實現(xiàn)復(fù)雜動畫