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

Stella981
• 阅读 768

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

前言

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

先看两个例子:

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

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

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

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

简介

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

核心思想

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

原理

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

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

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

一阶贝塞尔曲线

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

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

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

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

二阶贝塞尔曲线

先看动画效果。 Android 贝塞尔曲线实战之网易云音乐鲸云特效

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

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

最后得到的公式如下:

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

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

三阶贝塞尔曲线

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

Android 贝塞尔曲线实战之网易云音乐鲸云特效 最后的红色曲线是由蓝色一阶曲线获得的,而蓝色一阶曲线又是由绿色一阶曲线获得,最后的绿色一阶曲线则是最外的 P0,P1,P2,P3构成的。动画效果为:

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

四阶贝塞尔曲线

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

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

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

贝塞尔曲线在 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时的点一定落在圆弧上。只需要把坐标系带入到三阶方程式即可。

Android 贝塞尔曲线实战之网易云音乐鲸云特效 最后得知当 t=0.5,根据圆形方程式 X^2+Y^2=R^2 ,得到h=(4/3)(sqrt(2)-1) ≈ 0.552284749831 。有了上述的理论基础,再去画圆就非常的轻松,我们先在草稿纸中得到这么一个模型。 Android 贝塞尔曲线实战之网易云音乐鲸云特效

根据上图,这个圆是由 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](https://my.oschina.net/u/1162528)
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](https://my.oschina.net/u/1162528)
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);

}

}

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

效果实现2:以网易云音乐鲸云效果为例 Android 贝塞尔曲线实战之网易云音乐鲸云特效

转换成 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环绕效果即转圈圈。

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

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

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

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

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

mPaint.setStyle(Paint.Style.STROKE);

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

为达到更顺滑的环绕效果,我们需要不断调试各条贝塞尔曲线的对应的两个控制点。具体参数可根据业务场景来定。文中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); 得到的效果如下:

Android 贝塞尔曲线实战之网易云音乐鲸云特效 第三步 雏形已经完成,后续我们的做法是再往上添加 2 个圆,看下 3 个圆是怎么样的效果。

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

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

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

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

总结

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

如何获取实战 Demo

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

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

(微信号:getuitech)

回复关键词“曲线”

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

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Flutter 之贝塞尔曲线(一)
贝塞尔曲线简介!(https://oscimg.oschina.net/oscnet/863784996212c918a1feef7a916bce28f31.png"bezier1.png")bezier1.png由上图可以看出:A,C依据控制点B不断的取点使得AD:ABBE:BCDF:DE,构成一个二阶贝塞尔曲线。AD:
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
Unity横屏
Android下发现Unity里面的Player设置,并不能完全有效,比如打开了自动旋转,启动的时候还是会横屏,修改XML添加以下代码<applicationandroid:icon"@drawable/ic\_launcher"                    android:label"@string/app\_name"
Stella981 Stella981
3年前
Flutter贝塞尔曲线之水波纹与球形进度(二)
续上篇,再用贝塞尔曲线绘制一个循环水波纹,一个水波纹进度球,先看效果,以下效果的实现用的都是二阶贝塞尔曲线。效果图!(https://oscimg.oschina.net/oscnet/db689ecfebdfa10dbc59b588184913f7c6f.gif"bezierShow4.gif")bezierShow4.
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这