你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> iOS動畫篇:自定義動畫

iOS動畫篇:自定義動畫

編輯:IOS開發基礎

前言

在上一篇文章iOS動畫篇:自定義View中講到了如何在view裡畫一個圓,本文將在此基礎上給其加上弧度變化的動畫,形成一個簡單的Loading動畫,呈現自定義動畫的實現過程。

先來看看需要實現的Loading動畫效果:

CustomAnimation - preview.gif

條條大路通羅馬:在UIView上實現

1、在自定義View時所提到的路徑方法只能畫整圓,現在我們使用下面的方法來畫一部分圓弧:

- (void)drawRect:(CGRect)rect { CGFloat radius = self.bounds.size.width / 2; CGFloat lineWidth = 10.0;
UIBezierPath * path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(radius, radius) radius:radius - lineWidth / 2 startAngle:0.f endAngle:M_PI clockwise:YES];
[[UIColor colorWithRed:0.5 green:0.5 blue:0.9 alpha:1.0] setStroke];
[path setLineWidth:lineWidth];
[path stroke];
}

效果:半個圓弧

Circle - half.png

2、弧度總不能寫死吧,弧度得有變化才能形成動畫效果。怎樣控制它變化呢,我們給它加上一個progress屬性來控制其弧度

@interface CircleProgressView : UIView
@property (nonatomic, assign) CGFloat progress;
@end
- (void)drawRect:(CGRect)rect {
CGFloat radius = self.bounds.size.width / 2;
CGFloat lineWidth = 10.0;
UIBezierPath * path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(radius, radius) radius:radius - lineWidth / 2 startAngle:0.f endAngle:M_PI * 2 * self.progress clockwise:YES];
[[UIColor colorWithRed:0.5 green:0.5 blue:0.9 alpha:1.0] setStroke];
[path setLineWidth:lineWidth];
[path stroke];
}

3、加到視圖上

- (void)viewDidLoad {
[super viewDidLoad];
self.circleProgressView = [[CircleProgressView alloc]initWithFrame:CGRectMake(100, 100, 200, 200)];
self.circleProgressView.progress = 0.2;
[self.view addSubview:self.circleProgressView];
}

4、通過外部事件來改變它的弧度,並讓其重繪(這裡的例子時當點擊屏幕的時候改變其弧度屬性)

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.circleProgressView.progress = 0.5;
[self.circleProgressView setNeedsDisplay];
}

效果圖:

CustomAnimation - setNeedsDisplay.gif

小結

1)drawRect方法會執行view的重繪,但是drawRect方法不能手動調用(手動調用了也無效),必須通過調用setNeedsDisplay讓系統自動調該方法。

2)實現自定義動畫可以通過:O —>通過屬性控制view的形狀 —> 改變view的屬性 —> 調用重繪方法 —> view的形狀改變 —> O

下面我們創建slider來模擬進度變化

UISlider * slider = [[UISlider alloc]initWithFrame:CGRectMake(50, 400, 275, 10)];    [slider addTarget:self action:@selector(changeProgress:) forControlEvents:UIControlEventValueChanged];    slider.maximumValue = 1.0;    slider.minimumValue = 0.f;    slider.value = self.circleProgressView.progress;
[self.view addSubview:slider];
- (void)changeProgress:(UISlider *)slider {    self.circleProgressView.progress = slider.value;
[self.circleProgressView setNeedsDisplay];
}

效果圖:

CustomAnimation - setNeedsDisplay - play.gif

更優雅的實現方式:在CALayer上實現

通過重載View的drawRect來實現自定義動畫縱然可以,但是不夠優雅(逼格),而且實現更復雜的界面時也顯得不夠方便,下面我們使用添加Layer的方式來實現。

1、新建CircleProgressLayer類

CircleProgressView.h
CircleProgressView.m

2、給其添加progress屬性

@interface CircleProgressLayer : CALayer
@property (nonatomic, assign) CGFloat progress;
@end

3、重載其繪圖方法 drawInContext,並在progress屬性變化時讓其重繪

- (void)drawInContext:(CGContextRef)ctx {
CGFloat radius = self.bounds.size.width / 2;
CGFloat lineWidth = 10.0;
UIBezierPath * path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(radius, radius) radius:radius - lineWidth / 2 startAngle:0.f endAngle:M_PI * 2 * self.progress clockwise:YES];
CGContextSetRGBStrokeColor(ctx, 0.5, 0.5, 0.9, 1.0);//筆顏色
CGContextSetLineWidth(ctx, 10);//線條寬度
CGContextAddPath(ctx, path.CGPath);
CGContextStrokePath(ctx);
}
- (void)setProgress:(CGFloat)progress {
_progress = progress;
[self setNeedsDisplay];
}

4、將layer添加到自定義的view中,並在progress屬性變化時通知layer

- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.circleProgressLayer = [CircleProgressLayer layer];
self.circleProgressLayer.frame = self.bounds;        //像素大小比例
self.circleProgressLayer.contentsScale = [UIScreen mainScreen].scale;
[self.layer addSublayer:self.circleProgressLayer];
}
return self;
}
- (void)setProgress:(CGFloat)progress {
self.circleProgressLayer.progress = progress;
_progress = progress;
}

這樣做可以達到跟上面例子一樣的效果,那麼為什麼推薦使用這種方式呢?

答案是:CALayer自帶動畫效果(或者說自帶自動形成動畫幀的天賦)

1)直接在View中繪圖可以形成動畫效果,但前提是其變化幅度要求非常小,否則看起來就是一段一段的很生硬,比如上面的例子中,progress從0.2變化到0.5的時候,並沒有動畫效果。

2)對比起來在CALayer中繪圖可以使用CA動畫讓其自定義的屬性變化也有動畫效果,其原理是:給Layer的屬性提供初值、終值和動畫時間,CA會自動計算中間值,並生產關鍵幀,在非主線程中播放關鍵幀,這樣就形成了動畫效果。

下面我們給創建的Layer添加動畫效果:

1、新建CircleProgressLayer類

CircleProgressLayer.h
CircleProgressLayer.m

2、給其添加progress屬性

@interface CircleProgressLayer : CALayer
@property (nonatomic, assign) CGFloat progress;
@end

3、重載其繪圖方法 drawInContext,並在progress屬性變化時讓其重繪

- (void)drawInContext:(CGContextRef)ctx {
CGFloat radius = self.bounds.size.width / 2;
CGFloat lineWidth = 10.0;
UIBezierPath * path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(radius, radius) radius:radius - lineWidth / 2 startAngle:0.f endAngle:M_PI * 2 * self.progress clockwise:YES];
CGContextSetRGBStrokeColor(ctx, 0.5, 0.5, 0.9, 1.0);//筆顏色
CGContextSetLineWidth(ctx, 10);//線條寬度
CGContextAddPath(ctx, path.CGPath);
CGContextStrokePath(ctx);
}

4、重載 needsDisplayForKey方法指定progress屬性變化時進行重繪

+ (BOOL)needsDisplayForKey:(NSString *)key {
if ([key isEqualToString:@"progress"]) {
return YES;
}
return [super needsDisplayForKey:key];
}

5、重載initWithLayer方法

- (instancetype)initWithLayer:(CircleProgressLayer *)layer {
NSLog(@"initLayer");
if (self = [super initWithLayer:layer]) {
self.progress = layer.progress;
}
return self;
}

6、在View中,當progress屬性變化時,給對應layer增加CA動畫,並在動畫結束時刷新layer的progress屬性

- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.circleProgressLayer = [CircleProgressLayer layer];
self.circleProgressLayer.frame = self.bounds;        //像素大小比例
self.circleProgressLayer.contentsScale = [UIScreen mainScreen].scale;
[self.layer addSublayer:self.circleProgressLayer];
}
return self;
}
- (void)setProgress:(CGFloat)progress {
CABasicAnimation * ani = [CABasicAnimation animationWithKeyPath:@"progress"];
ani.duration = 5.0 * fabs(progress - _progress);
ani.toValue = @(progress);
ani.removedOnCompletion = YES;
ani.fillMode = kCAFillModeForwards;
ani.delegate = self;
[self.circleProgressLayer addAnimation:ani forKey:@"progressAni"];
_progress = progress;
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
self.circleProgressLayer.progress = self.progress;
}

7、添加到視圖中,通過外部事件改變其進度(這裡的測試例子是當點擊屏幕時隨機增加進度)

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.circleProgressView.progress += (arc4random() % 4 + 1) * 0.1;
}

效果圖:

CustomAnimation - layerAni.gif

小結

1)needsDisplayForKey方法:CA動畫生成需要指定對Layer的哪一個屬性進行插值,Layer默認有許多帶有動畫效果的屬性,如postion,backgroundColor等等,我們自定義的屬性需要手動指定。

2)initWithLayer方法:CA生成關鍵幀是通過拷貝CALayer進行的,在拷貝時,只能拷貝原有的(系統的,非自定義的)屬性,不能拷貝自定義的屬性或持有的對象等等,因此需要重載initWithLayer來手動拷貝我們需要拷貝的東西。

蛋糕出爐加奶油:UIView和CALayer的結合

進度條動畫已經具備了動畫,再加上進度的顯示,就完成了自定義的圓形進度條。

這裡的進度使用了UILabel來展示,當可以滿足需求的時候完全可以結合UIView來實現,當然如果有讀者追求完美動畫效果(例如進度數字的變化動畫),可以繼續思考如何實現,並完善之。

效果圖:

CustomAnimation - preview.gif

本文例子的demo可以到我的GitHub點擊我飛過去下載。

總結

至此,我們基本了解了自定義View動畫的實現流程,大家可以根據不同情形選擇其實現方式:

1)變化幅度小,變化速度快的情景,選用setNeedsDisplay進行重繪就可以滿足需求。

應用場景:進度條的拖動、下拉刷新的動畫、等等

2)變化幅度大、變化速度慢的情景,選用給屬性添加CA動畫來滿足需求。

應用場景:下載進度的變化、數字變化的效果


文/明仔Su(簡書作者)

原文鏈接:http://www.jianshu.com/p/673585164bd2


  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved