博主声明:

转载请在开头附加本文链接及作者信息,并标记为转载。本文由博主 威威喵 原创,请多支持与指教。

本文首发于此   博主:威威喵  |  博客主页:https://blog.csdn.net/smile_running

博主这几天一直在搞贝塞尔曲线(Bezier)动画的研究,虽然我的数学不太好,但是也勉勉强强能够看懂懂贝塞尔曲线的公式,套用还是很简单的。前几次搞了几个贝塞尔曲线动画效果,感觉那个效果还是非常赞的,今天兴致又来了,于是去搜索了一下 Android 相关的贝塞尔曲线的动画实例,偶然看到一个 Android 充电进度的贝塞尔曲线动画,它的效果图如下:

看到这个效果呢,我首先是想到用三阶贝塞尔曲线公式来做,于是就屁颠屁颠的开始了,套了三阶贝塞尔曲线的公式,发现效果没出来,卧槽。害我白高兴一场,以为我的数学还是可以的,结果。。。

我最先的想法是通过点位去计算波形路径,不过最后放弃了。哈哈,喜出望外,结果我发现了一个更简单的做法,用 Path 类下面的一个三阶贝塞尔曲线的封装方法,很简单就实现了波浪的效果,这是我写这个效果时所收获到的意外惊喜,之前还没字母使用过,接下来我们进行分析这个效果的实现,然后再讲解一下 Path 类三阶贝塞尔的简单用法。

多的就不扯淡了,我们直接开始吧。国际惯例,先来看看最终的实现效果图:

这个充电进度的动画效果还行吧,上面我搜索到的是一张静态图,我就是依照这那张图的样式做的,可能颜色又一点点缺陷,这个自己再美化美化就好啦。

来吧,拿到这个效果图,首先就是分析一波。来看一下草图

看上面那张图,首先我们要把圆绘制到中心点吧,这没什么问题。因为三阶贝塞尔曲线需要 2 个控制点,从图中我们知道 p1 和 p2 就是那条曲线的控制点, 而且上图 p1 p2 p3 p4 四个点获取坐标都很容易。

        //内部pIn0 = new PointF(rippleX - mDefCircleRadius, rippleY);pIn1 = new PointF(rippleX - mRandom.nextInt((int) mDefCircleRadius), rippleY - mRandom.nextInt((int) (mDefCircleRadius / 4)));pIn2 = new PointF(rippleX + mRandom.nextInt((int) mDefCircleRadius), rippleY + mRandom.nextInt((int) (mDefCircleRadius / 4)));pIn3 = new PointF(rippleX + mDefCircleRadius, rippleY);

因为海浪波纹有两条曲线组成,这两条曲线是交错的,所以我们需要再来 4 个点

        // 外部pExt0 = new PointF(rippleX - mDefCircleRadius, rippleY);pExt1 = new PointF(rippleX - mRandom.nextInt((int) mDefCircleRadius), rippleY + mRandom.nextInt((int) (mDefCircleRadius / 3)));pExt2 = new PointF(rippleX + mRandom.nextInt((int) mDefCircleRadius), rippleY + mRandom.nextInt((int) (mDefCircleRadius / 3)));pExt3 = new PointF(rippleX + mDefCircleRadius, rippleY);

得到曲线的点之后呢,我们就可以开始用 Path 类的一个方法去形成曲线的路径了,因为波浪是有颜色的,所以需要把 Path 给封闭起来,形成密闭的效果。接着,再来看一张草图

用 Path 类制作一条曲线,并且我们要把 p0 ~ p5 这几个点给封闭起来,形成海浪的效果。想法是不错,但是你会发现,这个形成的区域已经超出了圆的范围了吧,那样子就非常丑,犹如这个样子:

圆圈外面多出了两个蓝色部分区域,丑的不行啊。 像这个样子的情况,我最先想到的是 canvas 有没有画剪切区域的,后来找了一下,好像没找到。陷入深思,后来灵机一动,想到我上一次实现的一种效果,是画一个圆,从内到外扩散的,感兴趣的可以点击链接,去看看我的文章:Android 视差动画 — 雅虎新闻内容揭示效果

    这个圆效果呢,就是从小变到大,逐渐的把内容呈现出来。这就给我一个很好的启示,我可以绘制一个这样的圆,把外面蓝色部分遮住不久好了嘛,也就相当于除了绿色包含的圆以外全部给遮住,这样显示的效果只能看到这个绿色的圆了,我们的目的也就达到了。这个就需要对画笔的宽度进行计算,代码如下:

    private void drawMasked(Canvas canvas) {//绘制一个遮罩层,屏蔽 Path Close 以外的区域mMaskPaint.setStrokeWidth(mDiagonal + mDefCircleRadius * 2 - mPaintSize * 1.5f);canvas.drawCircle(mCircleX, mCircleY, mDiagonal, mMaskPaint);}

这样就把露出来的蓝色区域给遮挡住了,接下来还有一个难点,就是如何根据进度值把海浪也给升高,总不能在固定位置浪啊浪吧。这就要考虑一个问题,我们需要根据圆的直径和进度值的一个比例关系,计算出当前海平面的高度,通过不断的增加 progress(进度),海平面会随着进度升高,而且这个期间波浪一直在流动的。这部分关键代码如下:

        // 直径与进度的比例rippleScale = 2 * mDefCircleRadius / 100;// 绘制海浪的波纹效果,分内部和外部两条private void drawExternalRipple(Canvas canvas) {// 计算进度的 x , y 位置y = mCircleY - mDefCircleRadius + (100 - mProgress) * rippleScale;x = caculateX(y);float rippleY = y;float rippleX = mCircleX;//内部pIn0 = new PointF(rippleX - mDefCircleRadius, rippleY);pIn1 = new PointF(rippleX - mRandom.nextInt((int) mDefCircleRadius), rippleY - mRandom.nextInt((int) (mDefCircleRadius / 4)));pIn2 = new PointF(rippleX + mRandom.nextInt((int) mDefCircleRadius), rippleY + mRandom.nextInt((int) (mDefCircleRadius / 4)));pIn3 = new PointF(rippleX + mDefCircleRadius, rippleY);Path inPath = new Path();inPath.moveTo(pIn0.x, pIn0.y);inPath.cubicTo(pIn1.x, pIn1.y, pIn2.x, pIn2.y, pIn3.x, pIn3.y);inPath.lineTo(mCircleX + mDefCircleRadius, mCircleY + mDefCircleRadius);inPath.lineTo(mCircleX - mDefCircleRadius, mCircleY + mDefCircleRadius);inPath.close();canvas.drawPath(inPath, mInnerPaint);// 外部pExt0 = new PointF(rippleX - mDefCircleRadius, rippleY);pExt1 = new PointF(rippleX - mRandom.nextInt((int) mDefCircleRadius), rippleY + mRandom.nextInt((int) (mDefCircleRadius / 3)));pExt2 = new PointF(rippleX + mRandom.nextInt((int) mDefCircleRadius), rippleY + mRandom.nextInt((int) (mDefCircleRadius / 3)));pExt3 = new PointF(rippleX + mDefCircleRadius, rippleY);Path extPath = new Path();extPath.moveTo(pExt0.x, pExt0.y);extPath.cubicTo(pExt1.x, pExt1.y, pExt2.x, pExt2.y, pExt3.x, pExt3.y);extPath.lineTo(mCircleX + mDefCircleRadius, mCircleY + mDefCircleRadius);extPath.lineTo(mCircleX - mDefCircleRadius, mCircleY + mDefCircleRadius);extPath.close();canvas.drawPath(extPath, mExternalPaint);}

上面代码是计算进度条和圆的直径的比例,通过这个比例,我们可以拿到 path 中波浪逐渐上升的 y 坐标,通过不断的绘制 path 然后形成波浪的动画效果,直到进度条为 100 时,我们就进行判断处理

    public void setProgress(int progress) {this.mProgress = progress;this.mArcProgress = mProgress * 3.6f;if (mProgress <= 100) {isFinished = false;} else {isFinished = true;}invalidate();}

如果进度达到 100,我们就开始绘制完成时候的动画,代码如下

    private void drawFinished(Canvas canvas) {canvas.drawCircle(mCircleX, mCircleY, mDefCircleRadius, mArcPaint);canvas.drawCircle(mCircleX, mCircleY, mDefCircleRadius, mInnerPaint);canvas.drawText("充电完成", mCircleX - mTextPaint.getTextSize() * 2f, mCircleY + mTextPaint.getTextSize() / 2, mTextPaint);}

只有这样,当结束是才会显示不同的效果,否则不做处理的话,就是空空如也啦。

那么至此,我们对这个效果的分析也就完成了,并且手动进实现了一下,感觉收获了不少,哈哈。最后呢,给出本效果的完整代码,如下:

package nd.no.xww.qqmessagedragview;import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;import java.util.Random;/*** @author xww* @desciption :* @date 2019/8/6* @time 12:11* 博主:威威喵* 博客:https://blog.csdn.net/smile_Running*/
public class ChargeBezierView extends View {private Paint mExternalPaint;private Paint mInnerPaint;private Paint mArcPaint;private Paint mCirclePaint;private Paint mTextPaint;private Paint mMaskPaint;private int mWidth;private int mHeight;// 充电进度值百分制private int mProgress;private float mArcProgress;private float mPaintSize;//水波纹于进度条的高度比private float rippleScale;//用于画进度private RectF mRect;private Random mRandom;private float mCircleX;private float mCircleY;private float mDefCircleRadius;// 对角线的长度private float mDiagonal;private boolean isFinished = false;//水波纹高度坐标private float x;private float y;private void init() {mExternalPaint = getPaint(Color.parseColor("#554F94CD"));mInnerPaint = getPaint(Color.parseColor("#66B8FF"));mArcPaint = getPaint(Color.parseColor("#7FFF00"));mArcPaint.setStyle(Paint.Style.STROKE);//空心mCirclePaint = getPaint(Color.parseColor("#F8F8FF"));mCirclePaint.setStyle(Paint.Style.STROKE);//空心mTextPaint = getPaint(Color.parseColor("#FF00ff"));mMaskPaint = getPaint(Color.parseColor("#FFFFFF"));mMaskPaint.setStyle(Paint.Style.STROKE);mRandom = new Random();mPaintSize = mTextPaint.getTextSize();}private Paint getPaint(int color) {Paint paint = new Paint();paint.setDither(true);paint.setAntiAlias(true);paint.setStrokeWidth(18f);paint.setTextSize(60f);paint.setColor(color);return paint;}public ChargeBezierView(Context context) {this(context, null);}public ChargeBezierView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public ChargeBezierView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}@SuppressLint("DrawAllocation")@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);mWidth = MeasureSpec.getSize(widthMeasureSpec);mHeight = MeasureSpec.getSize(heightMeasureSpec);mCircleX = mWidth / 2;mCircleY = mHeight / 2;mDefCircleRadius = mWidth / 4;mRect = new RectF(mCircleX - mDefCircleRadius, mCircleY - mDefCircleRadius,mCircleX + mDefCircleRadius, mCircleY + mDefCircleRadius);mDiagonal = (float) Math.sqrt(Math.pow(mCircleX, 2) + Math.pow(mCircleY, 2));rippleScale = 2 * mDefCircleRadius / 100;}@Overrideprotected void onDraw(Canvas canvas) {if (isFinished) {drawMasked(canvas);drawFinished(canvas);} else {drawExternalRipple(canvas);drawMasked(canvas);drawProgressText(canvas);drawCircle(canvas);drawProgress(canvas);}}// 绘制电量圆形轨道private void drawCircle(Canvas canvas) {canvas.drawCircle(mCircleX, mCircleY, mDefCircleRadius, mCirclePaint);}private void drawProgress(Canvas canvas) {// -90 表示从上半轴 x=0 开始canvas.drawArc(mRect, -90, mArcProgress, false, mArcPaint);}private void drawProgressText(Canvas canvas) {canvas.drawText(mProgress + "%", mCircleX - mPaintSize, mCircleY + mTextPaint.getTextSize() / 2, mTextPaint);}private void drawMasked(Canvas canvas) {//绘制一个遮罩层,屏蔽 Path Close 以外的区域mMaskPaint.setStrokeWidth(mDiagonal + mDefCircleRadius * 2 - mPaintSize * 1.5f);canvas.drawCircle(mCircleX, mCircleY, mDiagonal, mMaskPaint);}private void drawFinished(Canvas canvas) {canvas.drawCircle(mCircleX, mCircleY, mDefCircleRadius, mArcPaint);canvas.drawCircle(mCircleX, mCircleY, mDefCircleRadius, mInnerPaint);canvas.drawText("充电完成", mCircleX - mTextPaint.getTextSize() * 2f, mCircleY + mTextPaint.getTextSize() / 2, mTextPaint);}private PointF pExt0;private PointF pExt1;private PointF pExt2;private PointF pExt3;private PointF pIn0;private PointF pIn1;private PointF pIn2;private PointF pIn3;ValueAnimator externalAnimator;// 绘制海浪的波纹效果,分内部和外部两条private void drawExternalRipple(Canvas canvas) {// 计算进度的 x , y 位置y = mCircleY - mDefCircleRadius + (100 - mProgress) * rippleScale;x = caculateX(y);float rippleY = y;float rippleX = mCircleX;//内部pIn0 = new PointF(rippleX - mDefCircleRadius, rippleY);pIn1 = new PointF(rippleX - mRandom.nextInt((int) mDefCircleRadius), rippleY - mRandom.nextInt((int) (mDefCircleRadius / 4)));pIn2 = new PointF(rippleX + mRandom.nextInt((int) mDefCircleRadius), rippleY + mRandom.nextInt((int) (mDefCircleRadius / 4)));pIn3 = new PointF(rippleX + mDefCircleRadius, rippleY);Path inPath = new Path();inPath.moveTo(pIn0.x, pIn0.y);inPath.cubicTo(pIn1.x, pIn1.y, pIn2.x, pIn2.y, pIn3.x, pIn3.y);inPath.lineTo(mCircleX + mDefCircleRadius, mCircleY + mDefCircleRadius);inPath.lineTo(mCircleX - mDefCircleRadius, mCircleY + mDefCircleRadius);inPath.close();canvas.drawPath(inPath, mInnerPaint);// 外部pExt0 = new PointF(rippleX - mDefCircleRadius, rippleY);pExt1 = new PointF(rippleX - mRandom.nextInt((int) mDefCircleRadius), rippleY + mRandom.nextInt((int) (mDefCircleRadius / 3)));pExt2 = new PointF(rippleX + mRandom.nextInt((int) mDefCircleRadius), rippleY + mRandom.nextInt((int) (mDefCircleRadius / 3)));pExt3 = new PointF(rippleX + mDefCircleRadius, rippleY);Path extPath = new Path();extPath.moveTo(pExt0.x, pExt0.y);extPath.cubicTo(pExt1.x, pExt1.y, pExt2.x, pExt2.y, pExt3.x, pExt3.y);extPath.lineTo(mCircleX + mDefCircleRadius, mCircleY + mDefCircleRadius);extPath.lineTo(mCircleX - mDefCircleRadius, mCircleY + mDefCircleRadius);extPath.close();canvas.drawPath(extPath, mExternalPaint);}public void setProgress(int progress) {this.mProgress = progress;this.mArcProgress = mProgress * 3.6f;if (mProgress <= 100) {isFinished = false;} else {isFinished = true;}invalidate();}// 圆的方程式 a2 = b2 + c2private float caculateX(float y) {x = (float) Math.sqrt(Math.pow(mDefCircleRadius, 2) - y * y);return x;}
}

还有一个是进行进度值设置的,这个很简单,在 MainActivity 里面开一个子线程,然后设置一下进度值就可以了

        chargeView = findViewById(R.id.chargeView);new Thread(new Runnable() {@Overridepublic void run() {while (true) {progress++;if (progress > 100) {progress = 101;}try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}runOnUiThread(new Runnable() {@Overridepublic void run() {chargeView.setProgress(progress);}});}}}).start();

使用起来就是这么简单,不过还有一些与贝塞尔曲线相关的知识没有介绍,感兴趣的话,可以去看我之前写的几篇文章,里面有关于贝塞尔的介绍,还有一些比较炫酷的 Android 动画效果哦。

贝塞尔曲线(Bezier)之水波纹的手机充电动画效果(一)相关推荐

  1. 贝塞尔曲线(Bezier)之水波纹的手机充电动画效果(二)

    博主声明: 转载请在开头附加本文链接及作者信息,并标记为转载.本文由博主 威威喵 原创,请多支持与指教. 本文首发于此   博主:威威喵  |  博客主页:https://blog.csdn.net/ ...

  2. 贝塞尔曲线(Bezier)之 QQ 消息拖拽动画效果

    博主声明: 转载请在开头附加本文链接及作者信息,并标记为转载.本文由博主 威威喵 原创,请多支持与指教. 本文首发于此   博主:威威喵  |  博客主页:https://blog.csdn.net/ ...

  3. 贝塞尔曲线(Bezier)之花束直播爱心点赞动画效果

    博主声明: 转载请在开头附加本文链接及作者信息,并标记为转载.本文由博主 威威喵 原创,请多支持与指教. 本文首发于此   博主:威威喵  |  博客主页:https://blog.csdn.net/ ...

  4. html贝塞尔曲线爱心,史上最全的贝塞尔曲线(Bezier)全解(三):贝塞尔曲线实现满屏爱心...

    这一篇文章会完整的介绍如何通过贝塞尔曲线实现爱心点赞的效果,如果实在看不懂,可以看第一篇贝塞尔曲线的简介,还有第二篇安卓中的简单使用; 好了,终于到了放大招的时候了,真实憋了很久了 这里写图片描述 先 ...

  5. 贝塞尔曲线(Bezier Curves)

    贝塞尔曲线 空间贝塞尔曲线(Spatial B′ ezier Curves) 当贝塞尔曲线的控制点为三维坐标时,即可得到空间贝塞尔曲线. 空间空间贝塞尔曲线任然满足性质: 端点插值性质: 端点切线定理 ...

  6. 史上最全的贝塞尔曲线(Bezier)全解(一):初识贝塞尔曲线

      作为一个有只志向的码农,除了知道一些基本的知识够自己努力搬砖以外,还应该get一些更炫酷的技能,用更优雅的姿势进行搬砖;想要实现一些十分炫酷的效果,贝塞尔曲线就必须进行一些研究了; 最近一段时间, ...

  7. android自定义水波纹,Android自定义View——实现水波纹效果类似剩余流量球(示例代码)...

    最近突然手痒就想搞个贝塞尔曲线做个水波纹效果玩玩,终于功夫不负有心人最后实现了想要的效果,一起来看下吧: 效果图镇楼 一:先一步一步来分解一下实现的过程 需要绘制一个正弦曲线(sin)或者余弦曲线(c ...

  8. android 立体 流量球,Android自定义View——实现水波纹效果类似剩余流量球

    Android自定义View--实现水波纹效果类似剩余流量球 三个点   pre   ber   block   span   初始化   move   理解最近突然手痒就想搞个贝塞尔曲线做个水波纹效 ...

  9. Android自定义View——实现水波纹效果类似剩余流量球

    最近突然手痒就想搞个贝塞尔曲线做个水波纹效果玩玩,终于功夫不负有心人最后实现了想要的效果,一起来看下吧: 效果图镇楼 一:先一步一步来分解一下实现的过程 需要绘制一个正弦曲线(sin)或者余弦曲线(c ...

最新文章

  1. PAT(甲级)2019年春季考试 7-3 Telefraud Detection
  2. Echarts渲染选择SVG /canvas
  3. C++之编码问题(Unicode,ASCII,本地默认)
  4. Dbvis数据库连接工具将查询出数据转化为sql插入语句方法
  5. Lesson 16.3 卷积操作
  6. 漫画:什么是MapReduce
  7. java mongodb gridfs_查询MongoDB GridFS元数据(Java)
  8. Linux less命令:查看文件内容
  9. go + influxdb + grafana 日志监控系统
  10. ros 安装c++编译的可执行文件
  11. 课堂作业-1成绩汇总
  12. Mongodb 与 MySQL对比
  13. 如何在C / Objective-C中跨多行拆分字符串文字?
  14. Mac电脑优化工具箱MacCleaner PRO
  15. 控件的颜色设置(本景色,文本色,文本背景色)
  16. opencv3/C++ SURF特征检测
  17. 万字整理,肝翻Linux内存管理所有知识点
  18. 最大熵,三硬币模型的R语言代码
  19. Y-Combinator
  20. VARCHART XGantt系列教程:如何利用颜色来丰富甘特图智能

热门文章

  1. 养育女孩——培养优雅、独立、内心强大的女孩
  2. 大一上学期第十六周学习生活总结
  3. 用Python做非参数检验
  4. 什么是期货(什么是期货什么是期权)
  5. “跨境理财通”启动,大湾区居民迎来投资“新高速”
  6. Centos7 合盖不休眠
  7. A Convolutional Neural Network Approach for Half-Pel Interpolation in Video Coding一视频编码中半像素插值的卷积神经网络
  8. optifine怎么用java打开_OptiFine下载及安装方法
  9. Unable to initialize GTK: could not open display
  10. 外贸生产仓储条码管理软件丨汇信外贸软件