Android 动画里的贝塞尔曲线

Android 动画里的贝塞尔曲线

对贝塞尔曲线的听闻,大概是 Photoshop 里的钢笔工具,用来画曲线的,但一直不明白这个曲线有什么用,接触学习到 Android 动画,又发现了贝塞尔曲线的身影,这玩意不是在绘图软件里画曲线的吗,怎么和动画扯上关系了,好吧,今天高低得来了解一下。

插值

在数学里面的插值(Interpolation),是一种通过已知的、离散的数据点,在范围内推求新数据点的过程或方法。

下面这个表给出了某个未知函数 <math xmlns="http://www.w3.org/1998/Math/MathML"> f f </math>f 的值

x f(x)
0 0
1 0.08415
2 0.9093
3 0.1411
4 −0.7568
5 −0.9589
6 −0.2794

通过插值就可以估算中间点函数,如得到 <math xmlns="http://www.w3.org/1998/Math/MathML"> x = 2.5 x=2.5 </math>x=2.5 时的值。

插值方法有许多如片段插值、线性插值、多项式插值等等

线性插值

假设我们已知坐标 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( x 0 ,   y 0 ) \left ( {{x}{0},\, {y}{0}} \right ) </math>(x0,y0) 与 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( x 1 ,   y 1 ) \left ( {{x}{1},\, {y}{1}} \right ) </math>(x1,y1),求: <math xmlns="http://www.w3.org/1998/Math/MathML"> [ x 0 ,   x 1 ] \left [ {{x}{0},\, {x}{1}} \right ] </math>[x0,x1] 区间内某一位置 <math xmlns="http://www.w3.org/1998/Math/MathML"> x x </math>x 在所对应的 <math xmlns="http://www.w3.org/1998/Math/MathML"> y y </math>y 值。

因为 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( x 0 ,   y 0 ) \left ( {{x}{0},\, {y}{0}} \right ) </math>(x0,y0) 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( x ,   y ) \left ( x,\, y \right ) </math>(x,y) 之间的斜率,与 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( x 0 ,   y 0 ) \left ( {{x}{0},\, {y}{0}} \right ) </math>(x0,y0) 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( x 1 ,   y 1 ) \left ( {{x}{1},\, {y}{1}} \right ) </math>(x1,y1) 之间的斜率相同,所以:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> y − y 0 x − x 0 = y 1 − y 0 x 1 − x 0   {\frac {y-{y}{0}} {x-{x}{0}}=\frac {{y}{1}-{y}{0}} {{x}{1}-{x}{0}}\, } </math>x−x0y−y0=x1−x0y1−y0

其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> x 0 {x}{0} </math>x0、 <math xmlns="http://www.w3.org/1998/Math/MathML"> y 0 {y}{0} </math>y0、 <math xmlns="http://www.w3.org/1998/Math/MathML"> x 1 {x}{1} </math>x1、 <math xmlns="http://www.w3.org/1998/Math/MathML"> y 1 {y}{1} </math>y1、 <math xmlns="http://www.w3.org/1998/Math/MathML"> x x </math>x 都已知,那么:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> y = y 1 − y 0 x 1 − x 0 ⋅ ( x − x 0 ) + y 0 = y 0 + ( y 1 − y 0 ) ⋅ x − x 0 x 1 − x 0 y=\frac {{y}{1}-{y}{0}} {{x}{1}-{x}{0}}·\left ( {x-{x}{0}} \right )+{y}{0}={y}{0}+\left ( {{y}{1}-{y}{0}} \right )·\frac {x-{x}{0}} {{x}{1}-{x}{0}} </math>y=x1−x0y1−y0⋅(x−x0)+y0=y0+(y1−y0)⋅x1−x0x−x0

整个过程,和上面的例子,线性插值估算未知函数 <math xmlns="http://www.w3.org/1998/Math/MathML"> f ( 2.5 ) f(2.5) </math>f(2.5) 的值是不是一样?

贝塞尔曲线

线性贝塞尔曲线

线性贝塞尔曲线是一条两点之间的直线,给定点 <math xmlns="http://www.w3.org/1998/Math/MathML"> P 0 {P}{0} </math>P0、 <math xmlns="http://www.w3.org/1998/Math/MathML"> P 1 {P}{1} </math>P1,这条线由下式给出:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> B ( t ) = P 0 + ( P 1 − P 0 ) ⋅ t ,   t ∈ [ 0 ,   1 ] B\left ( {t} \right )={P}{0}+\left ( {{P}{1}-{P}_{0}} \right )·t,\, t\in \left [ {0,\, 1} \right ] </math>B(t)=P0+(P1−P0)⋅t,t∈[0,1]

这不就是线性插值嘛,线性插值的结果也在一条两点之间的直线上。

二次方贝塞尔曲线

既然两个点线性插值可以表示一条两点之间的直线,那...3个点线性插值的结果,几何表示会是什么样?

点 <math xmlns="http://www.w3.org/1998/Math/MathML"> P 0 {P}{0} </math>P0、 <math xmlns="http://www.w3.org/1998/Math/MathML"> P 1 {P}{1} </math>P1 插值得到连续点 <math xmlns="http://www.w3.org/1998/Math/MathML"> Q 0 {Q}{0} </math>Q0,描述线段 <math xmlns="http://www.w3.org/1998/Math/MathML"> P 0 P 1 {P}{0}{P}_{1} </math>P0P1,是一条线性贝塞尔曲线;

点 <math xmlns="http://www.w3.org/1998/Math/MathML"> P 1 P 2 {P}{1}{P}{2} </math>P1P2 插值得到连续点 <math xmlns="http://www.w3.org/1998/Math/MathML"> Q 1 {Q}{1} </math>Q1,描述线段 <math xmlns="http://www.w3.org/1998/Math/MathML"> P 1 P 2 {P}{1}{P}_{2} </math>P1P2,是一条线性贝塞尔曲线;

点 <math xmlns="http://www.w3.org/1998/Math/MathML"> Q 0 Q 1 {Q}{0}{Q}{1} </math>Q0Q1 再插值,也就是对图中绿色线段进行插值,得到连续点 <math xmlns="http://www.w3.org/1998/Math/MathML"> B B </math>B,描述曲线 <math xmlns="http://www.w3.org/1998/Math/MathML"> P 0 P 2 {P}{0}{P}{2} </math>P0P2,也就是图中红色曲线,是一条二次贝塞尔曲线。

原来,两个及以上的点 线性插值函数 就是 贝塞尔曲线函数,我们可以简单地不断循坏迭代两点线性插值来得到最终结果。

动画速度曲线 与 三次方贝塞尔曲线

无论是网页设计里的 CSS 还是 Android 开发,它们里面的动画速度曲线其实是三次方贝塞尔曲线,由 4 个点不断两两插值得到。

我们自定义自己的动画曲线(三次方贝塞尔曲线)时,里面包含 4 个点的信息,其中第一个和最后一个点的坐标是 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( 0 , 0 ) (0, 0) </math>(0,0) 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( 1 , 1 ) (1, 1) </math>(1,1),我们还需要提供中间两个点的坐标。老实说...当初看到自定义动画曲线要填入 4 个数字,真是百思不得其解......

另外,你可以用网址 cubic-bezier 快速定制自己的动画曲线。

Jetpack Compose------Easing

现实生活中极少存在线性匀速运动的场景,汽车启动、停下、自由落体运动等等都包含加速、减速,人的脑子里已经潜移默化地习惯了这种加速减速地运动。动画也是一种运动,设计动画的时候应该遵循现实世界的物理模型,让动画看起来更加自然,符合直觉。

缓动 Ease,表示缓慢地移动(缓动),在 CSS 过渡动画里面,我们可以选择动画的缓动(Easing)类型,其中一些关键字有:

  • linear
  • ease-in
  • ease-out
  • ease-in-out

在经典动画中,开始阶段缓慢,然后加速的动作称为 "slow in";开始阶段运动较快,然后减速的动作称为 "slow out"。网络上面分别叫 "ease in" 和 "ease out",这里的 in/out 可以理解成一个动画里的一开始(start)或者最后(end)

slow in (ease in)

比较适合出场动画,因为开始阶段比较慢,容易让人注意到哪个元素要开始移动,然后加速飞到视线之外。

好比你送朋友,看到朋友上了车,车子缓缓启动,然后加速驶去。

slow out (ease out)

比较适合进场动画,因为结束阶段比较缓慢,能让人清楚看到是哪个元素飞了进来。

就像你站在公交车站,看到一辆公交车远远飞速驶来,减速停下。

ease in out

那 ease in out 又是啥呢?ease 是缓和的意思,而 in/out 前面说过可以看作是一次动画里面的开始或结束阶段。ease in out 自然就代表:在一次动画里的开始阶段和结束阶段,动作都是缓和的,仅中间阶段是加速的,能够将用户注意力集中在过渡的末端。这也是 Material Design 的标准缓动,由于现实世界中的物体不会立即开始或停止移动,这种缓动类型可以让动画更有质感。

这种动画曲线比较适合转换动画,也就是说一个元素运动过程中,没有涉及入场与离场,它始终位于屏幕内,只是由一种形态变换为另一种形态。

Jetpack Compose 里面,表示动画速度曲线的接口是 Easing,Compose 提供了 4 中常见的速度曲线:

kotlin 复制代码
/**
 * Elements that begin and end at rest use this standard easing. They speed up quickly
 * and slow down gradually, in order to emphasize the end of the transition.
 *
 * Standard easing puts subtle attention at the end of an animation, by giving more
 * time to deceleration than acceleration. It is the most common form of easing.
 *
 * This is equivalent to the Android `FastOutSlowInInterpolator`
 */
val FastOutSlowInEasing: Easing = CubicBezierEasing(0.4f, 0.0f, 0.2f, 1.0f)

/**
 * Incoming elements are animated using deceleration easing, which starts a transition
 * at peak velocity (the fastest point of an element's movement) and ends at rest.
 *
 * This is equivalent to the Android `LinearOutSlowInInterpolator`
 */
val LinearOutSlowInEasing: Easing = CubicBezierEasing(0.0f, 0.0f, 0.2f, 1.0f)

/**
 * Elements exiting a screen use acceleration easing, where they start at rest and
 * end at peak velocity.
 *
 * This is equivalent to the Android `FastOutLinearInInterpolator`
 */
val FastOutLinearInEasing: Easing = CubicBezierEasing(0.4f, 0.0f, 1.0f, 1.0f)

/**
 * It returns fraction unmodified. This is useful as a default value for
 * cases where a [Easing] is required but no actual easing is desired.
 */
val LinearEasing: Easing = Easing { fraction -> fraction }

LinearEasing 是匀速运动,最好理解了,另外 3 个和前面提到的 CSS 里的 ease-in-out、ease-out、ease-in 其实都是对应的

  • ease-in-out => FastOutSlowIn
  • ease-out => LinearOutSlowIn
  • ease-in => FastOutLinearIn

...真是想不明白 Compose 官方这个命名是从哪个角度理解的

相关推荐
Reese_Cool1 小时前
【C语言二级考试】循环结构设计
android·java·c语言·开发语言
平凡シンプル2 小时前
安卓 uniapp跨端开发
android·uni-app
elina80132 小时前
安卓实现导入Excel文件
android·excel
严文文-Chris2 小时前
【设计模式-享元】
android·java·设计模式
趋势大仙2 小时前
SQLiteDatabase insert or replace数据不生效
android·数据库
DS小龙哥2 小时前
QT For Android开发-打开PPT文件
android·qt·powerpoint
试行3 小时前
Android实现自定义下拉列表绑定数据
android·java
Dingdangr8 小时前
Android中的Intent的作用
android
技术无疆8 小时前
快速开发与维护:探索 AndroidAnnotations
android·java·android studio·android-studio·androidx·代码注入
GEEKVIP8 小时前
Android 恢复挑战和解决方案:如何从 Android 设备恢复删除的文件
android·笔记·安全·macos·智能手机·电脑·笔记本电脑