CALayer總結(jié)

轉(zhuǎn)載:http://www.cnblogs.com/jingdizhiwa/p/5601240.html

1.geometryFlipped設(shè)置為yes,則子圖層或者子視圖本來相對(duì)于左上角放置 改為 相對(duì)于左下角放置;

2.contents

3.contentGravity:

kCAGravityCenter

kCAGravityTop

kCAGravityBottom

kCAGravityLeft

kCAGravityRight

kCAGravityTopLeft

kCAGravityTopRight

kCAGravityBottomLeft

kCAGravityBottomRight

kCAGravityResize

kCAGravityResizeAspect

kCAGravityResizeAspectFill

4.contentsScale

5.maskToBounds

6.contentsRect

7.contentsCenter

8.自己繪制寄宿圖

方法-:繼承UIView并實(shí)現(xiàn)-drawRect:(如果你不需要寄宿圖,那就不要?jiǎng)?chuàng)建這個(gè)方法了,這會(huì)造成CPU資源和內(nèi)存的浪費(fèi),這也是為什么蘋果建議:如果沒有自定義繪制的任務(wù)就不要在子類中寫一個(gè)空的-drawRect:方法。)

方法二:CALayer有一個(gè)可選的delegate屬性,參考代碼如下:

blueLayer.delegate=self;

[blueLayer display];- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx

{//draw a thick red circleCGContextSetLineWidth(ctx,10.0f);

CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);

CGContextStrokeEllipseInRect(ctx, layer.bounds);

}

我們?cè)赽lueLayer上顯式地調(diào)用了-display。不同于UIView,當(dāng)圖層顯示在屏幕上時(shí),CALayer不會(huì)自動(dòng)重繪它的內(nèi)容。它把重繪的決定權(quán)交給了開發(fā)者。

盡管我們沒有用masksToBounds屬性,繪制的那個(gè)圓仍然沿邊界被裁剪了。這是因?yàn)楫?dāng)你使用CALayerDelegate繪制寄宿圖的時(shí)候,并沒有對(duì)超出邊界外的內(nèi)容提供繪制支持。

9.布局:

frame并不是一個(gè)非常清晰的屬性,它其實(shí)是一個(gè)虛擬屬性,是根據(jù)bounds,position和transform計(jì)算而來,所以當(dāng)其中任何一個(gè)值發(fā)生改變,frame都會(huì)變化。相反,改變frame的值同樣會(huì)影響到他們當(dāng)中的值;

錨點(diǎn)

10.坐標(biāo)系

- (CGPoint)convertPoint:(CGPoint)point fromLayer:(CALayer *)layer;- (CGPoint)convertPoint:(CGPoint)point toLayer:(CALayer *)layer;- (CGRect)convertRect:(CGRect)rect fromLayer:(CALayer *)layer;- (CGRect)convertRect:(CGRect)rect toLayer:(CALayer *)layer;

11.zPosition

增加圖層的zPosition,就可以把圖層向相機(jī)方向前置,于是它就在所有其他圖層的前面了(或者至少是小于它的zPosition值的圖層的前面);同樣適用于視圖的layer

12.Hit Testing

CALayer并不關(guān)心任何響應(yīng)鏈?zhǔn)录圆荒苤苯犹幚碛|摸事件或者手勢(shì)。但是它有一系列的方法幫你處理事件:-containsPoint:和-hitTest:。

13.自動(dòng)布局

如果想隨意控制CALayer的布局,就需要手工操作。最簡(jiǎn)單的方法就是使用CALayerDelegate如下函數(shù):

-?(void)layoutSublayersOfLayer:(CALayer?*)layer;

當(dāng)圖層的bounds發(fā)生改變,或者圖層的-setNeedsLayout方法被調(diào)用的時(shí)候,這個(gè)函數(shù)將會(huì)被執(zhí)行。這使得你可以手動(dòng)地重新擺放或者重新調(diào) 整子圖層的大小,但是不能像UIView的autoresizingMask和constraints屬性做到自適應(yīng)屏幕旋轉(zhuǎn)。

14.conrnerRadius

默認(rèn)情況下,這個(gè)曲率值只影響背景顏色而不影響背景圖片或是子圖層。不過,如果把masksToBounds設(shè)置成YES的話,圖層里面的所有東西都會(huì)被截取

15.shadowOpacity

若要改動(dòng)陰影的表現(xiàn),你可以使用CALayer的另外三個(gè)屬性:shadowColor,shadowOffset和shadowRadius

16.shadowPath屬性

我們已經(jīng)知道圖層陰影并不總是方的,而是從圖層內(nèi)容的形狀繼承而來。這看上去不錯(cuò),但是實(shí)時(shí)計(jì)算陰影也是一個(gè)非常消耗資源的,尤其是圖層有多個(gè)子圖層,每個(gè)圖層還有一個(gè)有透明效果的寄宿圖的時(shí)候。

如果你事先知道你的陰影形狀會(huì)是什么樣子的,你可以通過指定一個(gè)shadowPath來提高性能;

17.圖層蒙板

@interfaceViewController ()

@property (nonatomic, weak) IBOutlet UIImageView*imageView;@end@implementationViewController- (void)viewDidLoad

{

[super viewDidLoad];//create mask layerCALayer*maskLayer =[CALayer layer];

maskLayer.frame=self.imageView.bounds;

UIImage*maskImage = [UIImage imageNamed:@"1"];

maskLayer.contents= (__bridgeid)maskImage.CGImage;//apply mask to image layer?self.imageView.layer.mask=maskLayer;

self.imageView.image=[UIImage imageNamed:@"2"];

}@end

CALayer蒙板圖層真正厲害的地方在于蒙板圖不局限于靜態(tài)圖。任何有圖層構(gòu)成的都可以作為mask屬性,這意味著你的蒙板可以通過代碼甚至是動(dòng)畫實(shí)時(shí)生成。

18.shouldRasterize

實(shí)現(xiàn)組透明的效果,如果它被設(shè)置為YES,在應(yīng)用透明度之前,圖層及其子圖層都會(huì)被整合成一個(gè)整體的圖片,這樣就沒有透明度混合的問題了;

為了啟用shouldRasterize屬性,我們?cè)O(shè)置了圖層的rasterizationScale屬性。默認(rèn)情況下,所有圖層拉伸都是1.0, 所以如果你使用了shouldRasterize屬性,你就要確保你設(shè)置了rasterizationScale屬性去匹配屏幕,以防止出現(xiàn)Retina屏幕像素化的問題。

button2.layer.shouldRasterize =YES;

button2.layer.rasterizationScale= [UIScreen mainScreen].scale;

19.變換(CGAffineTransform)

view.transform=CGAffineTransformMakeRotation(M_PI_4) <---->self.view.layer.affineTransform=CGAffineTransformMakeRotation(M_PI_4) ;

CGAffineTransformMakeRotation(CGFloat angle)

CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)

CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty)

混合變換

CGAffineTransformRotate(CGAffineTransform t, CGFloat angle)

CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy)

CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty)

參考代碼:

CGAffineTransform transform = CGAffineTransformIdentity;//scale by 50%transform = CGAffineTransformScale(transform,0.5,0.5);//rotate by 30 degreestransform = CGAffineTransformRotate(transform, M_PI /180.0*30.0);//translate by 200 pointstransform = CGAffineTransformTranslate(transform,200,0);//apply transform to layerself.layerView.layer.affineTransform = transform;

設(shè)置CGAffineTransform的矩陣值,做任意變換:

CGAffineTransform transform =CGAffineTransformIdentity;

transform.c= -x;

transform.b= y;

20.變換(CATransform3D)

對(duì)應(yīng)layer的transform屬性

CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z)

CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz)

CATransform3DMakeTranslation(Gloat tx, CGFloat ty, CGFloat tz)

transform.m34---->應(yīng)用透視效果

21.sublayerTransform

CALayer有一個(gè)屬性叫做sublayerTransform。它也是CATransform3D類型,但和對(duì)一個(gè)圖層的變換不同,它影響到所有的子圖層。這意味著你可以一次性對(duì)包含這些圖層的容器做變換,于是所有的子圖層都自動(dòng)繼承了這個(gè)變換方法。

22.doubleSided

CALayer有一個(gè)叫做doubleSided的屬性來控制圖層的背面是否要被繪制。這是一個(gè)BOOL類型,默認(rèn)為YES,如果設(shè)置為NO,那么當(dāng)圖層正面從相機(jī)視角消失的時(shí)候,它將不會(huì)被繪制。

23.CAShapeLayer

使用CAShapeLayer有以下一些優(yōu)點(diǎn):

渲染快速。CAShapeLayer使用了硬件加速,繪制同一圖形會(huì)比用Core Graphics快很多。

高效使用內(nèi)存。一個(gè)CAShapeLayer不需要像普通CALayer一樣創(chuàng)建一個(gè)寄宿圖形,所以無(wú)論有多大,都不會(huì)占用太多的內(nèi)存。

不會(huì)被圖層邊界剪裁掉。一個(gè)CAShapeLayer可以在邊界之外繪制。你的圖層路徑不會(huì)像在使用Core Graphics的普通CALayer一樣被剪裁掉(如我們?cè)诘诙滤姡?/p>

不會(huì)出現(xiàn)像素化。當(dāng)你給CAShapeLayer做3D變換時(shí),它不像一個(gè)有寄宿圖的普通圖層一樣變得像素化。

24.CATextLayer

CATextLayer比UILabel有著更好的性能表現(xiàn),同時(shí)還有額外的布局選項(xiàng)并且在iOS 5上支持富文本。

讓我們編輯一下示例使用到NSAttributedString(見清單6.3).iOS 6及以上我們可以用新的NSTextAttributeName實(shí)例來設(shè)置我們的字符串屬性,但是練習(xí)的目的是為了演示在iOS 5及以下,所以我們用了Core Text,也就是說你需要把Core Text framework添加到你的項(xiàng)目中。否則,編譯器是無(wú)法識(shí)別屬性常量的。

UILabel的替代品

每一個(gè)UIView都是寄宿在一個(gè)CALayer的示例上。這個(gè)圖層是由視圖自動(dòng)創(chuàng)建和管理的,那我們可以用別的圖層類型替代它么?一旦被創(chuàng)建,我們就無(wú)法代替這個(gè)圖層了。但是如果我們繼承了UIView,那我們就可以重寫+layerClass方法使得在創(chuàng)建的時(shí)候能返回一個(gè)不同的圖層子類。UIView會(huì)在初始化的時(shí)候調(diào)用+layerClass方法,然后用它的返回類型來創(chuàng)建宿主圖層。

把CATextLayer作為宿主圖層的另一好處就是視圖自動(dòng)設(shè)置了contentsScale屬性。

25.CATransformLayer

在立方體示例,我們將通過旋轉(zhuǎn)camara來解決圖層平面化問題而不是像立方體示例代碼中用的sublayerTransform。這是一個(gè)非常不錯(cuò)的技巧,但是只能作用域單個(gè)對(duì)象上,如果你的場(chǎng)景包含兩個(gè)立方體,那我們就不能用這個(gè)技巧單獨(dú)旋轉(zhuǎn)他們了。

CATransformLayer不同于普通的CALayer,因?yàn)樗荒茱@示它自己的內(nèi)容。只有當(dāng)存在了一個(gè)能作用域子圖層的變換它才真正存在。CATransformLayer并不平面化它的子圖層,所以它能夠用于構(gòu)造一個(gè)層級(jí)的3D結(jié)構(gòu)。

26.CAGradientLayer

CAGradientLayer是用來生成兩種或更多顏色平滑漸變的。用Core Graphics復(fù)制一個(gè)CAGradientLayer并將內(nèi)容繪制到一個(gè)普通圖層的寄宿圖也是有可能的,但是CAGradientLayer的真正好處在于繪制使用了硬件加速。

27.CAReplicatorLayer

CAReplicatorLayer的目的是為了高效生成許多相似的圖層。它會(huì)繪制一個(gè)或多個(gè)圖層的子圖層,并在每個(gè)復(fù)制體上應(yīng)用不同的變換。

instanceCount屬性指定了圖層需要重復(fù)多少次。instanceTransform指定了一個(gè)CATransform3D 3D變換(這種情況下,下一圖層的位移和旋轉(zhuǎn)將會(huì)移動(dòng)到圓圈的下一個(gè)點(diǎn))。變換是逐步增加的,每個(gè)實(shí)例都是相對(duì)于前一實(shí)例布局。這就是為什么這些復(fù)制體最終不會(huì)出現(xiàn)在同一位置上;

CAReplicatorLayer真正應(yīng)用到實(shí)際程序上的場(chǎng)景比如:一個(gè)游戲中導(dǎo)彈的軌跡云,或者粒子爆炸。除此之外,還有一個(gè)實(shí)際應(yīng)用是:反射。

28.CAScrollLayer

CAScrollLayer有一個(gè)-scrollToPoint:方法,它自動(dòng)適應(yīng)bounds的原點(diǎn)以便圖層內(nèi)容出現(xiàn)在滑動(dòng)的地方。注意,這就是它做的所有事情。前面提到過,Core Animation并不處理用戶輸入,所以CAScrollLayer并不負(fù)責(zé)將觸摸事件轉(zhuǎn)換為滑動(dòng)事件,既不渲染滾動(dòng)條,也不實(shí)現(xiàn)任何iOS指定行為例如滑動(dòng)反彈(當(dāng)視圖滑動(dòng)超多了它的邊界的將會(huì)反彈回正確的地方)。




1.CATransaction

事務(wù);

UIView有兩個(gè)方法,+beginAnimations:context:和+commitAnimations,和CATransaction的+begin 和+commit方法類似。實(shí)際上在+beginAnimations:context:和+commitAnimations之間所有視圖或者圖層屬性的改變而做的動(dòng)畫都是由于設(shè)置了CATransaction的原因。

UIView封裝動(dòng)畫中的塊動(dòng)畫,對(duì)做一堆的屬性動(dòng)畫在語(yǔ)法上會(huì)更加簡(jiǎn)單,但實(shí)質(zhì)上它們都是在做同樣的事情。

基于UIView的block的動(dòng)畫允許你在動(dòng)畫結(jié)束的時(shí)候提供一個(gè)完成的動(dòng)作。CATranscation接口提供的+setCompletionBlock:方法也有同樣的功能

2.屬性動(dòng)畫實(shí)質(zhì):

當(dāng)CALayer的屬性被修改時(shí)候,它會(huì)調(diào)用-actionForKey:方法,傳遞屬性的名稱。剩下的操作都在CALayer的頭文件中有詳細(xì)的說明,實(shí)質(zhì)上是如下幾步:

圖層首先檢測(cè)它是否有委托,并且是否實(shí)現(xiàn)CALayerDelegate協(xié)議指定的-actionForLayer:forKey方法。如果有,直接調(diào)用并返回結(jié)果。

如果沒有委托,或者委托沒有實(shí)現(xiàn)-actionForLayer:forKey方法,圖層接著檢查包含屬性名稱對(duì)應(yīng)行為映射的actions字典。

如果actions字典沒有包含對(duì)應(yīng)的屬性,那么圖層接著在它的style字典接著搜索屬性名。

最后,如果在style里面也找不到對(duì)應(yīng)的行為,那么圖層將會(huì)直接調(diào)用定義了每個(gè)屬性的標(biāo)準(zhǔn)行為的-defaultActionForKey:方法。

所以一輪完整的搜索結(jié)束之后,-actionForKey:要么返回空(這種情況下將不會(huì)有動(dòng)畫發(fā)生),要么是CAAction協(xié)議對(duì)應(yīng)的對(duì)象,最后CALayer拿這個(gè)結(jié)果去對(duì)先前和當(dāng)前的值做動(dòng)畫。

通過下面一段代碼,可以理解每個(gè)UIView對(duì)它關(guān)聯(lián)的圖層是如何禁用隱式動(dòng)畫的

- (void)viewDidLoad

{

[super viewDidLoad];

NSLog(@"Outside: %@", [self.view actionForLayer:self.view.layer forKey:@"backgroundColor"]);

[UIView beginAnimations:nil context:nil];

NSLog(@"Inside: %@", [self.view actionForLayer:self.view.layer forKey:@"backgroundColor"]);

[UIView commitAnimations];

}

運(yùn)行結(jié)果:

2016-05-3014:48:18.614測(cè)試[10222:147396] Outside: 2016-05-3014:48:18.615測(cè)試[10222:147396] Inside:

總結(jié)一下,我們知道了如下幾點(diǎn)

UIView 關(guān)聯(lián)的圖層禁用了隱式動(dòng)畫,對(duì)這種圖層做動(dòng)畫的唯一辦法就是使用UIView的動(dòng)畫函數(shù)(而不是依賴CATransaction),或者繼承 UIView,并覆蓋-actionForLayer:forKey:方法,或者直接創(chuàng)建一個(gè)顯式動(dòng)畫(具體細(xì)節(jié)見第八章)。

對(duì)于單獨(dú)存在的圖層,我們可以通過實(shí)現(xiàn)圖層的-actionForLayer:forKey:委托方法,或者提供一個(gè)actions字典來控制隱式動(dòng)畫。

3.CATransition

CATransition繼承CAAnimation,而CAAnimation響應(yīng)CAAction協(xié)議;

CATransition *transition =[CATransition animation];

transition.type=kCATransitionPush;

transition.subtype=kCATransitionFromLeft;

self.colorLayer.actions= @{@"backgroundColor": transition};

這種方法,只是針對(duì)單獨(dú)的圖層,不能用在UIView關(guān)聯(lián)的圖層;

4.presentationLayer

每個(gè)圖層屬性的顯示值都被存儲(chǔ)在一個(gè)叫做呈現(xiàn)圖層的獨(dú)立圖層當(dāng)中,他可以通過-presentationLayer方法來訪問。這個(gè)呈現(xiàn)圖層實(shí)際上是模型圖層的復(fù)制,但是它的屬性值代表了在任何指定時(shí)刻當(dāng)前外觀效果。換句話說,你可以通過呈現(xiàn)圖層的值來獲取當(dāng)前屏幕上真正顯示出來的值

注意呈現(xiàn)圖層僅僅當(dāng)圖層首次被提交(就是首次第一次在屏幕上顯示)的時(shí)候創(chuàng)建,所以在那之前調(diào)用-presentationLayer將會(huì)返回nil。

在呈現(xiàn)圖層上調(diào)用–modelLayer將會(huì)返回它正在呈現(xiàn)所依賴的CALayer。通常在一個(gè)圖層上調(diào)用-modelLayer會(huì)返回–self(實(shí)際上我們已經(jīng)創(chuàng)建的原始圖層就是一種數(shù)據(jù)模型)。

5.顯式動(dòng)畫

在iOS中核心動(dòng)畫分為幾類:基礎(chǔ)動(dòng)畫、關(guān)鍵幀動(dòng)畫、動(dòng)畫組、轉(zhuǎn)場(chǎng)動(dòng)畫。各個(gè)類的關(guān)系大致如下:

CAAnimation:核心動(dòng)畫的基礎(chǔ)類,不能直接使用,負(fù)責(zé)動(dòng)畫運(yùn)行時(shí)間、速度的控制。CAAnimation本身并沒有做多少工作,它提供了一個(gè)計(jì)時(shí)函數(shù),一個(gè)委托(用于反饋動(dòng)畫狀態(tài))以及一個(gè) removedOnCompletion,用于標(biāo)識(shí)動(dòng)畫是否該在結(jié)束后自動(dòng)釋放(默認(rèn)YES,為了防止內(nèi)存泄露)。CAAnimation同時(shí)實(shí)現(xiàn)了一些 協(xié)議,包括CAAction(允許CAAnimation的子類可以提供圖層行為),以及CAMediaTiming

CAPropertyAnimation:屬性動(dòng)畫的基類(通過屬性進(jìn)行動(dòng)畫設(shè)置,注意是可動(dòng)畫屬性),不能直接使用。通過指定動(dòng)畫的keyPath作用于一個(gè)單一屬性,CAAnimation通常應(yīng)用于一個(gè)指定的CALayer,于是這里指的也就是一個(gè)圖層的 keyPath了。實(shí)際上它是一個(gè)關(guān)鍵路徑(一些用點(diǎn)表示法可以在層級(jí)關(guān)系中指向任意嵌套的對(duì)象),而不僅僅是一個(gè)屬性的名稱,因?yàn)檫@意味著動(dòng)畫不僅可以 作用于圖層本身的屬性,而且還包含了它的子成員的屬性,甚至是一些虛擬的屬性

CAAnimationGroup:動(dòng)畫組,動(dòng)畫組是一種組合模式設(shè)計(jì),可以通過動(dòng)畫組來進(jìn)行所有動(dòng)畫行為的統(tǒng)一控制,組中所有動(dòng)畫效果可以并發(fā)執(zhí)行。

CATransition:過渡(轉(zhuǎn)場(chǎng))動(dòng)畫,主要通過濾鏡進(jìn)行動(dòng)畫效果設(shè)置。過渡并不像屬性動(dòng)畫那樣平滑地在兩個(gè)值之間做動(dòng)畫,而是影響到整個(gè)圖層的變化。過渡動(dòng)畫首先展示之前的圖層外觀,然后通過一個(gè)交換過渡到新的外觀。

CABasicAnimation:基礎(chǔ)動(dòng)畫,通過屬性修改進(jìn)行動(dòng)畫參數(shù)控制,只有初始狀態(tài)和結(jié)束狀態(tài)。

CAKeyframeAnimation:關(guān)鍵幀動(dòng)畫,同樣是通過屬性進(jìn)行動(dòng)畫參數(shù)控制,但是同基礎(chǔ)動(dòng)畫不同的是它可以有多個(gè)狀態(tài)控制。CAKeyFrameAnimation添加了一個(gè)rotationMode的屬性。設(shè)置它為常量kCAAnimationRotateAuto,圖層將會(huì)根據(jù)曲線的切線自動(dòng)旋轉(zhuǎn)

基礎(chǔ)動(dòng)畫、關(guān)鍵幀動(dòng)畫都屬于屬性動(dòng)畫,就是通過修改屬性值產(chǎn)生動(dòng)畫效果,開發(fā)人員只需要設(shè)置初始值和結(jié)束值,中間的過程動(dòng)畫(又叫“補(bǔ)間動(dòng)畫”)由 系統(tǒng)自動(dòng)計(jì)算產(chǎn)生。和基礎(chǔ)動(dòng)畫不同的是關(guān)鍵幀動(dòng)畫可以設(shè)置多個(gè)屬性值,每?jī)蓚€(gè)屬性中間的補(bǔ)間動(dòng)畫由系統(tǒng)自動(dòng)完成,因此從這個(gè)角度而言基礎(chǔ)動(dòng)畫又可以看成是 有兩個(gè)關(guān)鍵幀的關(guān)鍵幀動(dòng)畫。

- (void)applyBasicAnimation:(CABasicAnimation *)animation toLayer:(CALayer *)layer

{//set the from value (using presentation layer if available)animation.fromValue = [layer.presentationLayer ?: layer valueForKeyPath:animation.keyPath];//update the property in advance//note: this approach will only work if toValue != nil[CATransaction begin];

[CATransaction setDisableActions:YES];

[layer setValue:animation.toValue forKeyPath:animation.keyPath];

[CATransaction commit];//apply animation to layer[layer addAnimation:animation forKey:nil];

}-(IBAction)changeColor

{//create a new random colorCGFloat red = arc4random() /(CGFloat)INT_MAX;

CGFloat green= arc4random() /(CGFloat)INT_MAX;

CGFloat blue= arc4random() /(CGFloat)INT_MAX;

UIColor*color = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];//create a basic animationCABasicAnimation *animation =[CABasicAnimation animation];

animation.keyPath=@"backgroundColor";

animation.toValue= (__bridgeid)color.CGColor;//apply animation without snap-back[self applyBasicAnimation:animation toLayer:self.colorLayer];

}

6.虛擬屬性

CABasicAnimation *animation =[CABasicAnimation animation];

animation.keyPath=@"transform.rotation";

animation.duration=2.0;

animation.byValue= @(M_PI *2);

[shipLayer addAnimation:animation forKey:nil];

用transform.rotation而不是transform做動(dòng)畫的好處如下:

我們可以不通過關(guān)鍵幀一步旋轉(zhuǎn)多于180度的動(dòng)畫。

可以用相對(duì)值而不是絕對(duì)值旋轉(zhuǎn)(設(shè)置byValue而不是toValue)。

可以不用創(chuàng)建CATransform3D,而是使用一個(gè)簡(jiǎn)單的數(shù)值來指定角度。

不會(huì)和transform.position或者transform.scale沖突(同樣是使用關(guān)鍵路徑來做獨(dú)立的動(dòng)畫屬性)。

transform.rotation 屬性有一個(gè)奇怪的問題是它其實(shí)并不存在。這是因?yàn)镃ATransform3D并不是一個(gè)對(duì)象,它實(shí)際上是一個(gè)結(jié)構(gòu)體,也沒有符合KVC相關(guān)屬 性,transform.rotation實(shí)際上是一個(gè)CALayer用于處理動(dòng)畫變換的虛擬屬性。

7.動(dòng)畫組

//create group animationCAAnimationGroup *groupAnimation =[CAAnimationGroup animation];

groupAnimation.animations=@[animation1, animation2];

groupAnimation.duration=4.0;//add the animation to the color layer[colorLayer addAnimation:groupAnimation forKey:nil];

8.過渡動(dòng)畫

CAAnimation有一個(gè)type和subtype來標(biāo)識(shí)變換效果。type屬性是一個(gè)NSString類型,可以被設(shè)置成如下類型:

kCATransitionFade

kCATransitionMoveIn

kCATransitionPush

kCATransitionReveal

通過subtype來控制它們的方向,提供了如下四種類型:

kCATransitionFromRight

kCATransitionFromLeft

kCATransitionFromTop

kCATransitionFromBottom

隱式過渡

CATransision 可以對(duì)圖層任何變化平滑過渡的事實(shí)使得它成為那些不好做動(dòng)畫的屬性圖層行為的理想候選。蘋果當(dāng)然意識(shí)到了這點(diǎn),并且當(dāng)設(shè)置了CALayer的 content屬性的時(shí)候,CATransition的確是默認(rèn)的行為。但是對(duì)于視圖關(guān)聯(lián)的圖層,或者是其他隱式動(dòng)畫的行為,這個(gè)特性依然是被禁用的,但 是對(duì)于你自己創(chuàng)建的圖層,這意味著對(duì)圖層contents圖片做的改動(dòng)都會(huì)自動(dòng)附上淡入淡出的動(dòng)畫。

對(duì)圖層樹的動(dòng)畫

CATransition并不作用于指定的圖層屬性,這就是說你可以在即使不能準(zhǔn)確得知改變了什么的情況下對(duì)圖層做動(dòng)畫,例如,在不知道 UITableView哪一行被添加或者刪除的情況下,直接就可以平滑地刷新它,或者在不知道UIViewController內(nèi)部的視圖層級(jí)的情況下對(duì) 兩個(gè)不同的實(shí)例做過渡動(dòng)畫。

這些例子和我們之前所討論的情況完全不同,因?yàn)樗鼈儾簧婕暗綀D層的屬性,而且是整個(gè)圖層樹的改變--我們?cè)谶@種動(dòng)畫的過程中手動(dòng)在層級(jí)關(guān)系中添加或者移除圖層。

這里用到了一個(gè)小詭計(jì),要確保CATransition添加到的圖層在過渡動(dòng)畫發(fā)生時(shí)不會(huì)在樹狀結(jié)構(gòu)中被移除,否則CATransition將會(huì)和圖層一起被移除。一般來說,你只需要將動(dòng)畫添加到被影響圖層的superlayer。

- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController

{//set up crossfade transitionCATransition *transition =[CATransition animation];

transition.type=kCATransitionFade;//apply transition to tab bar controller's view[self.tabBarController.view.layer addAnimation:transition forKey:nil];

}

自定義動(dòng)畫

奇怪的是蘋果通過UIView +transitionFromView:toView:duration:options:completion: 和+transitionWithView:duration:options:animations:方法提供了Core Animation的過渡特性。但是這里的可用的過渡選項(xiàng)和CATransition的type屬性提供的常量完全不同。UIView過渡方法中 options參數(shù)可以由如下常量指定:

UIViewAnimationOptionTransitionFlipFromLeft

UIViewAnimationOptionTransitionFlipFromRight

UIViewAnimationOptionTransitionCurlUp

UIViewAnimationOptionTransitionCurlDown

UIViewAnimationOptionTransitionCrossDissolve

UIViewAnimationOptionTransitionFlipFromTop

UIViewAnimationOptionTransitionFlipFromBottom

過渡動(dòng)畫做基礎(chǔ)的原則就是對(duì)原始的圖層外觀截圖,然后添加一段動(dòng)畫,平滑過渡到圖層改變之后那個(gè)截圖的效果。如果我們知道如何對(duì)圖層截圖,我們就可以使用屬性動(dòng)畫來代替CATransition或者是UIKit的過渡方法來實(shí)現(xiàn)動(dòng)畫。

事實(shí)證明,對(duì)圖層做截圖還是很簡(jiǎn)單的。CALayer有一個(gè)-renderInContext:方法,可以通過把它繪制到Core Graphics的上下文中捕獲當(dāng)前內(nèi)容的圖片,然后在另外的視圖中顯示出來。如果我們把這個(gè)截屏視圖置于原始視圖之上,就可以遮住真實(shí)視圖的所有變化, 于是重新創(chuàng)建了一個(gè)簡(jiǎn)單的過渡效果。

清單8.14演示了一個(gè)基本的實(shí)現(xiàn)。我們對(duì)當(dāng)前視圖狀態(tài)截圖,然后在我們改變?cè)家晥D的背景色的時(shí)候?qū)貓D快速轉(zhuǎn)動(dòng)并且淡出,圖8.5展示了我們自定義的過渡效果。

為 了讓事情更簡(jiǎn)單,我們用UIView -animateWithDuration:completion:方法來實(shí)現(xiàn)。雖然用CABasicAnimation可以達(dá)到同樣的效果,但是那樣的 話我們就需要對(duì)圖層的變換和不透明屬性創(chuàng)建單獨(dú)的動(dòng)畫,然后當(dāng)動(dòng)畫結(jié)束的是哦戶在CAAnimationDelegate中把coverView從屏幕中 移除。

清單8.14 用renderInContext:創(chuàng)建自定義過渡效果

-(IBAction)performTransition

{//preserve the current view snapshotUIGraphicsBeginImageContextWithOptions(self.view.bounds.size, YES,0.0);

[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];

UIImage*coverImage =UIGraphicsGetImageFromCurrentImageContext();//insert snapshot view in front of this oneUIView *coverView =[[UIImageView alloc] initWithImage:coverImage];

coverView.frame=self.view.bounds;

[self.view addSubview:coverView];//update the view (we'll simply randomize the layer background color)CGFloat red = arc4random() /(CGFloat)INT_MAX;

CGFloat green= arc4random() /(CGFloat)INT_MAX;

CGFloat blue= arc4random() /(CGFloat)INT_MAX;

self.view.backgroundColor= [UIColor colorWithRed:red green:green blue:blue alpha:1.0];//perform animation (anything you like)[UIView animateWithDuration:1.0animations:^{//scale, rotate and fade the viewCGAffineTransform transform = CGAffineTransformMakeScale(0.01,0.01);

transform=CGAffineTransformRotate(transform, M_PI_2);

coverView.transform=transform;

coverView.alpha=0.0;

} completion:^(BOOL finished) {//remove the cover view now we're finished with it[coverView removeFromSuperview];

}];

}

CAMediaTiming協(xié)議

duration

repeatCount

repeatDuration

autoreverses

注意repeatCount和repeatDuration可能會(huì)相互沖突,所以你只要對(duì)其中一個(gè)指定非零值。對(duì)兩個(gè)屬性都設(shè)置非0值的行為沒有被定義。

beginTime

指定了動(dòng)畫開始之前的的延遲時(shí)間。這里的延遲從動(dòng)畫添加到可見圖層的那一刻開始測(cè)量,默認(rèn)是0(就是說動(dòng)畫會(huì)立刻執(zhí)行)。

speed

是一個(gè)時(shí)間的倍數(shù),默認(rèn)1.0,減少它會(huì)減慢圖層/動(dòng)畫的時(shí)間,增加它會(huì)加快速度。如果2.0的速度,那么對(duì)于一個(gè)duration為1的動(dòng)畫,實(shí)際上在0.5秒的時(shí)候就已經(jīng)完成了。

timeOffset

和beginTime類似,但是和增加beginTime導(dǎo)致的延遲動(dòng)畫不同,增加timeOffset只是讓動(dòng)畫快進(jìn)到某一點(diǎn),例如,對(duì)于一個(gè)持續(xù)1秒的動(dòng)畫來說,設(shè)置timeOffset為0.5意味著動(dòng)畫將從一半的地方開始。

fillMode

fillMode是一個(gè)NSString類型,可以接受如下四種常量:

kCAFillModeForwards

kCAFillModeBackwards

kCAFillModeBoth

kCAFillModeRemoved

默認(rèn)是kCAFillModeRemoved,當(dāng)動(dòng)畫不再播放的時(shí)候就顯示圖層模型指定的值剩下的三種類型向前,向后或者即向前又向后去填充動(dòng)畫狀態(tài),使得動(dòng)畫在開始前或者結(jié)束后仍然保持開始和結(jié)束那一刻的值。

這就對(duì)避免在動(dòng)畫結(jié)束的時(shí)候急速返回提供另一種方案(見第八章)。但是記住了,當(dāng)用它來解決這個(gè)問題的時(shí)候,需要把removeOnCompletion設(shè)置為NO,另外需要給動(dòng)畫添加一個(gè)非空的鍵,于是可以在不需要?jiǎng)赢嫷臅r(shí)候把它從圖層上移除。

暫停,倒回和快進(jìn)(圖層的speed)

設(shè)置動(dòng)畫的speed屬性為0可以暫停動(dòng)畫,但在動(dòng)畫被添加到圖層之后不太可能再修改它了,所以不能對(duì)正在進(jìn)行的動(dòng)畫使用這個(gè)屬性。給圖層添加一個(gè) CAAnimation實(shí)際上是給動(dòng)畫對(duì)象做了一個(gè)不可改變的拷貝,所以對(duì)原始動(dòng)畫對(duì)象屬性的改變對(duì)真實(shí)的動(dòng)畫并沒有作用。相反,直接用 -animationForKey:來檢索圖層正在進(jìn)行的動(dòng)畫可以返回正確的動(dòng)畫對(duì)象,但是修改它的屬性將會(huì)拋出異常。

如果移除圖層正在進(jìn)行的動(dòng)畫,圖層將會(huì)急速返回動(dòng)畫之前的狀態(tài)。但如果在動(dòng)畫移除之前拷貝呈現(xiàn)圖層到模型圖層,動(dòng)畫將會(huì)看起來暫停在那里。但是不好的地方在于之后就不能再恢復(fù)動(dòng)畫了。

一個(gè)簡(jiǎn)單的方法是可以利用CAMediaTiming來暫停圖層本身。如果把圖層的speed設(shè)置成0,它會(huì)暫停任何添加到圖層上的動(dòng)畫。類似的,設(shè)置speed大于1.0將會(huì)快進(jìn),設(shè)置成一個(gè)負(fù)值將會(huì)倒回動(dòng)畫。

通過增加主窗口圖層的speed,可以暫停整個(gè)應(yīng)用程序的動(dòng)畫。這對(duì)UI自動(dòng)化提供了好處,我們可以加速所有的視圖動(dòng)畫來進(jìn)行自動(dòng)化測(cè)試(注意對(duì)于在主窗口之外的視圖并不會(huì)被影響,比如UIAlertview)。可以在app delegate設(shè)置如下進(jìn)行驗(yàn)證:

self.window.layer.speed =100;

你也可以通過這種方式來減速,但其實(shí)也可以在模擬器通過切換慢速動(dòng)畫來實(shí)現(xiàn)。

手動(dòng)動(dòng)畫(圖層的timeOffset)

timeOffset一個(gè)很有用的功能在于你可以它可以讓你手動(dòng)控制動(dòng)畫進(jìn)程,通過設(shè)置speed為0,可以禁用動(dòng)畫的自動(dòng)播放,然后來使用timeOffset來來回顯示動(dòng)畫序列。這可以使得運(yùn)用手勢(shì)來手動(dòng)控制動(dòng)畫變得很簡(jiǎn)單。

@property (nonatomic, strong) CALayer *doorLayer;@end@implementationViewController- (void)viewDidLoad

{

[super viewDidLoad];//add the doorself.doorLayer =[CALayer layer];

self.doorLayer.frame= CGRectMake(0,0,128,256);

self.doorLayer.position= CGPointMake(150-64,150);

self.doorLayer.anchorPoint= CGPointMake(0,0.5);

self.doorLayer.contents= (__bridgeid)[UIImage imageNamed:@"bjl_list_02"].CGImage;

[self.view.layer addSublayer:self.doorLayer];//apply perspective transformCATransform3D perspective =CATransform3DIdentity;

perspective.m34= -1.0/500.0;

self.view.layer.sublayerTransform=perspective;//add pan gesture recognizer to handle swipesUIPanGestureRecognizer *pan =[[UIPanGestureRecognizer alloc] init];

[pan addTarget:self action:@selector(pan:)];

[self.view addGestureRecognizer:pan];//pause all layer animationsself.doorLayer.speed =0.0;//apply swinging animation (which won't play because layer is paused)CABasicAnimation *animation =[CABasicAnimation animation];

animation.keyPath=@"transform.rotation.y";

animation.toValue= @(-M_PI_2);

animation.duration=1.0;

[self.doorLayer addAnimation:animation forKey:nil];

}

- (void)pan:(UIPanGestureRecognizer *)pan

{//get horizontal component of pan gestureCGFloat x =[pan translationInView:self.view].x;//convert from points to animation duration//using a reasonable scale factorx /=200.0f;//update timeOffset and clamp resultCFTimeInterval timeOffset =self.doorLayer.timeOffset;

timeOffset= MIN(0.999, MAX(0.0, timeOffset -x));

self.doorLayer.timeOffset=timeOffset;//reset pan gesture[pan setTranslation:CGPointZero inView:self.view];

}


CPU VS GPU

動(dòng)畫和屏幕上組合的圖層實(shí)際上被一個(gè)單獨(dú)的進(jìn)程管理,而不是你的應(yīng)用程序。這個(gè)進(jìn)程就是所謂的渲染服務(wù)。在iOS5和之前的版本是SpringBoard進(jìn)程(同時(shí)管理著iOS的主屏)。在iOS6之后的版本中叫做BackBoard。

一件重要的事情就是性能測(cè)試一定要用發(fā)布配置,而不是調(diào)試模式。因?yàn)楫?dāng)用發(fā)布環(huán)境打包的時(shí)候,編譯器會(huì)引入一系列提高性能的優(yōu)化,例如去掉調(diào)試符號(hào)或者移 除并重新組織代碼。你也可以自己做到這些,例如在發(fā)布環(huán)境禁用NSLog語(yǔ)句。你只關(guān)心發(fā)布性能,那才是你需要測(cè)試的點(diǎn)。

Core Animation工具也提供了一系列復(fù)選框選項(xiàng)來幫助調(diào)試渲染瓶頸:

Color Blended Layers - 這個(gè)選項(xiàng)基于渲染程度對(duì)屏幕中的混合區(qū)域進(jìn)行綠到紅的高亮(也就是多個(gè)半透明圖層的疊加)。由于重繪的原因,混合對(duì)GPU性能會(huì)有影響,同時(shí)也是滑動(dòng)或者動(dòng)畫幀率下降的罪魁禍?zhǔn)字弧?/p>

ColorHitsGreenandMissesRed - 當(dāng)使用shouldRasterizep屬性的時(shí)候,耗時(shí)的圖層繪制會(huì)被緩存,然后當(dāng)做一個(gè)簡(jiǎn)單的扁平圖片呈現(xiàn)。當(dāng)緩存再生的時(shí)候這個(gè)選項(xiàng)就用紅色對(duì)柵格 化圖層進(jìn)行了高亮。如果緩存頻繁再生的話,就意味著柵格化可能會(huì)有負(fù)面的性能影響了(更多關(guān)于使用shouldRasterize的細(xì)節(jié)見第15章“圖層 性能”)。

Color Copied Images - 有時(shí)候寄宿圖片的生成意味著Core Animation被強(qiáng)制生成一些圖片,然后發(fā)送到渲染服務(wù)器,而不是簡(jiǎn)單的指向原始指針。這個(gè)選項(xiàng)把這些圖片渲染成藍(lán)色。復(fù)制圖片對(duì)內(nèi)存和CPU使用來 說都是一項(xiàng)非常昂貴的操作,所以應(yīng)該盡可能的避免。

Color Immediately - 通常Core Animation Instruments以每毫秒10次的頻率更新圖層調(diào)試顏色。對(duì)某些效果來說,這顯然太慢了。這個(gè)選項(xiàng)就可以用來設(shè)置每幀都更新(可能會(huì)影響到渲染性 能,而且會(huì)導(dǎo)致幀率測(cè)量不準(zhǔn),所以不要一直都設(shè)置它)。

Color Misaligned Images - 這里會(huì)高亮那些被縮放或者拉伸以及沒有正確對(duì)齊到像素邊界的圖片(也就是非整型坐標(biāo))。這些中的大多數(shù)通常都會(huì)導(dǎo)致圖片的不正常縮放,如果把一張大圖當(dāng)縮 略圖顯示,或者不正確地模糊圖像,那么這個(gè)選項(xiàng)將會(huì)幫你識(shí)別出問題所在。

Color Offscreen-Rendered Yellow - 這里會(huì)把那些需要離屏渲染的圖層高亮成黃色。這些圖層很可能需要用shadowPath或者shouldRasterize來優(yōu)化。

Color OpenGL Fast Path Blue - 這個(gè)選項(xiàng)會(huì)對(duì)任何直接使用OpenGL繪制的圖層進(jìn)行高亮。如果僅僅使用UIKit或者Core Animation的API,那么不會(huì)有任何效果。如果使用GLKView或者CAEAGLLayer,那如果不顯示藍(lán)色塊的話就意味著你正在強(qiáng)制CPU 渲染額外的紋理,而不是繪制到屏幕。

Flash Updated Regions - 這個(gè)選項(xiàng)會(huì)對(duì)重繪的內(nèi)容高亮成黃色(也就是任何在軟件層面使用Core Graphics繪制的圖層)。這種繪圖的速度很慢。如果頻繁發(fā)生這種情況的話,這意味著有一個(gè)隱藏的bug或者說通過增加緩存或者使用替代方案會(huì)有提升 性能的空間。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,143評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,416評(píng)論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,940評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,170評(píng)論 0 287
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,709評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,597評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,784評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評(píng)論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,029評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評(píng)論 1 280
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,403評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,746評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容

  • 在iOS中隨處都可以看到絢麗的動(dòng)畫效果,實(shí)現(xiàn)這些動(dòng)畫的過程并不復(fù)雜,今天將帶大家一窺ios動(dòng)畫全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,537評(píng)論 6 30
  • 在iOS中隨處都可以看到絢麗的動(dòng)畫效果,實(shí)現(xiàn)這些動(dòng)畫的過程并不復(fù)雜,今天將帶大家一窺iOS動(dòng)畫全貌。在這里你可以看...
    F麥子閱讀 5,130評(píng)論 5 13
  • Core Animation其實(shí)是一個(gè)令人誤解的命名。你可能認(rèn)為它只是用來做動(dòng)畫的,但實(shí)際上它是從一個(gè)叫做Laye...
    小貓仔閱讀 3,747評(píng)論 1 4
  • 前言 本文只要描述了iOS中的Core Animation(核心動(dòng)畫:隱式動(dòng)畫、顯示動(dòng)畫)、貝塞爾曲線、UIVie...
    GitHubPorter閱讀 3,644評(píng)論 7 11
  • 書寫的很好,翻譯的也棒!感謝譯者,感謝感謝! iOS-Core-Animation-Advanced-Techni...
    錢噓噓閱讀 2,308評(píng)論 0 6