iOS動畫-CALayer

一扫沼、前言

Core Animation 主要依賴于CALayer的對象layer來進行一切與動畫有關的操作。在view中,layer包含了view的頁面信息充甚,幾何信息(寬高以政,圓角等)還有一些視覺屬性霸褒。layer無法單獨顯示內容伴找,一個layer僅僅只是管理了一個bitmap的狀態(tài)信息,而bitmap可以作為用戶定義的一個view或者多個view混合形成的image的返回結果废菱。layer主要是管理數據技矮,因此在app中使用layer,可以將它當中和model一樣的對象使用殊轴。

通過CALayer可以實現以下功能:
移動衰倦、縮放、旋轉旁理、變形樊零、圓角、透明度孽文、陰影驻襟,帶顏色的邊框、不規(guī)則圖形實現芋哭、透明遮罩沉衣、多級非線性動畫。

二减牺、CALayer

CALayer和UIView有些相似豌习,是一些被層級關系樹管理著的矩形塊,同時也可以添加subLayer拔疚,唯一的區(qū)別是CALayer不處理交互事件肥隆。

CALayer的層級,與UIView相似

2.1CALayer tree

Core Animation 使用三種類型的layer tree對象來實現動畫:

  • model layer tree(模型層樹)

Objects in the model layer tree (or simply “l(fā)ayer tree”) are the ones your app interacts with the most. The objects in this tree are the model objects that store the target values for any animations. Whenever you change the property of a layer, you use one of these objects.

模型層樹是于app管理最密切的稚失,這個層樹里面的model對象存儲了所有與動畫有關所有值信息栋艳,改變layer的值的時候就是改變這些對象的值。

  • presentation tree(表示層樹)

Objects in the presentation tree contain the in-flight values for any running animations. Whereas the layer tree objects contain the target values for an animation, the objects in the presentation tree reflect the current values as they appear onscreen. You should never modify the objects in this tree. Instead, you use these objects to read current animation values, perhaps to create a new animation starting at those values.

表示層樹的對象包含了任何正在進行的動畫的值墩虹,是當前值在屏幕上的映射嘱巾。在使用動畫時,最好不要更改這些對象诫钓,而是使用這些對象讀取當前動畫相關值旬昭,在這些值的基礎上創(chuàng)建一個新的動畫。

  • refer tree(渲染層樹)

Objects in the render tree perform the actual animations and are private to Core Animation.

渲染樹是動畫的真實執(zhí)行者菌湃,但是它是私有的问拘,開發(fā)者不能調用。

執(zhí)行動畫時,app主要與layer tree對象通信,偶爾會獲取通過presentationLayer屬性獲取presentation tree中與之響應的對象骤坐。通過presentationLayer,開發(fā)者可以在動畫過程中讀取到presentation layer的屬性值绪杏。

當改變layer的值的時候,layer-tree的值會馬上改變纽绍,通過render-tree渲染蕾久,presentation-tree以動畫的形式展現layer的某個屬性值的漸變過程。

layer trees之間的通信

Important: You should access objects in the presentation tree only while an animation is in flight. While an animation is in progress, the presentation tree contains the layer values as they appear onscreen at that instant. This behavior differs from the layer tree, which always reflects the last value set by your code and is equivalent to the final state of the animation.

如果在動畫中的任意時刻拌夏,查看 layer 的 opacity值僧著,你是得不到與屏幕內容對應的透明度的。取而代之障簿,你需要查看 presentationLayeropacity 以獲得正確的結果盹愚。

2.2CALayer的屬性

屬性表
  • layer中動畫很少使用frame,用的是bounds和position
  • 設置透明度不是alpha站故,而是opacity

anchorPoint

Geometry related manipulations of a layer occur relative to that layer’s anchor point皆怕,The impact of the anchor point is most noticeable when manipulating the position or transform properties of the layer.

layer的幾何操作都與錨點有關,當操作position和transform屬性的時候最為明顯西篓。
表示的是該點相對于x愈腾,y的比例,默認值為(0.5污淋,0.5)顶滩。

改變錨點相當于改變了參照點,圖形沿著錨點改變的反方向移動寸爆。

嘗試用一個黑點表示錨點礁鲁,改變錨點的位置,看看圖形的移動情況:

int i = 1;
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"bbb");

    UIView *v = self.subviews[0];
    v.layer.cornerRadius = 4;
    v.layer.masksToBounds = YES;

    CGPoint p;
    switch (i) {
        case 1:
            p = CGPointMake(0,0);
            break;
        case 2:
            p = CGPointMake(0.5,0.5);  
            break;
        case 3:
            p = CGPointMake(1,1);
            break;  
        default:
            break;
    }

    i++;
    if (i > 3) {
        i = 1;
    }
    
    self.layer.anchorPoint = p;
    [UIView animateWithDuration:0.3 animations:^{
        //紅色view的寬高分別為100赁豆,50
        v.center = CGPointMake(p.x * 100, p.x * 50);
    }];
    NSLog(@"%@",NSStringFromCGPoint(p));
    NSLog(@"%@",NSStringFromCGPoint(self.layer.position));
}
圖形移動方向與錨點改變方向

其他幾個屬性的改變演示:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    UITouch *touch = [touches anyObject];
    CGPoint p = [touch locationInView:self];
    
    CGFloat raidus = 25;
    CGSize size = CGSizeMake(2*raidus, 2*raidus);
    
    CALayer *layer = [[CALayer alloc] init];
    //中心點位置
    layer.position = p;
    //layer大小
    layer.bounds = CGRectMake(0, 0, size.width,size.height);
    //圓角
    layer.cornerRadius = raidus;
    //背景顏色仅醇,
    layer.backgroundColor = [UIColor colorWithRed:50/255 green:200/255 blue:120/255 alpha:1].CGColor;
    //陰影顏色
    layer.shadowColor = [UIColor grayColor].CGColor;
    //陰影偏移量
    layer.shadowOffset = CGSizeMake(5, 5);
    //陰影透明度
    layer.shadowOpacity = 0.8;
    
    [self.layer addSublayer:layer];
}
layer的幾個屬性改變

2.3 layer與view的關系

雖然可以在layer上不斷添加子layer,但是layer不能取代view魔种,layer只是讓動畫和繪制view的內容更高效析二。

Layers do not handle events, draw content, participate in the responder chain, or do many other things.

layer不會響應事件,也不會參與事件的傳遞节预,因此view是必不可少的叶摄,否則就不能響應各類的事件了()。

NOTE:可以通過hitTest:方法判斷是否點擊了layer

下面代碼利用presentedLayerhitTest:實現點擊屏幕任意位置安拟,layer會移動到該處蛤吓,點擊layer隨機改變layer背景:

@interface ViewController ()
@property (strong, nonatomic) CALayer *layer;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    UIImage *image = [UIImage imageNamed:@"icon1"];
    CALayer *layer = [CALayer layer];
    layer.position = CGPointMake(50, 50);
    layer.bounds   = CGRectMake(0, 0, 50, 50);
    layer.contents = (__bridge id)(image.CGImage);
    layer.contentsGravity = kCAGravityResizeAspect;
    layer.contentsScale = [UIScreen mainScreen].scale;
    [self.view.layer addSublayer:layer];
    self.point = self.leftBottomView.layer.position;
    self.layer = layer;
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    CGPoint p = [[touches anyObject] locationInView:self.view];
    if ([self.layer.presentationLayer hitTest:p]) {
        self.layer.backgroundColor = [UIColor colorWithRed:arc4random()/INT_MAX green:arc4random()/INT_MAX blue:arc4random()/INT_MAX alpha:1].CGColor;
    }else{
        [CATransaction begin];
        [CATransaction setAnimationDuration:4];
        self.layer.position = p;
        [CATransaction commit];
    }
}
@end
hitest

三、基于CALayer的繪圖模型

layer在app中不具有實際的繪圖能力糠赦,它只是獲取了app的頁面并將它緩存到一個被稱作 后備緩存器 的bitmap中会傲。用戶改變了layer的屬性時只是改變了layer的狀態(tài)信息锅棕,當與動畫結合時,Core Animation將layer的bitmap傳遞給graphics hardware淌山,graphics hardware會將新的變化以動畫的形式表現出來裸燎,

動畫示例

在view層會調用drawRect:方法從新繪制新的視圖,但是這個方法是用CPU在主線程重新繪制了視圖泼疑,會導致內存消耗過大德绿。Core Animation通過在任何可以的情況下調度bitmap的緩存來達到同樣或者相似的效果。

3.1 CALayer conent屬性

CALayer有一個與你所要展現的效果的bitmap相結合的contents屬性王浴,
@property(nullable, strong) id contents;
有三種方式創(chuàng)建content:

1.當layer的content基本不變時可以直接把image對象直接賦值給content
2.當layer的content可能周期改變或者通過外部提供時脆炎,可以把layer的delegate賦值給外部對象梅猿,讓delegate來繪制content
3.當你想創(chuàng)建一個sublayer或者改變layer的繪制行為的時候氓辣,通過重寫sublayer的繪制方法來給layer的content賦值。

  • 直接賦值image給content

layer只是管理bitmap圖片的一個容器袱蚓,所有可以直接把image(必須是CGImageRef)直接賦值給content钞啸。layer不會將你的image復制,而是直接使用你提供的image喇潘。在多處使用同一張圖片的時候可以節(jié)省內存体斩。

The image you assign to a layer must be a CGImageRef type.

這里要注意的是,contents雖然是id類型颖低,但是如果你賦值的不是CGImageRef類型的值絮吵,會得到空白的圖層。更頭疼的是UIImage有一個CGImage屬性忱屑,它返回的是一個CGImageRef類型值蹬敲,但是你

aLayer.contents = [UIImage imageWithName(@"pic")].CGImage

的時候發(fā)現,編譯器報錯了莺戒,因為CGImageRef并不是一個真正的Cocoa對象伴嗡,而是一個Core Foundation類型,要通過bridged關鍵之轉換(編譯器會提示你這樣做):

aLayer.contents = (__bride id)[UIImage imageWithName(@"pic")].CGImage

示例代碼如下:

-(void)viewDidLoad {
    [super viewDidLoad];
    UIImage *img = [UIImage imageNamed:@"icon1"];
    self.view.layer.contents = (__bridge id)(img.CGImage);
}

得到的效果


CGImage

contentGravity
可以看出本來圓形的icon被拉長了从铲,UIImageView顯示圖片的時候也遇到過這種情況瘪校,解決方法是設置合適的contentMode值。layer中控制這中屬性的叫contentsGravity名段,它是一個NSString類型:

kCAGravityCenter
kCAGravityTop
kCAGravityBottom
kCAGravityLeft
kCAGravityRight
kCAGravityTopLeft
kCAGravityTopRight
kCAGravityBottomLeft
kCAGravityBottomRight
kCAGravityResize
kCAGravityResizeAspect
kCAGravityResizeAspectFill

contentsScale
contentsScale定義了圖片的像素尺寸和試圖大小的比例阱扬,默認值是1.0(相當于自定義@2x、@3x圖片伸辟,例如麻惶,如果要以每個點一個像素繪制圖片,就自娩,設置值為1.0用踩,如果要以每個點2個像素繪制圖片渠退,設置值為2.0)。

NOTE:記得手動設置contentsScale屬性的值脐彩,否則圖片在retina屏幕上顯示就不正確了碎乃。

layer.contentsScale = [UIScreen mainScreen].bounds;

** contentRect**
contentRect允許開發(fā)者在圖層的邊框里顯示圖片的一個子區(qū)域,它使用了單位坐標來計算惠奸,默認的值為(0梅誓,0,1佛南,1)梗掰,如果知道一個小一點的矩形,圖片就會被裁剪嗅回。

contentsRect

利用這一點可以用來做圖片拼合及穗。先將需要拼合的圖片打包整合到一張大圖上一次性載入,相比多次載入不同的圖片绵载,可以優(yōu)化內存使用埂陆,縮短載入時間等。

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *leftTopView;
@property (weak, nonatomic) IBOutlet UIView *rightTop;
@property (weak, nonatomic) IBOutlet UIView *leftBottomView;
@property (weak, nonatomic) IBOutlet UIView *rightBottomView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    UIImage *image = [UIImage imageNamed:@"icon1"];
    [self addSpriteImage:image withContentRect:CGRectMake(0, 0, 0.5, 0.5) toLayer:self.leftTopView.layer];
    
    [self addSpriteImage:image withContentRect:CGRectMake(0.5, 0, 0.5, 0.5) toLayer:self.rightTop.layer];
    
    [self addSpriteImage:image withContentRect:CGRectMake(0, 0.5, 0.5, 0.5) toLayer:self.leftBottomView.layer];
    
    [self addSpriteImage:image withContentRect:CGRectMake(0.5, 0.5, 0.5, 0.5) toLayer:self.rightBottomView.layer];
}

- (void)addSpriteImage:(UIImage *)image withContentRect:(CGRect)rect toLayer:(CALayer *)layer //set image
{
    layer.contents = (__bridge id)image.CGImage;
    //scale contents to fit
    layer.contentsGravity = kCAGravityResizeAspect;
    //set contentsRect
    layer.contentsRect = rect;
}
利用contentsRect展示一張圖片的不同區(qū)域

contentsCenter
contentsCenter是CGRect類型的值娃豹,它與contentGravity配合使用焚虱,表示的是可被拉伸的區(qū)域,也就是說懂版,在這個區(qū)域內的才會被contentGravity(kCAGravityResize
鹃栽、kCAGravityResizeAspect、
kCAGravityResizeAspectFill才有效)影響到躯畴。

    UIImage *image = [UIImage imageNamed:@"icon1"];
    CALayer *layer = self.view.layer;
    layer.contents = (__bridge id)(image.CGImage);
    layer.contentsGravity = kCAGravityResizeAspect;
    layer.contentsScale = [UIScreen mainScreen].scale;
    layer.contentsCenter = CGRectMake(0.45, 0.45, 0.1, 0.1);
contensCenter效果
  • 通過layer的代理賦值image
    如果layer的content屬性會動態(tài)改變時民鼓,需要用到CALayerDelegate的代理方法在需要的時候給content賦值。layer有一個可選的代理屬性私股,實現了CALayerDelegate協議摹察,它是一個非正式協議,你只需要調用想調用的方法倡鲸,剩下的CALayer會幫你實現供嚎,代理方法:
//當已經有image對象時可以直接調用這個代理方法給content賦值
-(void)displayLayer:(CALayer *)layer;
//如果想通過自定義顯示內容的話用這個方法
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)cox;

NOTE:兩個代理方法調用一個即可,如果兩個方法都實現了峭状,系統只會調用displayLayer:方法克滴。

apple官方文檔給的實例代碼如下:

- (void)displayLayer:(CALayer *)theLayer {
  // Check the value of some state property
  if (self.displayYesImage) {
      // Display the Yes image
      theLayer.contents = [someHelperObject loadStateYesImage];
  }else {
      // Display the No image
      theLayer.contents = [someHelperObject loadStateNoImage];
  }
}

必須是新建的layer的delegate方法,而且必須調用layer(注意是layer不是view)的setNeedsDisplay方法优床,否則兩個代理方法都不會實現劝赔。

-(void)viewDidLoad {
  [super viewDidLoad];
  CALayer *layer = [[CALayer alloc] init];
  layer.position = CGPointMake(100, 200);
  layer.bounds = CGRectMake(0, 0, 100, 100);
  layer.delegate = self;
  [self.view.layer addSublayer:layer];
  //必須手動調用setNeedsDisplay方法,否則代理方法無法實現
  [layer setNeedsDisplay];
}
-(void)drawLayer:(CALayer *)theLayer inContext:(CGContextRef)theContext {
  CGMutablePathRef thePath = CGPathCreateMutable();

  CGPathMoveToPoint(thePath,NULL,15.0f,15.f);
  CGPathAddCurveToPoint(thePath,
                        NULL,
                        15.f,250.0f,
                        295.0f,250.0f,
                        295.0f,15.0f);

  CGContextBeginPath(theContext);
  CGContextAddPath(theContext, thePath);

  CGContextSetLineWidth(theContext, 5);
  CGContextStrokePath(theContext);

  // Release the path
  CFRelease(thePath);
}

閱讀原文

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末胆敞,一起剝皮案震驚了整個濱河市着帽,隨后出現的幾起案子杂伟,更是在濱河造成了極大的恐慌,老刑警劉巖仍翰,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赫粥,死亡現場離奇詭異,居然都是意外死亡予借,警方通過查閱死者的電腦和手機越平,發(fā)現死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來灵迫,“玉大人秦叛,你說我怎么就攤上這事∑僦啵” “怎么了挣跋?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長利凑。 經常有香客問我浆劲,道長,這世上最難降的妖魔是什么哀澈? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮度气,結果婚禮上割按,老公的妹妹穿的比我還像新娘。我一直安慰自己磷籍,他們只是感情好适荣,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著院领,像睡著了一般弛矛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上比然,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天丈氓,我揣著相機與錄音,去河邊找鬼强法。 笑死万俗,一個胖子當著我的面吹牛,可吹牛的內容都是我干的饮怯。 我是一名探鬼主播闰歪,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蓖墅!你這毒婦竟也來了库倘?” 一聲冷哼從身側響起临扮,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎教翩,沒想到半個月后公条,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡迂曲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年靶橱,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片路捧。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡关霸,死狀恐怖,靈堂內的尸體忽然破棺而出杰扫,到底是詐尸還是另有隱情队寇,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布章姓,位于F島的核電站佳遣,受9級特大地震影響,放射性物質發(fā)生泄漏凡伊。R本人自食惡果不足惜零渐,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望系忙。 院中可真熱鬧诵盼,春花似錦、人聲如沸银还。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蛹疯。三九已至戒财,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間捺弦,已是汗流浹背饮寞。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留羹呵,地道東北人骂际。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像冈欢,于是被迫代替她去往敵國和親歉铝。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內容

  • 在iOS中隨處都可以看到絢麗的動畫效果凑耻,實現這些動畫的過程并不復雜太示,今天將帶大家一窺ios動畫全貌柠贤。在這里你可以看...
    每天刷兩次牙閱讀 8,490評論 6 30
  • 在iOS中隨處都可以看到絢麗的動畫效果,實現這些動畫的過程并不復雜类缤,今天將帶大家一窺iOS動畫全貌臼勉。在這里你可以看...
    F麥子閱讀 5,111評論 5 13
  • 在iOS實際開發(fā)中常用的動畫無非是以下四種:UIView動畫,核心動畫餐弱,幀動畫宴霸,自定義轉場動畫。 1.UIView...
    請叫我周小帥閱讀 3,097評論 1 23
  • Core Animation Basics Layers Provide the Basis for Drawin...
    杰米閱讀 552評論 0 0
  • 啞鈴推舉 12kg*12 16kg*12 20kg*10 22kg*10 3組 22kg*8+12kg*8 器械推...
    shane911閱讀 155評論 0 0