Skip to content

1.1 曲线和弹性动画的定义与开发

MartinRGB edited this page Dec 13, 2018 · 8 revisions

设计者非常在意动画的曲线实现问题,因为这是使用感觉和动画体验的核心

在主流的动画设计中(After Effect、Origami、Principle、FramerJS),由于动画引擎不同的缘故,导致同样的设计效果不同。

然而,其中的贝塞尔插值函数在开发过程中具有相当的借鉴意义。

一、曲线动画的贝塞尔插值法

ScreenShoot

贝塞尔的原理可以通过这个视频来学习

贝塞尔插值在动画的设计和开发中得以广泛应用,是自定义动画最常用的方式

在设计过程中,设计者可以获取非常有意义的参数 —— 动画时间、属性变化量、以及贝塞尔插值曲线

获取到贝塞尔插值后和上述参数后,开发者可以实现动画的还原。

iOS开发(利用 Core Animation )

CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"position.x";
animation.fromValue = @77;
animation.toValue = @455;
animation.duration = 1;
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
animation.timingFunction =[CAMediaTimingFunction  functionWithControlPoints:0.78:0.36:0.14:1.09];

[myView.layer addAnimation:animation forKey:@"myKey"];

Android开发:

PathInterpolator

前端开发者:

div {
  -webkit-transition: all 600ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
  transition:         all 600ms cubic-bezier(0.25, 0.46, 0.45, 0.94); 
}

参考资料

1.Cubic-Bezier.com

2.Bezier Curve based easing functions – from concept to implementation

3.Bezier Curves and Picasso

二、函数动画

有些情况下,设计者和开发者可能需要使用函数来描述动画,一般应用以下几个场景:

  1. 设计者使用 AE 制作弹性动画时,使用表达式
if (nearestKeyIndex > 0 && currentTime < 1) {
  calculatedVelocity = velocityAtTime(key(nearestKeyIndex).time - thisComp.frameDuration / 10);
  amplitude = 1.1;
  frequency = 2.0;
  decay = 4.0;
  value + calculatedVelocity * amplitude * Math.sin(frequency * currentTime * 2 * Math.PI) / Math.exp(decay * currentTime);
} else {
  value;
}

效果如下:

  1. 同样的,在 AE 中也可以利用函数描述近似贝塞尔曲线的缓动动画(Ease&Wizz JS 文件下载
//去
var p = 0.8;		// period for elastic
var a = 50;			// amplitude for elastic
var s = 1.70158;	// overshoot amount for "back"
function outQuart(t, b, c, d, a, p) {
	return -c * ((t=t/d-1)*t*t*t - 1) + b;
}
//回
var p = 0.8;		// period for elastic
var a = 50;			// amplitude for elastic
var s = 1.70158;	// overshoot amount for "back"
function inOutQuad(t, b, c, d, a, p) {
	if ((t/=d/2) < 1) return c/2*t*t + b;
	return -c/2 * ((--t)*(t-2) - 1) + b;
}

效果如下:

那么这种缓动函数效果,iOS 中有 AHEasing 这个第三方库

3.1 案例1:利用函数可以将手势交互变化量动画属性良好结合,实现真实、细腻的交互效果(案例来自 Kitten Yang 的 《A Guide to iOS animation》 Github地址

这个跟随 Pan 手势变化的交互效果,代码如下

-(void)panGestureRecognized:(UIPanGestureRecognizer *)pan{
 
    static CGPoint initialPoint;
    CGFloat factorOfAngle = 0.0f;
    CGFloat factorOfScale = 0.0f;
    CGPoint transition = [pan translationInView:self.superview];
 
    if (pan.state == UIGestureRecognizerStateBegan) {
 
        initialPoint = self.center;
 
    }else if(pan.state == UIGestureRecognizerStateChanged){
 
 
        self.center = CGPointMake(initialPoint.x,initialPoint.y + transition.y);
 
        CGFloat Y =MIN(SCROLLDISTANCE,MAX(0,ABS(transition.y)));
 
        //一个开口向下,顶点(SCROLLDISTANCE/2,1),过(0,0),(SCROLLDISTANCE,0)的二次函数
        factorOfAngle = MAX(0,-4/(SCROLLDISTANCE*SCROLLDISTANCE)*Y*(Y-SCROLLDISTANCE));
        //一个开口向下,顶点(SCROLLDISTANCE,1),过(0,0),(2*SCROLLDISTANCE,0)的二次函数
        factorOfScale = MAX(0,-1/(SCROLLDISTANCE*SCROLLDISTANCE)*Y*(Y-2*SCROLLDISTANCE));
 
        CATransform3D t = CATransform3DIdentity;
        t.m34  = 1.0/-1000;
        t = CATransform3DRotate(t,factorOfAngle*(M_PI/5), transition.y>0?-1:1, 0, 0);
        t = CATransform3DScale(t, 1-factorOfScale*0.2, 1-factorOfScale*0.2, 0);
 
        self.layer.transform = t;
    }
}

3.2 案例2:获取 Pan 手势的手指位置后,传递给贝塞尔图形,实现拉窗帘一般的效果。

窗帘代码如下:

func createPulledPath(width:CGFloat,point:CGPoint) -> UIBezierPath{
        let height = view.bounds.height
        let offset = width + point.x
        
        let path = UIBezierPath()
        path.moveToPoint(CGPoint(x: -mtExtenedEdgesOffset, y: -mtExtenedEdgesOffset))
        path.addLineToPoint(CGPoint(x: width, y: -mtExtenedEdgesOffset))
        
        // add curve
        path.addCurveToPoint(CGPoint(x: offset, y: point.y),
            controlPoint1: CGPoint(x: width, y: point.y * mtControlPointRatio),
            controlPoint2: CGPoint(x: offset, y: point.y - mtControlPointPulledDistance))
        
        path.addCurveToPoint(CGPoint(x: width, y: height + mtExtenedEdgesOffset),
            controlPoint1: CGPoint(x: offset, y: point.y + mtControlPointPulledDistance),
            controlPoint2: CGPoint(x: width, y: point.y + (height - point.y) * (1 - mtControlPointRatio)))
        
        path.addLineToPoint(CGPoint(x: -mtExtenedEdgesOffset, y: height + mtExtenedEdgesOffset))
        
        path.closePath()
        return path

        
}

然后利用贝塞尔反解函数,传入手指当前点和贝塞尔图形信息,获取实时状态下,曲线上各个点的位置,然后传给方块,获取方块x、y代码如下:

func usefourmulaGetX(width:CGFloat,point:CGPoint,x0:CGFloat,x1:CGFloat,x2:CGFloat,x3:CGFloat,t:CGFloat) -> CGFloat {
        let xValue:CGFloat = x0*(1-t)*(1-t)*(1-t) + 3*x1*t*(1-t)*(1-t) + 3*x2*t*t*(1-t) + x3*t*t*t
        return xValue
    }
    
func usefourmulaGetY(width:CGFloat,point:CGPoint,y0:CGFloat,y1:CGFloat,y2:CGFloat,y3:CGFloat,t:CGFloat) -> CGFloat {
        let yValue:CGFloat = y0*(1-t)*(1-t)*(1-t) + 3*y1*t*(1-t)*(1-t) + 3*y2*t*t*(1-t) + y3*t*t*t
        return yValue
    }

由上述案例可见,在制作交互式动画时,函数式动画可以发挥极大的作用,然而对于设计者和开发者来说,成本略大。

而一般情况下,普通的贝塞尔曲线动画就能满足设计者和开发者的需求。

4.安卓开发时,使用 RapidInterpolator ,也可以调节原声插值器动画 以及 easing.net那套Easing函数动画

参考资料

1.缓动函数速查表

2.Easing Equations

3.More Animation Curves than You Can Shake a Stick at

4.AHEasing 开源库 (可以尝试简单修改以便与 Ease & Wizz 的JS函数对接)

三、弹性动画

一般来说有以下几种弹性动画的制作方法,设计与开发对接的形式也有所不同:

1.使用 UIkit Dynamic 中的SnapBehavior

2.使用 UIView 动画的 Spring 形式

3.使用上文提到的函数来描述一根弹性动画曲线

//UI动力学
self.Cardview.center = CGPointMake(160, 219.5)
self.snapBehavior = UISnapBehavior(item: self.Cardview, snapToPoint: CGPointMake(160, 219.5))

//UIView - Spring
UIView.animateWithDuration(0.5, delay: 0,usingSpringWithDamping: 0.7, initialSpringVelocity: 0, options: .CurveEaseOut, animations: {
                self.introbtn.frame.origin.y = 692
                }, completion: { finished in
            })

这几种方法不一一赘述,当设计者在 AE 中使用弹性函数时,开发者需要解读 AE 表达式,转写到程序中 当开发者使用 UI 动力学和 UIView Spring动画时,设计者需要在开发者的代码中进行参数调试,以便达到最佳效果。

4.使用 Facebook POP 与 Origami

利用 Origami 这一设计工具结合 POP 动画库,可以高度还原设计效果,设计者给出 BouncinessSpeed 参数即可

Quartz Composer

POP动画的写法类似CAAnimation:

    POPSpringAnimation *switchFrameAnim = [POPSpringAnimation animation];
    switchFrameAnim.property = [POPAnimatableProperty propertyWithName:kPOPViewFrame];
    switchFrameAnim.springBounciness = 20;
    switchFrameAnim.springSpeed = 10;
    switchFrameAnim.toValue = [NSValue valueWithCGRect:CGRectMake(137.5, 450,  100, 100)];
    [_myBtn pop_addAnimation:switchFrameAnim forKey:@"switchBtnFrame"];

效果对比:

效果对比

POP动画可以做 CompletionBlock:

    anim.completionBlock = ^(POPAnimation *anim, BOOL finished) {
    if(finished){NSLog(@"Animation finished!");}};
    }

POP动画可以获取动画的 Progress:

//获取某一动画属性的Progress
    POPSpringAnimation *progressAnim = [POPSpringAnimation animation];
    progressAnim.springBounciness = smallBounciness;
    progressAnim.springSpeed = smallSpeed;
    progressAnim.property = [POPAnimatableProperty propertyWithName:@"popAnimationProgress" initializer:^(POPMutableAnimatableProperty *prop) {
        prop.readBlock = ^(ViewController *obj, CGFloat values[]) {
            values[0] = obj.popAnimationProgress;
        };
        prop.writeBlock = ^(ViewController *obj, const CGFloat values[]) {
            obj.popAnimationProgress = values[0];
        };
        prop.threshold = 0.001;
    }];
    [self pop_addAnimation:progressAnim forKey:@"popAnimation"];

可以给任何属性进行自定义动画:

//阴影动画
    POPSpringAnimation *shadowAnim = [POPSpringAnimation animation];
    //Custom Animation Property
    _animateableshadowOpacityProperty = [POPAnimatableProperty propertyWithName:@"shadowOpacity" initializer:^(POPMutableAnimatableProperty *prop) {
        // read value
        prop.readBlock = ^(CALayer * shadowOpacity , CGFloat values[]) {
            values[0] = shadowOpacity.shadowOpacity;
        };
        // write value
        prop.writeBlock = ^(CALayer * shadowOpacity, const CGFloat values[]) {
            shadowOpacity.shadowOpacity = values[0];
        };
        // dynamics threshold
        prop.threshold = 0.01;
    }];
    shadowAnim.property = _animateableshadowOpacityProperty;
    shadowAnim.springBounciness = smallBounciness;
    shadowAnim.springSpeed = smallSpeed;
    [_shadowView.layer pop_addAnimation:shadowAnim forKey:@"shadowOpacity"];

5.转换 spring-rk4 到 Facebook pop bouncy animation 效果对比 Framer JS 的 spring-rk4动画,可以使用ReboundJS中的转化公式,转为POP动画,方便了 iOS 开发 和 Web 开发

convert = (tension, friction) ->
	result = 
		tension :  Utils.round (tension - 194.0) / 3.62 + 30.0
		friction : Utils.round (friction - 25.0) / 3.0 + 8.0
	return result
 

print convert 600, 60
# » {tension:142, friction:20}

参考资料

1.POP使用指南

2.POP进阶指南

四、定义常用曲线动画

贝塞尔插值 缓动类型 效果 图表
bezier(0.33,0,0.25,1) EaseOut1
bezier(0.33,0,0.33,1) EaseOut2
bezier(0.33,0,0.46,1) EaseOut3
bezier(0.55,0,0.1,1) EaseOut4(SwiftOut)
bezier(0.78,0.36,0.14,1.09) EaseOut5(Back)
bezier(0.66,0,0.33,1) EaseInOut1
bezier(0.33,0,0.66,1) EaseInOut2

以上曲线效果可以通过贝塞尔插值法进行复用,保证了设计效果和开发效果的一致,其他类型曲线效果待定

You can’t perform that action at this time.