引文
这里举例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) {
// 动画完成后执行
// 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; // 动画持续时间
animation.repeatCount = -1; // 重复次数
animation.autoreverses = YES; // 动画结束时执行逆动画
// 缩放倍数
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会恢复到之前的状态
- kCAFillModeForwards 当动画结束后,layer会一直保持着动画最后的状态
- kCAFillModeBackwards 这个和kCAFillModeForwards是相对的,就是在动画开始前,你只要将动画加入了一个layer,layer便立即进入动画的初始状态并等待动画开始.你可以这样设定测试代码,将一个动画加入一个layer的时候延迟5秒执行.然后就会发现在动画没有开始的时候,只要动画被加入了layer,layer便处于动画初始状态
- kCAFillModeBoth 理解了上面两个,这个就很好理解了,这个其实就是上面两个的合成.动画加入后开始之前,layer便处于动画初始状态,动画结束后layer保持动画最后的状态.
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; framea + 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的中心位置,这里要选择合适的参数使这个震荡过程至少持续了一个周期,不然抖动只在一侧出现
4. 通过修改layer的相关自有属性
属性变化会触发动画,如:
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;