主頁 > 移動端開發 > iOS核心影片高級技巧-5

iOS核心影片高級技巧-5

2020-09-11 20:57:44 移動端開發

9. 圖層時間

圖層時間

時間和空間最大的區別在于,時間不能被復用 -- 弗斯特梅里克

在上面兩章中,我們探討了可以用CAAnimation和它的子類實作的多種圖層影片,影片的發生是需要持續一段時間的,所以計時對整個概念來說至關重要,在這一章中,我們來看看CAMediaTiming,看看Core Animation是如何跟蹤時間的,

9.1 CAMediaTiming協議

CAMediaTiming`協議

CAMediaTiming協議定義了在一段影片內用來控制逝去時間的屬性的集合,CALayerCAAnimation都實作了這個協議,所以時間可以被任意基于一個圖層或者一段影片的類控制,

持續和重復

我們在第八章“顯式影片”中簡單提到過duration(CAMediaTiming的屬性之一),duration是一個CFTimeInterval的型別(類似于NSTimeInterval的一種雙精度浮點型別),對將要進行的影片的一次迭代指定了時間,

這里的一次迭代是什么意思呢?CAMediaTiming另外還有一個屬性叫做repeatCount,代表影片重復的迭代次數,如果duration是2,repeatCount設為3.5(三個半迭代),那么完整的影片時長將是7秒,

一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流群:1012951431, 分享BAT,阿里面試題、面試經驗,討論技術, 大家一起交流學習成長!希望幫助開發者少走彎路,

durationrepeatCount默認都是0,但這不意味著影片時長為0秒,或者0次,這里的0僅僅代表了“默認”,也就是0.25秒和1次,你可以用一個簡單的測驗來嘗試為這兩個屬性賦多個值,如清單9.1,圖9.1展示了程式的結果,

清單9.1 測驗durationrepeatCount

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic, weak) IBOutlet UITextField *durationField;
@property (nonatomic, weak) IBOutlet UITextField *repeatField;
@property (nonatomic, weak) IBOutlet UIButton *startButton;
@property (nonatomic, strong) CALayer *shipLayer;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //add the ship
    self.shipLayer = [CALayer layer];
    self.shipLayer.frame = CGRectMake(0, 0, 128, 128);
    self.shipLayer.position = CGPointMake(150, 150);
    self.shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGImage;
    [self.containerView.layer addSublayer:self.shipLayer];
}

- (void)setControlsEnabled:(BOOL)enabled
{
    for (UIControl *control in @[self.durationField, self.repeatField, self.startButton]) {
        control.enabled = enabled;
        control.alpha = enabled? 1.0f: 0.25f;
    }
}

- (IBAction)hideKeyboard
{
    ?[self.durationField resignFirstResponder];
    [self.repeatField resignFirstResponder];
}

- (IBAction)start
{
    CFTimeInterval duration = [self.durationField.text doubleValue];
    float repeatCount = [self.repeatField.text floatValue];
    //animate the ship rotation
    CABasicAnimation *animation = [CABasicAnimation animation];
    animation.keyPath = @"transform.rotation";
    animation.duration = duration;
    animation.repeatCount = repeatCount;
    animation.byValue = @(M_PI * 2);
    animation.delegate = self;
    [self.shipLayer addAnimation:animation forKey:@"rotateAnimation"];
    //disable controls
    [self setControlsEnabled:NO];
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
    //reenable controls
    [self setControlsEnabled:YES];
}

@end

 

圖9.2 擺動門的影片

對門進行擺動的代碼見清單9.2,我們用了autoreverses來使門在打開后自動關閉,在這里我們把repeatDuration設定為INFINITY,于是影片無限回圈播放,設定repeatCountINFINITY也有同樣的效果,注意repeatCount和repeatDuration可能會相互沖突,所以你只要對其中一個指定非零值,對兩個屬性都設定非0值的行為沒有被定義,

清單9.2 使用autoreverses屬性實作門的搖擺

@interface ViewController ()

@property (nonatomic, weak) UIView *containerView;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //add the door
    CALayer *doorLayer = [CALayer layer];
    doorLayer.frame = CGRectMake(0, 0, 128, 256);
    doorLayer.position = CGPointMake(150 - 64, 150);
    doorLayer.anchorPoint = CGPointMake(0, 0.5);
    doorLayer.contents = (__bridge id)[UIImage imageNamed: @"Door.png"].CGImage;
    [self.containerView.layer addSublayer:doorLayer];
    //apply perspective transform
    CATransform3D perspective = CATransform3DIdentity;
    perspective.m34 = -1.0 / 500.0;
    self.containerView.layer.sublayerTransform = perspective;
    //apply swinging animation
    CABasicAnimation *animation = [CABasicAnimation animation];
    animation.keyPath = @"transform.rotation.y";
    animation.toValue = @(-M_PI_2);
    animation.duration = 2.0;
    animation.repeatDuration = INFINITY;
    animation.autoreverses = YES;
    [doorLayer addAnimation:animation forKey:nil];
}

@end

 

相對時間

每次討論到Core Animation,時間都是相對的,每個影片都有它自己描述的時間,可以獨立地加速,延時或者偏移,

beginTime指定了影片開始之前的的延遲時間,這里的延遲從影片添加到可見圖層的那一刻開始測量,默認是0(就是說影片會立刻執行),

speed是一個時間的倍數,默認1.0,減少它會減慢圖層/影片的時間,增加它會加快速度,如果2.0的速度,那么對于一個duration為1的影片,實際上在0.5秒的時候就已經完成了,

timeOffsetbeginTime類似,但是和增加beginTime導致的延遲影片不同,增加timeOffset只是讓影片快進到某一點,例如,對于一個持續1秒的影片來說,設定timeOffset為0.5意味著影片將從一半的地方開始,

beginTime不同的是,timeOffset并不受speed的影響,所以如果你把speed設為2.0,把timeOffset設定為0.5,那么你的影片將從影片最后結束的地方開始,因為1秒的影片實際上被縮短到了0.5秒,然而即使使用了timeOffset讓影片從結束的地方開始,它仍然播放了一個完整的時長,這個影片僅僅是回圈了一圈,然后從頭開始播放,

可以用清單9.3的測驗程式驗證一下,設定speedtimeOffset滑塊到隨意的值,然后點擊播放來觀察效果(見圖9.3)

清單9.3 測驗timeOffsetspeed屬性

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic, weak) IBOutlet UILabel *speedLabel;
@property (nonatomic, weak) IBOutlet UILabel *timeOffsetLabel;
@property (nonatomic, weak) IBOutlet UISlider *speedSlider;
@property (nonatomic, weak) IBOutlet UISlider *timeOffsetSlider;
@property (nonatomic, strong) UIBezierPath *bezierPath;
@property (nonatomic, strong) CALayer *shipLayer;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //create a path
    self.bezierPath = [[UIBezierPath alloc] init];
    [self.bezierPath moveToPoint:CGPointMake(0, 150)];
    [self.bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(75, 0) controlPoint2:CGPointMake(225, 300)];
    //draw the path using a CAShapeLayer
    CAShapeLayer *pathLayer = [CAShapeLayer layer];
    pathLayer.path = self.bezierPath.CGPath;
    pathLayer.fillColor = [UIColor clearColor].CGColor;
    pathLayer.strokeColor = [UIColor redColor].CGColor;
    pathLayer.lineWidth = 3.0f;
    [self.containerView.layer addSublayer:pathLayer];
    //add the ship
    self.shipLayer = [CALayer layer];
    self.shipLayer.frame = CGRectMake(0, 0, 64, 64);
    self.shipLayer.position = CGPointMake(0, 150);
    self.shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGImage;
    [self.containerView.layer addSublayer:self.shipLayer];
    //set initial values
    [self updateSliders];
}

- (IBAction)updateSliders
{
    CFTimeInterval timeOffset = self.timeOffsetSlider.value;
    self.timeOffsetLabel.text = [NSString stringWithFormat:@"%0.2f", timeOffset];
    float speed = self.speedSlider.value;
    self.speedLabel.text = [NSString stringWithFormat:@"%0.2f", speed];
}

- (IBAction)play
{
    //create the keyframe animation
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
    animation.keyPath = @"position";
    animation.timeOffset = self.timeOffsetSlider.value;
    animation.speed = self.speedSlider.value;
    animation.duration = 1.0;
    animation.path = self.bezierPath.CGPath;
    animation.rotationMode = kCAAnimationRotateAuto;
    animation.removedOnCompletion = NO;
    [self.shipLayer addAnimation:animation forKey:@"slide"];
}

@end

 

9.2 層級關系時間

層級關系時間

9.3 手動影片

手動影片

timeOffset一個很有用的功能在于你可以它可以讓你手動控制影片行程,通過設定speed為0,可以禁用影片的自動播放,然后來使用timeOffset來來回顯示影片序列,這可以使得運用手勢來手動控制影片變得很簡單,

舉個簡單的例子:還是之前關門的影片,修改代碼來用手勢控制影片,我們給視圖添加一個UIPanGestureRecognizer,然后用timeOffset左右搖晃,

因為在影片添加到圖層之后不能再做修改了,我們來通過調整layertimeOffset達到同樣的效果(清單9.4),

清單9.4 通過觸摸手勢手動控制影片

@interface ViewController ()

@property (nonatomic, weak) UIView *containerView;
@property (nonatomic, strong) CALayer *doorLayer;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //add the door
    self.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 = (__bridge id)[UIImage imageNamed:@"Door.png"].CGImage;
    [self.containerView.layer addSublayer:self.doorLayer];
    //apply perspective transform
    CATransform3D perspective = CATransform3DIdentity;
    perspective.m34 = -1.0 / 500.0;
    self.containerView.layer.sublayerTransform = perspective;
    //add pan gesture recognizer to handle swipes
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] init];
    [pan addTarget:self action:@selector(pan:)];
    [self.view addGestureRecognizer:pan];
    //pause all layer animations
    self.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 gesture
    CGFloat x = [pan translationInView:self.view].x;
    //convert from points to animation duration //using a reasonable scale factor
    x /= 200.0f;
    //update timeOffset and clamp result
    CFTimeInterval 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];
}

@end

 

這其實是個小詭計,也許相對于設定個影片然后每次顯示一幀而言,用移動手勢來直接設定門的transform會更簡單,

在這個例子中的確是這樣,但是對于比如說關鍵這這樣更加復雜的情況,或者有多個圖層的影片組,相對于實時計算每個圖層的屬性而言,這就顯得方便的多了,

9.4 總結

總結

在這一章,我們了解了CAMediaTiming協議,以及Core Animation用來操作時間控制影片的機制,在下一章,我們將要接觸緩沖,另一個用來使影片更加真實的操作時間的技術,

10. 緩沖

緩沖

生活和藝術一樣,最美的永遠是曲線, -- 愛德華布爾沃 - 利頓

在第九章“圖層時間”中,我們討論了影片時間和CAMediaTiming協議,現在我們來看一下另一個和時間相關的機制--所謂的緩沖,Core Animation使用緩沖來使影片移動更平滑更自然,而不是看起來的那種機械和人工,在這一章我們將要研究如何對你的影片控制和自定義緩沖曲線,

10.1 影片速度

影片速度

影片實際上就是一段時間內的變化,這就暗示了變化一定是隨著某個特定的速率進行,速率由以下公式計算而來:

velocity = change / time

 

這里的變化可以指的是一個物體移動的距離,時間指影片持續的時長,用這樣的一個移動可以更加形象的描述(比如positionbounds屬性的影片),但實際上它應用于任意可以做影片的屬性(比如coloropacity),

上面的等式假設了速度在整個影片程序中都是恒定不變的(就如同第八章“顯式影片”的情況),對于這種恒定速度的影片我們稱之為“線性步調”,而且從技術的角度而言這也是實作影片最簡單的方式,但也是完全不真實的一種效果,

考慮一個場景,一輛車行駛在一定距離內,它并不會一開始就以60mph的速度行駛,然后到達終點后突然變成0mph,一是因為需要無限大的加速度(即使是最好的車也不會在0秒內從0跑到60),另外不然的話會干死所有乘客,在現實中,它會慢慢地加速到全速,然后當它接近終點的時候,它會慢慢地減速,直到最后停下來,

那么對于一個掉落到地上的物體又會怎樣呢?它會首先停在空中,然后一直加速到落到地面,然后突然停止(然后由于積累的動能轉換伴隨著一聲巨響,砰!),

現實生活中的任何一個物體都會在運動中加速或者減速,那么我們如何在影片中實作這種加速度呢?一種方法是使用物理引擎來對運動物體的摩擦和動量來建模,然而這會使得計算過于復雜,我們稱這種型別的方程為緩沖函式,幸運的是,Core Animation內嵌了一系列標準函式提供給我們使用,

CAMediaTimingFunction

那么該如何使用緩沖方程式呢?首先需要設定CAAnimation的timingFunction屬性,是CAMediaTimingFunction類的一個物件,如果想改變隱式影片的計時函式,同樣也可以使用CATransaction+setAnimationTimingFunction:方法,

這里有一些方式來創建CAMediaTimingFunction,最簡單的方式是呼叫+timingFunctionWithName:的構造方法,這里傳入如下幾個常量之一:

kCAMediaTimingFunctionLinear 
kCAMediaTimingFunctionEaseIn 
kCAMediaTimingFunctionEaseOut 
kCAMediaTimingFunctionEaseInEaseOut
kCAMediaTimingFunctionDefault

 

kCAMediaTimingFunctionLinear選項創建了一個線性的計時函式,同樣也是CAAnimation的timingFunction屬性為空時候的默認函式,線性步調對于那些立即加速并且保持勻速到達終點的場景會有意義(例如射出槍膛的子彈),但是默認來說它看起來很奇怪,因為對大多數的影片來說確實很少用到,

kCAMediaTimingFunctionEaseIn常量創建了一個慢慢加速然后突然停止的方法,對于之前提到的自由落體的例子來說很適合,或者比如對準一個目標的導彈的發射,

kCAMediaTimingFunctionEaseOut則恰恰相反,它以一個全速開始,然后慢慢減速停止,它有一個削弱的效果,應用的場景比如一扇門慢慢地關上,而不是砰地一聲,

kCAMediaTimingFunctionEaseInEaseOut創建了一個慢慢加速然后再慢慢減速的程序,這是現實世界大多數物體移動的方式,也是大多數影片來說最好的選擇,如果只可以用一種緩沖函式的話,那就必須是它了,那么你會疑惑為什么這不是默認的選擇,實際上當使用UIView的影片方法時,他的確是默認的,但當創建CAAnimation的時候,就需要手動設定它了,

最后還有一個kCAMediaTimingFunctionDefault,它和kCAMediaTimingFunctionEaseInEaseOut很類似,但是加速和減速的程序都稍微有些慢,它和kCAMediaTimingFunctionEaseInEaseOut的區別很難察覺,可能是蘋果覺得它對于隱式影片來說更適合(然后對UIKit就改變了想法,而是使用kCAMediaTimingFunctionEaseInEaseOut作為默認效果),雖然它的名字說是默認的,但還是要記住當創建顯式的CAAnimation它并不是默認選項(換句話說,默認的圖層行為影片用kCAMediaTimingFunctionDefault作為它們的計時方法),

你可以使用一個簡單的測驗工程來實驗一下(清單10.1),在運行之前改變緩沖函式的代碼,然后點擊任何地方來觀察圖層是如何通過指定的緩沖移動的,

清單10.1 緩沖函式的簡單測驗

@interface ViewController ()

@property (nonatomic, strong) CALayer *colorLayer;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //create a red layer
    self.colorLayer = [CALayer layer];
    self.colorLayer.frame = CGRectMake(0, 0, 100, 100);
    self.colorLayer.position = CGPointMake(self.view.bounds.size.width/2.0, self.view.bounds.size.height/2.0);
    self.colorLayer.backgroundColor = [UIColor redColor].CGColor;
    [self.view.layer addSublayer:self.colorLayer];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //configure the transaction
    [CATransaction begin];
    [CATransaction setAnimationDuration:1.0];
    [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
    //set the position
    self.colorLayer.position = [[touches anyObject] locationInView:self.view];
    //commit transaction
    [CATransaction commit];
}

@end

 

UIView的影片緩沖

UIKit的影片也同樣支持這些緩沖方法的使用,盡管語法和常量有些不同,為了改變UIView影片的緩沖選項,給options引數添加如下常量之一:

UIViewAnimationOptionCurveEaseInOut 
UIViewAnimationOptionCurveEaseIn 
UIViewAnimationOptionCurveEaseOut 
UIViewAnimationOptionCurveLinear

 

它們和CAMediaTimingFunction緊密關聯,UIViewAnimationOptionCurveEaseInOut是默認值(這里沒有kCAMediaTimingFunctionDefault相對應的值了),

具體使用方法見清單10.2(注意到這里不再使用UIView額外添加的圖層,因為UIKit的影片并不支持這類圖層),

清單10.2 使用UIKit影片的緩沖測驗工程

@interface ViewController ()

@property (nonatomic, strong) UIView *colorView;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //create a red layer
    self.colorView = [[UIView alloc] init];
    self.colorView.bounds = CGRectMake(0, 0, 100, 100);
    self.colorView.center = CGPointMake(self.view.bounds.size.width / 2, self.view.bounds.size.height / 2);
    self.colorView.backgroundColor = [UIColor redColor];
    [self.view addSubview:self.colorView];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //perform the animation
    [UIView animateWithDuration:1.0 delay:0.0
                        options:UIViewAnimationOptionCurveEaseOut
                     animations:^{
                            //set the position
                            self.colorView.center = [[touches anyObject] locationInView:self.view];
                        }
                     completion:NULL];

}

@end

 

緩沖和關鍵幀影片

或許你會回想起第八章里面顏色切換的關鍵幀影片由于線性變換的原因(見清單8.5)看起來有些奇怪,使得顏色變換非常不自然,為了糾正這點,我們來用更加合適的緩沖方法,例如kCAMediaTimingFunctionEaseIn,給圖層的顏色變化添加一點脈沖效果,讓它更像現實中的一個彩色燈泡,

我們不想給整個影片程序應用這個效果,我們希望對每個影片的程序重復這樣的緩沖,于是每次顏色的變換都會有脈沖效果,

CAKeyframeAnimation有一個NSArray型別的timingFunctions屬性,我們可以用它來對每次影片的步驟指定不同的計時函式,但是指定函式的個數一定要等于keyframes陣列的元素個數減一,因為它是描述每一幀之間影片速度的函式,

在這個例子中,我們自始至終想使用同一個緩沖函式,但我們同樣需要一個函式的陣列來告訴影片不停地重復每個步驟,而不是在整個影片序列只做一次緩沖,我們簡單地使用包含多個相同函式拷貝的陣列就可以了(見清單10.3),

運行更新后的代碼,你會發現影片看起來更加自然了,

清單10.3 對CAKeyframeAnimation使用CAMediaTimingFunction

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *layerView;
@property (nonatomic, weak) IBOutlet CALayer *colorLayer;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //create sublayer
    self.colorLayer = [CALayer layer];
    self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
    self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;
    //add it to our view
    [self.layerView.layer addSublayer:self.colorLayer];
}

- (IBAction)changeColor
{
    //create a keyframe animation
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
    animation.keyPath = @"backgroundColor";
    animation.duration = 2.0;
    animation.values = @[
                         (__bridge id)[UIColor blueColor].CGColor,
                         (__bridge id)[UIColor redColor].CGColor,
                         (__bridge id)[UIColor greenColor].CGColor,
                         (__bridge id)[UIColor blueColor].CGColor ];
    //add timing function
    CAMediaTimingFunction *fn = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn];
    animation.timingFunctions = @[fn, fn, fn];
    //apply animation to layer
    [self.colorLayer addAnimation:animation forKey:nil];
}

@end

 

10.2 自定義緩沖函式

自定義緩沖函式

在第八章中,我們給時鐘專案添加了影片,看起來很贊,但是如果有合適的緩沖函式就更好了,在顯示世界中,鐘表指標轉動的時候,通常起步很慢,然后迅速啪地一聲,最后緩沖到終點,但是標準的緩沖函式在這里每一個適合它,那該如何創建一個新的呢?

除了+functionWithName:之外,CAMediaTimingFunction同樣有另一個建構式,一個有四個浮點引數的+functionWithControlPoints::::(注意這里奇怪的語法,并沒有包含具體每個引數的名稱,這在objective-C中是合法的,但是卻違反了蘋果對方法命名的指導方針,而且看起來是一個奇怪的設計),

使用這個方法,我們可以創建一個自定義的緩沖函式,來匹配我們的時鐘影片,為了理解如何使用這個方法,我們要了解一些CAMediaTimingFunction是如何作業的,

三次貝塞爾曲線

CAMediaTimingFunction函式的主要原則在于它把輸入的時間轉換成起點和終點之間成比例的改變,我們可以用一個簡單的圖示來解釋,橫軸代表時間,縱軸代表改變的量,于是線性的緩沖就是一條從起點開始的簡單的斜線(圖10.1),

圖10.2 三次貝塞爾緩沖函式

實際上它是一個很奇怪的函式,先加速,然后減速,最后快到達終點的時候又加速,那么標準的緩沖函式又該如何用影像來表示呢?

CAMediaTimingFunction有一個叫做-getControlPointAtIndex:values:的方法,可以用來檢索曲線的點,這個方法的設計的確有點奇怪(或許也就只有蘋果能回答為什么不簡單回傳一個CGPoint),但是使用它我們可以找到標準緩沖函式的點,然后用UIBezierPathCAShapeLayer來把它畫出來,

曲線的起始和終點始終是{0, 0}和{1, 1},于是我們只需要檢索曲線的第二個和第三個點(控制點),具體代碼見清單10.4,所有的標準緩沖函式的影像見圖10.3,

清單10.4 使用UIBezierPath繪制CAMediaTimingFunction

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *layerView;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //create timing function
    CAMediaTimingFunction *function = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut];
    //get control points
    CGPoint controlPoint1, controlPoint2;
    [function getControlPointAtIndex:1 values:(float *)&controlPoint1];
    [function getControlPointAtIndex:2 values:(float *)&controlPoint2];
    //create curve
    UIBezierPath *path = [[UIBezierPath alloc] init];
    [path moveToPoint:CGPointZero];
    [path addCurveToPoint:CGPointMake(1, 1)
            controlPoint1:controlPoint1 controlPoint2:controlPoint2];
    //scale the path up to a reasonable size for display
    [path applyTransform:CGAffineTransformMakeScale(200, 200)];
    //create shape layer
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.strokeColor = [UIColor redColor].CGColor;
    shapeLayer.fillColor = [UIColor clearColor].CGColor;
    shapeLayer.lineWidth = 4.0f;
    shapeLayer.path = path.CGPath;
    [self.layerView.layer addSublayer:shapeLayer];
    //flip geometry so that 0,0 is in the bottom-left
    self.layerView.layer.geometryFlipped = YES;
}

@end

 

圖10.4 自定義適合時鐘的緩沖函式

清單10.5 添加了自定義緩沖函式的時鐘程式

- (void)setAngle:(CGFloat)angle forHand:(UIView *)handView ?animated:(BOOL)animated
{
    //generate transform
    CATransform3D transform = CATransform3DMakeRotation(angle, 0, 0, 1);
    if (animated) {
        //create transform animation
        CABasicAnimation *animation = [CABasicAnimation animation];
        animation.keyPath = @"transform";
        animation.fromValue = [handView.layer.presentationLayer valueForKey:@"transform"];
        animation.toValue = [NSValue valueWithCATransform3D:transform];
        animation.duration = 0.5;
        animation.delegate = self;
        animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:1 :0 :0.75 :1];
        //apply animation
        handView.layer.transform = transform;
        [handView.layer addAnimation:animation forKey:nil];
    } else {
        //set transform directly
        handView.layer.transform = transform;
    }
}

 

更加復雜的影片曲線

考慮一個橡膠球掉落到堅硬的地面的場景,當開始下落的時候,它會持續加速知道落到地面,然后經過幾次反彈,最后停下來,如果用一張圖來說明,它會如圖10.5所示,

這可以起到作用,但效果并不是很好,到目前為止我們所完成的只是一個非常復雜的方式來使用線性緩沖復制CABasicAnimation的行為,這種方式的好處在于我們可以更加精確地控制緩沖,這也意味著我們可以應用一個完全定制的緩沖函式,那么該如何做呢?

緩沖背后的數學并不很簡單,但是幸運的是我們不需要一一實作它,羅伯特·彭納有一個網頁關于緩沖函式(http://www.robertpenner.com/easing ),包含了大多數普遍的緩沖函式的多種編程語言的實作的鏈接,包括C,這里是一個緩沖進入緩沖退出函式的示例(實際上有很多不同的方式去實作它),

float quadraticEaseInOut(float t) 
{
    return (t < 0.5)? (2 * t * t): (-2 * t * t) + (4 * t) - 1; 
}

 

對我們的彈性球來說,我們可以使用bounceEaseOut函式:

float bounceEaseOut(float t)
{
    if (t < 4/11.0) {
        return (121 * t * t)/16.0;
    } else if (t < 8/11.0) {
        return (363/40.0 * t * t) - (99/10.0 * t) + 17/5.0;
    } else if (t < 9/10.0) {
        return (4356/361.0 * t * t) - (35442/1805.0 * t) + 16061/1805.0;
    }
    return (54/5.0 * t * t) - (513/25.0 * t) + 268/25.0;
}

 

如果修改清單10.7的代碼來引入bounceEaseOut方法,我們的任務就是僅僅交換緩沖函式,現在就可以選擇任意的緩沖型別創建影片了(見清單10.8),

清單10.8 用關鍵幀實作自定義的緩沖函式

- (void)animate
{
    //reset ball to top of screen
    self.ballView.center = CGPointMake(150, 32);
    //set up animation parameters
    NSValue *fromValue = https://www.cnblogs.com/Julday/p/[NSValue valueWithCGPoint:CGPointMake(150, 32)];
    NSValue *toValue = https://www.cnblogs.com/Julday/p/[NSValue valueWithCGPoint:CGPointMake(150, 268)];
    CFTimeInterval duration = 1.0;
    //generate keyframes
    NSInteger numFrames = duration * 60;
    NSMutableArray *frames = [NSMutableArray array];
    for (int i = 0; i < numFrames; i++) {
        float time = 1/(float)numFrames * i;
        //apply easing
        time = bounceEaseOut(time);
        //add keyframe
        [frames addObject:[self interpolateFromValue:fromValue toValue:toValue time:time]];
    }
    //create keyframe animation
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
    animation.keyPath = @"position";
    animation.duration = 1.0;
    animation.delegate = self;
    animation.values = frames;
    //apply animation
    [self.ballView.layer addAnimation:animation forKey:nil];
}

 

10.3 總結

在這一章中,我們了解了有關緩沖和CAMediaTimingFunction類,它可以允許我們創建自定義的緩沖函式來完善我們的影片,同樣了解了如何用CAKeyframeAnimation來避開CAMediaTimingFunction的限制,創建完全自定義的緩沖函式,

在下一章中,我們將要研究基于定時器的影片--另一個給我們對影片更多控制的選擇,并且實作對影片的實時操縱,

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/7505.html

標籤:iOS

上一篇:如何用Linux重現《黑客帝國》中的經典界面?

下一篇:iOS 裁剪工具

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more