# 要想做好动效，你得先知道这些

## 二、利用物理公式探究缓动曲线

iOS 9 提供了 CASpringAnimation 类实现该效果，而 Web 上就没有提供类似函数。但我们仍然可以通过以前学过的物理学和数学知识来做一下研究。

## 三、常用缓动曲线

EaseIn 是从慢到快的曲线，就像开车时先慢后快，EaseOut 和 EaseIn 的曲线图像关于 (0.5,0.5) 中心对称，EaseInOut：分别由 EaseIn、EaseOut 分别缩小一半，然后再拼接一起。

// Modeled after the parabola y = x^2
{
returnp * p;
}
// Modeled after the parabola y = -x^2 + 2x
{
return-(p * (p -2));
}
// Modeled after the piecewise quadratic
// y = (1/2)((2x)^2) ; [0, 0.5)
// y = -(1/2)((2x-1)*(2x-3) - 1) ; [0.5, 1]
{
if(p <0.5){
return2* p * p;
}else{
return(-2* p * p) + (4* p) -1;
}
}

// Modeled after the cubic y = x^3
doublefsCubicEaseIn(doublep)
{
returnp * p * p;
}
// Modeled after the cubic y = (x - 1)^3 + 1
doublefsCubicEaseOut(doublep)
{
doublef = (p -1);
returnf * f * f +1;
}
// Modeled after the piecewise cubic
// y = (1/2)((2x)^3) ; [0, 0.5)
// y = (1/2)((2x-2)^3 + 2) ; [0.5, 1]
doublefsCubicEaseInOut(doublep)
{
if(p <0.5){
return4* p * p * p;
}else{
doublef = ((2* p) -2);
return0.5* f * f * f +1;
}
}

// Modeled after the quartic x^4
doublefsQuarticEaseIn(doublep)
{
returnp * p * p * p;
}
// Modeled after the quartic y = 1 - (x - 1)^4
doublefsQuarticEaseOut(doublep)
{
doublef = (p -1);
returnf * f * f * (1- p) +1;
}
// Modeled after the piecewise quartic
// y = (1/2)((2x)^4) ; [0, 0.5)
// y = -(1/2)((2x-2)^4 - 2) ; [0.5, 1]
doublefsQuarticEaseInOut(doublep)
{
if(p <0.5){
return8* p * p * p * p;
}else{
doublef = (p -1);
return-8* f * f * f * f +1;
}
}
// Modeled after the quintic y = x^5
doublefsQuinticEaseIn(doublep)
{
returnp * p * p * p * p;
}
// Modeled after the quintic y = (x - 1)^5 + 1
doublefsQuinticEaseOut(doublep)
{
doublef = (p -1);
returnf * f * f * f * f +1;
}
// Modeled after the piecewise quintic
// y = (1/2)((2x)^5) ; [0, 0.5)
// y = (1/2)((2x-2)^5 + 2) ; [0.5, 1]
doublefsQuinticEaseInOut(doublep)
{
if(p <0.5){
return16* p * p * p * p * p;
}else{
doublef = ((2* p) -2);
return0.5* f * f * f * f * f +1;
}
}


Sine ：正弦函数曲线，常用于模拟波浪和呼吸效果

// Modeled after quarter-cycle of sine wave
doublefsSineEaseIn(doublep)
{
returnsin((p -1) * M_PI_2) +1;
}
// Modeled after quarter-cycle of sine wave (different phase)
doublefsSineEaseOut(doublep)
{
returnsin(p * M_PI_2);
}
// Modeled after half sine wave
doublefsSineEaseInOut(doublep)
{
return0.5* (1-cos(p * M_PI));
}


Expo：2^(10(x-1))，指数函数，开始很慢后期很快

// Modeled after the exponential function y = 2^(10(x - 1))
doublefsExponentialEaseIn(doublep)
{
return(p ==0.0) ? p : pow(2,10* (p -1));
}
// Modeled after the exponential function y = -2^(-10x) + 1
doublefsExponentialEaseOut(doublep)
{
return(p ==1.0) ? p :1- pow(2,-10* p);
}
// Modeled after the piecewise exponential
// y = (1/2)2^(10(2x - 1)) ; [0,0.5)
// y = -(1/2)*2^(-10(2x - 1))) + 1 ; [0.5,1]
doublefsExponentialEaseInOut(doublep)
{
if(p ==0.0|| p ==1.0)returnp;

if(p <0.5){
return0.5* pow(2, (20* p) -10);
}else{
return-0.5* pow(2, (-20* p) +10) +1;
}
}


Circ：顾名思义就是弧（1/4 圆，如果选择了 InOut 就是两个外切的 1/4 圆）

// Modeled after shifted quadrant IV of unit circle
doublefsCircularEaseIn(doublep)
{
return1- sqrt(1- (p * p));
}
// Modeled after shifted quadrant II of unit circle ? ?
doublefsCircularEaseOut(doublep)
{
returnsqrt((2- p) * p);
}
// Modeled after the piecewise circular function
// y = (1/2)(1 - sqrt(1 - 4x^2)) ; [0, 0.5)
// y = (1/2)(sqrt(-(2x - 3)*(2x - 1)) + 1) ; [0.5, 1]
doublefsCircularEaseInOut(doublep)
{
if(p <0.5){
return0.5* (1- sqrt(1-4* (p * p)));
}else{
return0.5* (sqrt(-((2* p) -3) * ((2* p) -1)) +1);
}
}


Bounce：这是个模拟小球落地的反弹曲线，运动时间每次按 0.5 倍衰减

doublefsBounceEaseIn(doublep)
{
return1- fsBounceEaseOut(1- p);
}
doublefsBounceEaseOut(doublep)
{
if(p <4/11.0){
return(121* p * p)/16.0;
}
elseif(p <8/11.0){
return(363/40.0* p * p) - (99/10.0* p) +17/5.0;
}
elseif(p <9/10.0){
return(4356/361.0* p * p) - (35442/1805.0* p) +16061/1805.0;
}else{
return(54/5.0* p * p) - (513/25.0* p) +268/25.0;
}
}
doublefsBounceEaseInOut(doublep)
{
if(p <0.5){
return0.5* fsBounceEaseIn(p*2);
}else{
return0.5* fsBounceEaseOut(p *2-1) +0.5;
}
}


Back: 这是个模拟弹簧运动过阻尼曲线

// Modeled after the overshooting cubic y = x^3-x*sin(x*pi)
doublefsBackEaseIn(doublep)
{
returnp * p * p - p * sin(p * M_PI);
}
// Modeled after overshooting cubic y = 1-((1-x)^3-(1-x)*sin((1-x)*pi))
doublefsBackEaseOut(doublep)
{
doublef = (1- p);
return1- (f * f * f - f * sin(f * M_PI));
}
// Modeled after the piecewise overshooting cubic function:
// y = (1/2)*((2x)^3-(2x)*sin(2*x*pi)) ; [0, 0.5)
// y = (1/2)*(1-((1-x)^3-(1-x)*sin((1-x)*pi))+1) ; [0.5, 1]
doublefsBackEaseInOut(doublep)
{
if(p <0.5){
doublef =2* p;
return0.5* (f * f * f - f * sin(f * M_PI));
}else{
doublef = (1- (2*p -1));
return0.5* (1- (f * f * f - f * sin(f * M_PI))) +0.5;
}
}


Elastic：这是个模拟弹簧运动欠阻尼曲线，就是我们前面研究想得出的曲线,

// Modeled after the damped sine wave y = sin(13pi/2*x)*pow(2, 10 * (x - 1))
doublefsElasticEaseIn(doublep)
{
returnsin(13* M_PI_2 * p) * pow(2,10* (p -1));
}
// Modeled after the damped sine wave y = sin(-13pi/2*(x + 1))*pow(2, -10x) + 1
doublefsElasticEaseOut(doublep)
{
returnsin(-13* M_PI_2 * (p +1)) * pow(2,-10* p) +1;
}
// Modeled after the piecewise exponentially-damped sine wave:
// y = (1/2)*sin(13pi/2*(2*x))*pow(2, 10 * ((2*x) - 1)) ; [0,0.5)
// y = (1/2)*(sin(-13pi/2*((2x-1)+1))*pow(2,-10(2*x-1)) + 2) ; [0.5, 1]
doublefsElasticEaseInOut(doublep)
{
if(p <0.5){
return0.5* sin(13* M_PI_2 * (2* p)) * pow(2,10* ((2* p) -1));
}else{
return0.5* (sin(-13* M_PI_2 * ((2* p -1) +1)) * pow(2,-10* (2* p -1)) +2);
}
}


LTMorphingLabel 用 Swift 编写的 UILabel 子类，实现了 iOS8 中 iMessage 文字变换动画。它用到了 EaseInQuint，EaseOutQuint，EaseOutBack，EaseOutBounce 4 个缓动曲线，下面是一些效果图，另外附上我写的 OC 版本源码 FSMorphingLabel：

## 四、使用 Matlab 曲线拟合尝试剖析苹果 ScrollView 动效参数

xdata = [7.4811267.0771556.7643866.6158956.3988476.0123185.3360425.0374384.6324232.4996861.9013681.3760710.7707630.5234220.5078040.460093];
ydata = [3732.0000003530.0000003374.0000003299.5000003191.0000002998.0000002660.5000002511.0000002309.0000001243.500000944.500000682.500000380.000000256.500000248.500000225.000000];
p = polyfit(xdata, ydata,1)

fitxdataArr =0:0.2:15;
yFitArr = polyval(p, fitxdataArr);

plot(xdata, ydata,'o');
hold on; grid on;
plot(fitxdataArr, yFitArr,'linewidth',2);
xlabel('速度（v）');
ylabel('里程（s）');
legend('原始数据','拟合曲线')


myfun.m：

functionF=myfun(x, xdata)
F=x(1)*log2(xdata)+x(2);
end


fit.m：

xdata = [7.4811267.0771556.7643866.6158956.3988476.0123185.3360425.0374384.6324232.4996861.9013681.3760710.7707630.5234220.5078040.460093];
ydata = [3.2967693.2793253.2469853.2302963.2294003.1967633.1609443.0968773.0512052.7687842.6345922.4677052.1848121.9847121.9505201.900448];

x0 = [20];
[coefArr,resnorm] = lsqcurvefit(@myfun, x0,xdata, ydata)

fitxdataArr =0:0.2:20;
yFitArr = myfun(coefArr, fitxdataArr);
plot(xdata, ydata, fitxdataArr, yFitArr,'linewidth',1);


myfun.m：

functionF=myfun(x, xdata)
F=x(1)*2.^(x(2)*xdata) +x(3);
end


fit.m：

xdata = [0.0000000.0003780.0051090.0115600.0180970.0245820.0310210.0375140.0439670.0503960.0568480.0633430.0697540.0762350.0827030.0891500.0956070.1020780.1085350.1149840.1214420.1279010.1343630.1408350.1472830.1537360.1601990.1666600.1731180.1795910.1860180.1923990.1988850.2053180.2118140.2182800.2247350.2312490.2377280.2441670.2506270.2571050.2635400.2699780.2764160.2829280.2893830.2958620.3022940.3087810.3152410.3216810.3281270.3346160.3410500.3475150.3539930.3604360.3668840.3733690.3798040.3862890.3927510.3991910.4056410.4121260.4185650.4250370.4314300.4379480.4443950.4508790.4573180.4637810.4702270.4766940.4831500.4897880.4960730.5025280.5090030.5154500.5219070.5283840.5348480.5412230.5477980.5542150.5605760.5670790.5735660.5799770.5864400.5929020.5993600.6058270.6123320.6188090.6252500.6317150.6381730.6446500.6510820.6575770.6639560.6704720.6769260.6834050.6898410.6963040.7027800.7092260.7156720.7221620.7285950.7350590.7415370.7479720.7544280.7608730.7673100.7737470.7802750.7867290.7931850.7996700.8061050.8190030.8253980.8319530.8384240.8512830.8576810.8642430.8771130.8900310.8964370.9094120.9223410.9352560.9481690.9674460.9804120.999779];
ydata = [0.0000000.0330530.0560220.0868350.1170870.1462180.1742300.2016810.2280110.2532210.2778710.3019610.3249300.3473390.3686270.3899160.4100840.4296920.4481790.4666670.4840340.5014010.5176470.5338940.5490200.5641460.5787110.5927170.6061620.6190480.6319330.6442580.6560220.6672270.6784310.6890760.6997200.7098040.7193280.7288520.7378150.7462180.7551820.7630250.7708680.7787110.7859940.7932770.8005600.8067230.8134450.8196080.8257700.8319330.8375350.8431370.8481790.8532210.8582630.8633050.8677870.8722690.8767510.8806720.8851540.8890760.8924370.8963590.8997200.9030810.9064430.9098040.9131650.9159660.9187680.9215690.9243700.9271710.9294120.9322130.9344540.9366950.9389360.9411760.9434170.9450980.9473390.9490200.9507000.9529410.9546220.9563030.9574230.9591040.9607840.9619050.9635850.9647060.9663870.9675070.9686270.9697480.9708680.9719890.9731090.9742300.9753500.9764710.9770310.9781510.9787110.9798320.9803920.9815130.9820730.9831930.9837540.9843140.9848740.9854340.9865550.9871150.9876750.9882350.9887960.9893560.9899160.9904760.9910360.9915970.9921570.9927170.9932770.9938380.9943980.9949580.9955180.9960780.9966390.9971990.9977590.9983190.9988800.999440];

x0 = [0.001-100];
[coefArr,resnorm] = lsqcurvefit(@myfun, x0, xdata, ydata)

fitxdataArr =0:0.01:1;
yFitArr = myfun(coefArr, fitxdataArr);
subplot(1,2,1);
plot(xdata, ydata, fitxdataArr, yFitArr,'linewidth',2);
legend('原始数据','拟合曲线');
xlabel('时间（t）');
ylabel('');
grid on,axis equal

subplot(1,2,2);
plot(xdata, ydata)
legend('原始数据');
xlabel('时间（t）');
ylabel('');
grid on,axis equal


## 五、使用线性插值高仿 APP 动效

AppSotre 上有很多让人惊艳的 APP，他们的交互值得我们学习，下面我以蘑菇街为例简单分析一下如何在没有设计稿和动效参数的情况下，使用简单的线性插值来做出几乎跟原版一模一样的交互。

floatcalculate(floatbegin,floatend,floatlowerBound,floatupperBound,floatcurVal)
{
if(curValupperBound) {
curVal = upperBound;
}
floatt = (curVal-lowerBound) / (upperBound-lowerBound);
returnbegin+ (end-begin)*t;;
}