W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
在第八章中,我們給時鐘項目添加了動畫。看起來很贊,但是如果有合適的緩沖函數(shù)就更好了。在顯示世界中,鐘表指針轉(zhuǎn)動的時候,通常起步很慢,然后迅速啪地一聲,最后緩沖到終點。但是標準的緩沖函數(shù)在這里每一個適合它,那該如何創(chuàng)建一個新的呢?
除了+functionWithName:
之外,CAMediaTimingFunction
同樣有另一個構(gòu)造函數(shù),一個有四個浮點參數(shù)的+functionWithControlPoints::::
(注意這里奇怪的語法,并沒有包含具體每個參數(shù)的名稱,這在objective-C中是合法的,但是卻違反了蘋果對方法命名的指導方針,而且看起來是一個奇怪的設計)。
使用這個方法,我們可以創(chuàng)建一個自定義的緩沖函數(shù),來匹配我們的時鐘動畫,為了理解如何使用這個方法,我們要了解一些CAMediaTimingFunction
是如何工作的。
CAMediaTimingFunction
函數(shù)的主要原則在于它把輸入的時間轉(zhuǎn)換成起點和終點之間成比例的改變。我們可以用一個簡單的圖標來解釋,橫軸代表時間,縱軸代表改變的量,于是線性的緩沖就是一條從起點開始的簡單的斜線(圖10.1)。
圖10.2 三次貝塞爾緩沖函數(shù)
實際上它是一個很奇怪的函數(shù),先加速,然后減速,最后快到達終點的時候又加速,那么標準的緩沖函數(shù)又該如何用圖像來表示呢?
CAMediaTimingFunction
有一個叫做-getControlPointAtIndex:values:
的方法,可以用來檢索曲線的點,這個方法的設計的確有點奇怪(或許也就只有蘋果能回答為什么不簡單返回一個CGPoint
),但是使用它我們可以找到標準緩沖函數(shù)的點,然后用UIBezierPath
和CAShapeLayer
來把它畫出來。
曲線的起始和終點始終是{0, 0}和{1, 1},于是我們只需要檢索曲線的第二個和第三個點(控制點)。具體代碼見清單10.4。所有的標準緩沖函數(shù)的圖像見圖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 自定義適合時鐘的緩沖函數(shù)
清單10.5 添加了自定義緩沖函數(shù)的時鐘程序
- (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;
}
}
考慮一個橡膠球掉落到堅硬的地面的場景,當開始下落的時候,它會持續(xù)加速知道落到地面,然后經(jīng)過幾次反彈,最后停下來。如果用一張圖來說明,它會如圖10.5所示。
這可以起到作用,但效果并不是很好,到目前為止我們所完成的只是一個非常復雜的方式來使用線性緩沖復制CABasicAnimation
的行為。這種方式的好處在于我們可以更加精確地控制緩沖,這也意味著我們可以應用一個完全定制的緩沖函數(shù)。那么該如何做呢?
緩沖背后的數(shù)學并不很簡單,但是幸運的是我們不需要一一實現(xiàn)它。羅伯特·彭納有一個網(wǎng)頁關(guān)于緩沖函數(shù)(http://www.robertpenner.com/easing),包含了大多數(shù)普遍的緩沖函數(shù)的多種編程語言的實現(xiàn)的鏈接,包括C。這里是一個緩沖進入緩沖退出函數(shù)的示例(實際上有很多不同的方式去實現(xiàn)它)。
float quadraticEaseInOut(float t)
{
return (t < 0.5)? (2 * t * t): (-2 * t * t) + (4 * t) - 1;
}
對我們的彈性球來說,我們可以使用bounceEaseOut
函數(shù):
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
方法,我們的任務就是僅僅交換緩沖函數(shù),現(xiàn)在就可以選擇任意的緩沖類型創(chuàng)建動畫了(見清單10.8)。
清單10.8 用關(guān)鍵幀實現(xiàn)自定義的緩沖函數(shù)
- (void)animate
{
//reset ball to top of screen
self.ballView.center = CGPointMake(150, 32);
//set up animation parameters
NSValue *fromValue = [NSValue valueWithCGPoint:CGPointMake(150, 32)];
NSValue *toValue = [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];
}
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: