引文
這里舉例UIView & CoreAnimation 中的相關用法
1. commitAnimations
基本寫法,代碼必須放在Begin和Commit之間:
[UIView beginAnimations:nil context:nil]; // 開始動畫
// Code...
[UIView commitAnimations]; // 提交動畫
示例代碼
[UIView beginAnimations:nil context:nil]; // 開始動畫
[UIView setAnimationDuration:10.0]; // 動畫時長
/**
* 圖像向下移動
*/
CGPoint point = _imageView.center;
point.y += 150;
[_imageView setCenter:point];
[UIView commitAnimations]; // 提交動畫
項目中用于頁面翻轉的動畫:
-(void)flip{
CGContextRef context=UIGraphicsGetCurrentContext();
[UIView beginAnimations:nil context:context];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:1.0];
UIView *topView ;
//這里時查找視圖里的子視圖(這種情況查找,可能時因為父視圖里面不只兩個視圖)
NSInteger first= [[self.view subviews] indexOfObject:[self.view viewWithTag:100]];
NSInteger second= [[self.view subviews] indexOfObject:[self.view viewWithTag:101]];
topView = first < second ?[self.view viewWithTag:101]:[self.view viewWithTag:100];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:topView cache:YES];
[self.view exchangeSubviewAtIndex:first withSubviewAtIndex:second];
//當父視圖里面只有兩個視圖的時候,可以直接使用下面這段.
//[self.view exchangeSubviewAtIndex:0 withSubviewAtIndex:1];
[UIView setAnimationDelegate:self];
[UIView commitAnimations];
}
2. 使用UIView的動畫塊代碼:
這也是最常用的一種方式:
[UIView animateWithDuration:4.0 // 動畫時長
delay:2.0 // 動畫延遲
options:UIViewAnimationOptionCurveEaseIn // 動畫過渡效果
animations:^{
// code...
}
completion:^(BOOL finished) {
// 動畫完成后執(zhí)行
// code...
}];
對于使用Masonry的同學來說,使用這個方法需要注意調用mas_updateConstraints
,使用方法相見GitHub。比如項目中用到的滾動label的效果:
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
[ws.tipLbl mas_updateConstraints:^(MASConstraintMaker *make) {
// make.centerY.equalTo(@(-50));
make.center.equalTo(ws).centerOffset(CGPointMake(0, -1*ws.bounds.size.height));//25
}];
//[self.view updateConstraintsIfNeeded];
[ws layoutIfNeeded];
} completion:^(BOOL finished) {
ws.tipLbl.text = tip;
[self.tipLbl mas_updateConstraints:^(MASConstraintMaker *make) {
// make.centerY.equalTo(@(10));
make.center.equalTo(ws).centerOffset(CGPointMake(0, ws.bounds.size.height));
}];
[self layoutIfNeeded];
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
[ws.tipLbl mas_updateConstraints:^(MASConstraintMaker *make) {
// make.centerY.equalTo(@(-15));
make.center.equalTo(ws);
}];
//[self.view updateConstraintsIfNeeded];
[self layoutIfNeeded];
} completion:^(BOOL finished) {
}];
}];
3. CoreAnimation
CABasicAnimation
CABasicAnimation : CAPropertyAnimation
指定動畫的who(key path ),when(duration), where(from,to)使用示例:
- (void)animationbegin:(UIView *)view {
/* 放大縮小 */
// 設定為縮放
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
// 動畫選項設定
animation.duration = 0.1; // 動畫持續(xù)時間
animation.repeatCount = -1; // 重復次數
animation.autoreverses = YES; // 動畫結束時執(zhí)行逆動畫
// 縮放倍數
animation.fromValue = [NSNumber numberWithFloat:1.0]; // 開始時的倍率
animation.toValue = [NSNumber numberWithFloat:0.9]; // 結束時的倍率
// 添加動畫
[view.layer addAnimation:animation forKey:@"scale-layer"];
}
這里需要注意的是animation的一個屬性:fillMode
fillMode的作用就是決定當前對象過了非active時間段的行為. 比如動畫開始之前,動畫結束之后。如果是一個動畫CAAnimation,則需要將其removedOnCompletion設置為NO,要不然fillMode不起作用.
下面來講各個fillMode的意義
- kCAFillModeRemoved 這個是默認值,也就是說當動畫開始前和動畫結束后,動畫對layer都沒有影響,動畫結束后,layer會恢復到之前的狀態(tài)
- kCAFillModeForwards 當動畫結束后,layer會一直保持著動畫最后的狀態(tài)
- kCAFillModeBackwards 這個和kCAFillModeForwards是相對的,就是在動畫開始前,你只要將動畫加入了一個layer,layer便立即進入動畫的初始狀態(tài)并等待動畫開始.你可以這樣設定測試代碼,將一個動畫加入一個layer的時候延遲5秒執(zhí)行.然后就會發(fā)現在動畫沒有開始的時候,只要動畫被加入了layer,layer便處于動畫初始狀態(tài)
- kCAFillModeBoth 理解了上面兩個,這個就很好理解了,這個其實就是上面兩個的合成.動畫加入后開始之前,layer便處于動畫初始狀態(tài),動畫結束后layer保持動畫最后的狀態(tài).
CAKeyframeAnimation
/** General keyframe animation class. **/
@interface CAKeyframeAnimation : CAPropertyAnimation
CAKeyframeAnimation不同于basic是吧動畫從起點到終點均勻劃分到時間片里,而是通過提供一個數值數組
/* An array of objects providing the value of the animation function for
* each keyframe. */
@property(nullable, copy) NSArray *values;
我們可以將duration中的每一幀數值都提供出來,放在數組里
下面給出示例代碼,這里再次感謝Kitten Yang
-(CAKeyframeAnimation *)createSpringAnima:(NSString *)keypath duration:(CFTimeInterval)duration usingSpringWithDamping:(CGFloat)damping initialSpringVelocity:(CGFloat)velocity fromValue:(id)fromValue toValue:(id)toValue{
CGFloat dampingFactor = 10.0;
CGFloat velocityFactor = 10.0;
NSMutableArray *values = [self springAnimationValues:fromValue toValue:toValue usingSpringWithDamping:damping * dampingFactor initialSpringVelocity:velocity * velocityFactor duration:duration];
CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:keypath];
anim.values = values;
anim.duration = duration;
anim.fillMode = kCAFillModeForwards;
anim.removedOnCompletion = NO;
return anim;
}
-(NSMutableArray *) springAnimationValues:(id)fromValue toValue:(id)toValue usingSpringWithDamping:(CGFloat)damping initialSpringVelocity:(CGFloat)velocity duration:(CGFloat)duration{
//60個關鍵幀
NSInteger numOfFrames = duration * 60;
NSMutableArray *values = [NSMutableArray arrayWithCapacity:numOfFrames];
for (NSInteger i = 0; i < numOfFrames; i++) {
[values addObject:@(0.0)];
}
//差值
CGFloat diff = [toValue floatValue] - [fromValue floatValue];
for (NSInteger frame = 0; frame<numOfFrames; frame++) {
CGFloat x = (CGFloat)frame / (CGFloat)numOfFrames;
CGFloat value = [toValue floatValue] - diff * (pow(M_E, -damping * x) * cos(velocity * x)); // y = 1-e^{-5x} * cos(30x)
//Y = a + b *cos(c*x)*e^{-d*x}
// x=0 ==>a + b= from;
// x=1 ==>近似忽略冪次項,==》a = to;
// ==> b = from - to
// 不準確
values[frame] = @(value);
}
return values;
}
這里的算法是假設呈衰減的諧振函數
對于如何選擇相關參數,可以在這個網站中輸入相關函數來觀察圖象
比如在做手勢密碼時,做了一個仿iphone的touch ID輸錯的效果
提示語會來回震動:
-(void)addAlertAnimation{
float positonX = state.layer.position.x;
//Spring animation
CAKeyframeAnimation *anim = [[KYSpringLayerAnimation sharedAnimManager]createSpringAnima:@"position.x" duration:1 usingSpringWithDamping:0.5 initialSpringVelocity:3 fromValue:@(positonX-60) toValue:@(positonX)];
//line animation
// CAKeyframeAnimation *anim = [[KYSpringLayerAnimation sharedAnimManager] createBasicAnima:@"factor" duration:0.4 fromValue:@(0.5+[howmanydistance floatValue]* 1.5) toValue:@(0)];
//half animation
// CAKeyframeAnimation *anim = [[KYSpringLayerAnimation sharedAnimManager] createHalfCurveAnima:@"factor" duration:0.3 fromValue:@(0.5+[howmanydistance floatValue]* 1.5) toValue:@(0)];
anim.delegate = self;
[state.layer addAnimation:anim forKey:@"shaking"];
}
動畫過程中改變label的中心位置,這里要選擇合適的參數使這個震蕩過程至少持續(xù)了一個周期,不然抖動只在一側出現
4. 通過修改layer的相關自有屬性
屬性變化會觸發(fā)動畫,如:
self.arrowImageView.layer.transform = CATransform3DMakeRotation(M_PI, 0, 0, 1);
self.arrowImageView.layer.transform = CATransform3DIdentity;
縮放動畫的關鍵是transform.scale
x軸,y軸同時按比例縮放:
CABasicAnimation *theAnimation;
theAnimation=[CABasicAnimation animationWithKeyPath:@"transform.scale"];
theAnimation.duration=8;
theAnimation.removedOnCompletion = YES;
theAnimation.fromValue = [NSNumber numberWithFloat:1];
theAnimation.toValue = [NSNumber numberWithFloat:0.5];
[yourView.layer addAnimation:theAnimation forKey:@"animateTransform"];
以上縮放是以view的中心點為中心縮放的,如果需要自定義縮放點,可以設置卯點:
//中心點
[yourView.layer setAnchorPoint:CGPointMake(0.5, 0.5)];
//左上角
[yourView.layer setAnchorPoint:CGPointMake(0, 0)];
//右下角
[yourView.layer setAnchorPoint:CGPointMake(1, 1)];
補充
要做一個動效,需要充分理解position與anchorPoint 。這篇文章寫的很不錯,其中的幾個要點:
- position是layer中的anchorPoint在superLayer中的位置坐標。
- 互不影響原則:單獨修改position與anchorPoint中任何一個屬性都不影響另一個屬性。
- frame、position與anchorPoint有以下關系:
frame.origin.x = position.x - anchorPoint.x * bounds.size.width;
frame.origin.y = position.y - anchorPoint.y * bounds.size.height;
第2條的互不影響原則還可以這樣理解:position與anchorPoint是處于不同坐標空間中的重合點,修改重合點在一個坐標空間的位置不影響該重合點在另一個坐標空間中的位置。但這樣會引起frame的變化,若是想改變anchor point后仍維持frame不變,則可以:
- (void) setAnchorPoint:(CGPoint)anchorpoint forView:(UIView *)view
{
CGRect oldFrame = view.frame;
view.layer.anchorPoint = anchorpoint;
view.frame = oldFrame;
}
重設一下就好了
實現組合動畫:
// Animation group
CAAnimationGroup* group = [CAAnimationGroup animation];
group.animations = [NSArray arrayWithObjects:colorAnim, widthAnim, nil];
group.duration = 5.0;
[myLayer addAnimation:group forKey:@"BorderChanges"];
注意組合動畫中默認是并行的,要實現連貫的需要指定動畫的起始時間
// animation2.beginTime = CACurrentMediaTime()+ animation1.duration;