Android 贝塞尔曲线实战之网易云音乐鲸云特效

· jack · Created at · Last by j54733 Replied at · 2156 hits
434 1549005614

作者:哈哈将 -个推 Android 高级开发工程师

前言

APP开发市场已经告别“野蛮生长”时代,人们不再满足于APP外形创新,而将目光转向全方面的用户体验上。在这过程中,动效化作为移动互联网产品的新趋势,如何实现酷炫丝滑的动画效果已然成为开发者们的新课题。实现方式其实很简单。本文将为你剖析理论基础以及具体应用。咱们日常使用的 APP 的时候,出现的很多酷炫动画k可能都是有着贝塞尔曲线的身影。看完这篇文章,你的App也可以达到酷炫吊炸天的动画效果。

先看两个例子:

  1. 手机 QQ 未读消息红点拖拽效果。

  1. 小说阅读 APP 的翻页效果。

简介

在开始实战之前,我们还是先了解下理论基础。动画的终极武器就是贝塞尔曲线了。它是一条光滑的曲线,依据四个位置任意的点坐标绘制而成。1962年,法国工程师皮埃尔·贝塞尔(Pierre Bézier)率先研究出这种矢量绘制曲线的方法并给出了详细的计算公式,应用于汽车的主体设计。因此,人们将按照此种公式绘制的曲线命名为贝塞尔曲线。

核心思想

贝塞尔曲线是计算机图形学中运用得最多的参数曲线之一。它通过控制曲线上的四个点(起始点、终止点以及两个相互分离的中间点)来创造、编辑图形。其中起重要作用的是位于曲线中央的控制线。这条线是虚拟的,中间与贝塞尔曲线交叉,两端是控制端点。移动两端的端点时贝塞尔曲线可以改变曲线的曲率(弯曲的程度);移动中间点(也就是移动虚拟的控制线)时,贝塞尔曲线在起始点和终止点锁定的情况下做均匀移动。注意:贝塞尔曲线上的所有控制点、节点均可编辑。

原理

这里面有个通用公式,这个公式已经有前辈帮我们总结好了。

其中 P0 为起点,Pn 为中点,Pi 为控制点。

一阶贝塞尔曲线

一阶这个比较简单,因为没有在网上找到可以直接输入数学公式的工具,就手工推导了下。

最后的公式为 B(t)=(1-t)Po+tP1,t->[0,1]

二阶贝塞尔曲线

先看动画效果。

关注红线部分,这条线就是我们单位时间内运行的贝塞尔曲线效果图。这条红线实际上是由无数个点组成,随着 t 的不断变化,红线的转换过程非常的顺滑。


最后得到的公式如下:

贝塞尔曲线的绘制,无论多少阶(一阶除外),均需要逐级降阶,最终降至一阶。在 “二阶贝塞尔曲线解析” 这段文字中,从 第一步 到 第二步 的过程就是在降阶。贝塞尔曲线最终的路径是由 一阶基线 上游走的红色小点形成的。

三阶贝塞尔曲线

有了二阶的推导过程,三阶的推导就容易多啦。由于篇幅限制,推导过程这里不再展开,大家有兴趣的话可以自行推导下。


最后的红色曲线是由蓝色一阶曲线获得的,而蓝色一阶曲线又是由绿色一阶曲线获得,最后的绿色一阶曲线则是最外的 P0,P1,P2,P3构成的。动画效果为:

四阶贝塞尔曲线

五阶贝塞尔曲线

结论 我们发现原来贝塞尔曲线上的点与高数中二项式展开一样,对于每个线段上的点经过控制点进行切面操作,而连续的两点之间是无限接近的,所以在绘制的过程中会出现非常丝滑地过度。

贝塞尔曲线在 Android 上的使用

在Android 中使用贝塞尔曲线比较简单,Android 已经内置了贝塞尔曲线的 API,开发者可以直接予以调用。主要有两个 API 。

quadTo

Path path = new Path(); path.moveTo(startX, startY);
path.quadTo(eventX, eventY, endX, endY);
canvas.drawPath(path, paint);

其中 (startX,startY) 为起点,(endX,endY)为终点,而 (eventX,eventY)即为控制点了。

cubicTo

Path path = new Path();
path.moveTo(startX, startY);
path.cubicTo(leftX, leftY, rightX, rightY, endX, endY);
canvas.drawPath(path, paint);

调用此方法即可画出一条三阶贝塞尔曲线。(startX,startY)为起点,(endX,endY)为终点,而(leftX,leftY)与(rightX,rightY) 为两个控制点了。

多阶贝塞尔曲线: Android 系统最高只能画出三阶的贝塞尔曲线,那么想画出更高阶的怎么办呢?其实也很简单。如果真的需要使用高阶的曲线,可以进行人工降阶,降阶到 3 级即可。

实战

终于到实战环节了,该环节共有两个demo。一个是贝塞尔曲线拟圆效果,另一个是仿网易云音乐里面的鲸云效果。

效果实现1:以贝塞尔曲线画圆为例

前文总结了贝塞尔曲线的通用公式。在网上浏览资料的过程中我们发现有这么一个公式:(4/3)tan(π/(2n)),其意义是由n段三阶贝塞尔曲线拟合圆形时,曲线端点到该端点最近的控制点的最佳距离是(4/3)tan(π/(2n))。大家感兴趣的话可以自行推导。推导过程并不复杂,因为贝塞尔曲线有个重要的性质,即曲线方程中t=0.5时的点一定落在圆弧上。只需要把坐标系带入到三阶方程式即可。


最后得知当 t=0.5,根据圆形方程式 X^2+Y^2=R^2 ,得到h=(4/3)(sqrt(2)-1) ≈ 0.552284749831 。有了上述的理论基础,再去画圆就非常的轻松,我们先在草稿纸中得到这么一个模型。

根据上图,这个圆是由 4 段三阶贝塞尔曲线构成的,分别是 P0->P3,P3->P6,P6->P9,P9->P11。三阶贝塞尔曲线的构图是 Android 内置的,我们直接调用API 即可,核心代码如下:

public HeartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context)
}

@Override
protected void init(Context context) {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);

mPath = new Path();
//绘制 12 个点。
mCurPointList = new ArrayList<>();
mCurPointList.add(new PointF(0, dpToPx(-89)));
mCurPointList.add(new PointF(dpToPx(50), dpToPx(-89)));
mCurPointList.add(new PointF(dpToPx(90), dpToPx(-49)));
mCurPointList.add(new PointF(dpToPx(90), 0));
mCurPointList.add(new PointF(dpToPx(90), dpToPx(50)));
mCurPointList.add(new PointF(dpToPx(50), dpToPx(90)));
mCurPointList.add(new PointF(0, dpToPx(90)));
mCurPointList.add(new PointF(dpToPx(-49), dpToPx(90)));
mCurPointList.add(new PointF(dpToPx(-89), dpToPx(50)));
mCurPointList.add(new PointF(dpToPx(-89), 0));
mCurPointList.add(new PointF(dpToPx(-89), dpToPx(-49)));
mCurPointList.add(new PointF(dpToPx(-49), dpToPx(-89)));
}

@Override
protected void onDraw(Canvas canvas) {
drawCoordinate(canvas);

canvas.translate(mWidth / 2, mHeight / 2);

mPath.reset();
for (int i = 0; i < 4; i++) {
if (i == 0) {
mPath.moveTo(mCurPointList.get(i * 3).x, mCurPointList.get(i * 3).y);
} else {
mPath.lineTo(mCurPointList.get(i * 3).x, mCurPointList.get(i * 3).y);
}

int endPointIndex;
if (i == 3) {
endPointIndex = 0;
} else {
endPointIndex = i * 3 + 3;
}

mPath.cubicTo(mCurPointList.get(i * 3 + 1).x, mCurPointList.get(i * 3 + 1).y,
mCurPointList.get(i * 3 + 2).x, mCurPointList.get(i * 3 + 2).y,
mCurPointList.get(endPointIndex).x, mCurPointList.get(endPointIndex).y);
}

canvas.drawPath(mPath, mPaint);

}

}

成果展示

效果实现2:以网易云音乐鲸云效果为例

转换成 GIF,图片可能会有点失真,但并不妨碍具体实现思路。根据这个 GIF,我们发现有三点功能需要去完成:

1.背景色与歌曲图片相搭配,随图片的变化而变化;

2.歌曲中间图片是一张圆形图片并且可以自动旋转;

3.图形外圈有动感 3D环绕效果。

第一点实现比较简单。

第二点也不难。我们可以把一张图片裁剪成圆形,也可以使用 GitHub 上现有的开源库,再加上一个属性动画代码。

public void handleRotate(){
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(ivShowPic, "rotation", 0f, 360f);
objectAnimator.setDuration(20 * 1000);

objectAnimator.setRepeatMode(ValueAnimator.RESTART);

objectAnimator.setInterpolator(new LinearInterpolator());

objectAnimator.setRepeatCount(-1);

objectAnimator.start();
}
看下动感3D环绕效果即转圈圈。

第三点 如何做跳动旋律特效?!!先不考虑前面两点需求,我们逐步分析下跳动旋律特效。动态图在文章开始部分已经看到了,我们建议先从静态图着手。

我们猜测可能的实现思路(不代表官方实现思路):该动效外层一圈有 4 条线段在不规则地跳动,每条线的背后是一个圆,每个圆由 4 条贝塞尔曲线组成。

第一步 先画个圆。我们只需要把画笔的属性设置成如下属性,即可画出一个空心圆。

mPaint.setStyle(Paint.Style.STROKE);

为达到更顺滑的环绕效果,我们需要不断调试各条贝塞尔曲线的对应的两个控制点。具体参数可根据业务场景来定。文中demo仅作参考。

第二步 上文我们分析过这个圆其实是由贝塞尔曲线组成的拟圆。在Android系统中是以每秒60帧为满帧的,那么只要将1秒÷60帧,就能得出16毫秒(ms)/帧是满帧的界限,即每帧快于16ms则为流畅。所以我们这边的刷新频率设定为每 80 毫秒刷新一次:

public void onPlay(View view){
scheduledExecutorService = Executors.newScheduledThreadPool(1);
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
diyBezierView.post(new Runnable() {
@Override
public void run() {
diyBezierView.play();
}
});
}
},0,80, TimeUnit.MILLISECONDS);
得到的效果如下:


第三步 雏形已经完成,后续我们的做法是再往上添加 2 个圆,看下 3 个圆是怎么样的效果。


第四步 最后一步当然是把前面两点合在一起啦,合一起后就可以看下最终效果了:

实际效果与预期效果会存在一定的差异,主要原因在于函数坐标以及画笔的一些属性问题。以上就是具体的实现思路,供大家参考。

总结

酷炫动画的实现过程并没有我们想象的那么复杂。其实,很多复杂特效都是由不同的动画组合而成的,而丝滑般的动态效果则离不开贝塞尔曲线的应用。希望这篇文章可以帮助到想要做出酷炫丝滑的动态效果的你。

如何获取实战 Demo

https://github.com/LiuLei0571/jingyun_breizerstar(方便的话给程序员小哥哥一个 哦)

或者也可以关注【个推技术学院】微信公众号

(微信号:getuitech)

回复关键词“曲线”

即可获取仿鲸云特效动画demo!

共收到 26 条回复
1Floor Deleted
2Floor Deleted
3Floor Deleted
4Floor Deleted
5Floor Deleted
6Floor Deleted
7Floor Deleted
8Floor Deleted
9Floor Deleted
10Floor Deleted
11Floor Deleted
12Floor Deleted
13Floor Deleted
14Floor Deleted
15Floor Deleted
16Floor Deleted
96
j54733 · #17 ·

又騷又緊的單親媽媽~ 賴:j54733
穎兒是我在嬰兒用品店裡遇見的,當時我陪朋友一起幫他的孩子選購奶瓶,穎兒原來和我的朋友認識,透過聊天發現她生活上遇到困難也挺可憐的,當下我們就交換了聯絡方式,也就開始慢慢的了解彼此。這段時間下來和穎兒聊的挺好,個性上也很和的來,她是個很善良的女孩,更善解人意,但也因此發現了她有出來下海兼職,最主要的原因就是她是個單親媽媽,年紀小的時候被騙懷孕了,後來男生跑了,留下她自己撫養孩子,穎兒想要給孩子一個更好的生活環境,所以下海兼職。我跟她也約過很多次,配合的很好,她的服務態度超讚,很主動性慾很強,重點是外貌也很漂亮,身材保持的很好,一直都有健身運動的習慣。平時在家帶完孩子做完家事,會趁著休息的時間做做有氧或是重訓。穎兒的身體是很敏感也容易高潮,雖然生過小孩,但是穴穴還是很緊,很粉。奶泡 吸奶 舔鮑 後背式 觀音坐蓮 泡澡 LG 還有更多想要嘗試的姿勢 可配合情趣用品【看配合fu】這是我發現的小秘密。也可以跟她約看電影或是外出玩,如果喜歡的話可以加賴聊一聊,因為小弟一直國外長期出差,疫情這段期間無法幫助她,在盡我最大努力的之下在這裡介紹她,希望你們能多幫助她,小弟在這裡謝謝你們了。她的賴:j54733 身材密碼:164 D 24歲 阿傑介紹

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up