博主声明:

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

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

对于博主上一次写的一个水波纹的充电动画效果,我个人还并不是特别满意,也就是写着玩的。这次想认真的写一个,能够用到实际的项目中去,并且可以很方便的使用,像 TextView 一样简单,定制性高一些。本来呢,想着在充电时,添加一个泡泡一直往上冒的效果,无奈啊,写了好一会儿,效果不太理想。我考虑一下,这次就不加入冒泡的效果了,下次在换另一种方式,实现一下冒泡的效果。

对于这次的效果,会比上一篇水波纹充电动画好那么一丢丢吧,看看上篇的效果,点击:贝塞尔曲线(Bezier)之水波纹的手机充电动画效果(一)

上一次没有理解那个 path 绘制三次贝塞尔曲线的方法,动画不太行。这次,我发现用二次贝塞尔也可以绘制出三角函数的图像来,是我学的太好了,见识短浅。来吧,先看几波效果图再说:

这个是有电池盖子的,就是上面突了一个方形,然后是水波纹的效果:

你可以选择不要上面突出来的那部分

你也可以选择关闭水波纹的动画效果,也可以关闭电量进度的百分比值文本,我想没人愿意这么干吧,不就是为了炫酷嘛

你可以任意的切换电池外圈的颜色,充电进度的颜色,上面突出方形的颜色,以及文本的颜色和大小等

好了,具体的功能就这些了,下面我们来看看关键实现的代码,首先肯定是自定义属性,你看这个属性,就知道它有什么样的功能了,代码如下:

    <declare-styleable name="ChargeBubbleView">//电池四周边框的宽度<attr name="charge_battery_width" format="dimension" />//电池边框的颜色<attr name="charge_battery_color" format="color" />//电池文本的颜色<attr name="charge_battery_text_color" format="color" />//电池文本的大小<attr name="charge_battery_text_size" format="dimension" />//当前电量的颜色<attr name="charge_battery_current_color" format="color" />//电池是否需要帽子<attr name="charge_battery_cap" format="boolean" />//是否开启电量文本<attr name="charge_battery_has_text" format="boolean" />//电池盖的填充颜色<attr name="charge_battery_cap_fill_color" format="color" />//是否开启水波纹效果<attr name="charge_battery_water_bezier" format="boolean" /></declare-styleable>

1、电池的绘制,这部分需要用到 path 来绘制,很简单,就是计算各个点的坐标即可。

    private Path getBatteryStroke(boolean hasCap, float width, float height) {Path path = new Path();if (hasCap) {float capWidth = width / 3f;float capHeight = capWidth / 2f;path.moveTo(0, capHeight);path.lineTo(0, height);path.lineTo(width, height);path.lineTo(width, capHeight);path.lineTo(capWidth * 2, capHeight);path.lineTo(capWidth * 2, 0);path.lineTo(capWidth, 0);path.lineTo(capWidth, capHeight);path.close();} else {path.moveTo(0, 0);path.lineTo(0, height);path.lineTo(width, height);path.lineTo(width, 0);path.close();}return path;}

2、水波纹的动画效果,这部分比较难。首先,我们需要这样考虑,来看这张草图:

从这张图就很形成的表明了我的意思,我们在绘制贝塞尔曲线的时候,其实是绘制了两条,就如下面的代码,我用了一个 Path[] 数组来保存两条贝塞尔曲线绘制出来的封闭区域,形成一种海水的效果。让曲线上的点 x 的坐标一直增加,它将会移动到屏幕的右侧,那么后面那条隐藏的贝塞尔曲线也会随之显现,一直不停的循环,就达到了水波纹动画的效果了。

    private Path[] getRealBatteryPath() {Path[] path = new Path[2];float realBatteryHeight = mCurrentBattery * mBatteryPercent;if (hasWaterBezier) {path[0] = new Path();path[1] = new Path();float realY = mHeight - realBatteryHeight;float p0x = 0;float p0y = realY;float pc1x = mWidth / 4;float pc1y = realY - 50;float p1x = mWidth / 2;float p1y = realY;float pc2x = p1x + pc1x;float pc2y = realY + 50;float p2x = mWidth;float p2y = realY;path[0].moveTo(p0x + diff, p0y);path[0].quadTo(pc1x + diff, pc1y, p1x + diff, p1y);path[0].quadTo(pc2x + diff, pc2y, p2x + diff, p2y);path[0].lineTo(mWidth + diff, mHeight);path[0].lineTo(0 + diff, mHeight);path[0].close();float p0x2 = 0;float p0y2 = realY;float pc1x2 = mWidth / 4;float pc1y2 = realY - 50;float p1x2 = mWidth / 2;float p1y2 = realY;float pc2x2 = p1x + pc1x;float pc2y2 = realY + 50;float p2x2 = mWidth;float p2y2 = realY;path[1].moveTo(p0x2 - mWidth + diff, p0y2);path[1].quadTo(pc1x2 - mWidth + diff, pc1y2, p1x2 - mWidth + diff, p1y2);path[1].quadTo(pc2x2 - mWidth + diff, pc2y2, p2x2 - mWidth + diff, p2y2);path[1].lineTo(diff, mHeight);path[1].lineTo(-mWidth + diff, mHeight);path[1].close();if (mWaterAnimator == null) {mWaterAnimator = ObjectAnimator.ofFloat(0, mWidth);mWaterAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {diff = (float) animation.getAnimatedValue();}});mWaterAnimator.setDuration(2000);mWaterAnimator.setRepeatCount(-1);mWaterAnimator.start();}} else {path[0] = new Path();path[0].moveTo(0, mHeight - realBatteryHeight);path[0].lineTo(0, mHeight);path[0].lineTo(mWidth, mHeight);path[0].lineTo(mWidth, mHeight - realBatteryHeight);path[0].close();}return path;}

3、充电完成了,当然需要绘制一个提示文本,这个比较简单吧,就是 canvas 的基本操作,主要还是计算坐标位置,代码如下

    private void drawBatteryText(Canvas canvas) {if (hasText) {String batteryText = mCurrentBattery >= 100 ? "充电完成" : (String.valueOf(mCurrentBattery) + "%");Rect bounnds = new Rect();mPaints[3].getTextBounds(batteryText, 0, batteryText.length(), bounnds);if (mCurrentBattery >= 100) {canvas.drawText(batteryText, mWidth / 2 - bounnds.width() / 2, mHeight / 2, mPaints[3]);} else {canvas.drawText(batteryText, mWidth / 2 - bounnds.width() / 2, mHeight - mCurrentBattery * mBatteryPercent - mTextHeight, mPaints[3]);}}}

4、代码中还有一些简单的我就不说了,都是一些属性的赋值操作,自己看看代码吧。下面是本效果的全部代码

package nd.no.xww.qqmessagedragview;import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;import java.util.ArrayList;
import java.util.List;
import java.util.Random;/*** @author xww* @desciption : 气泡曲线上升与水波纹综合的充电效果(备注:气泡效果还没实现,等下次再搞)* @date 2019/8/9* @time 10:40* 博主:威威喵* 博客:https://blog.csdn.net/smile_Running*/
public class ChargeBubbleView extends View {private float density;private float mWidth;private float mHeight;private Paint mPaints[] = new Paint[5];private Path mBatteryStrokePath;private Path[] mRealBatteryPath;private float mBatteryWidth;private int mBatteryStrokeColor;private int mCurrentBatteryColor;private int mCapFillColor;private boolean hasBatteryCap;private int mBatteryTextColor;private float mBatteryTextSize;private boolean hasText;private boolean hasWaterBezier;private float mTextHeight;//文本高度private int mCurrentBattery;// 电量private float mBatteryPercent;// 百分比private Random mRandom;private ValueAnimator mWaterAnimator = null;private float diff = 0;private List<PointF> mBubblePonits;private void init() {//绘制当前电量mPaints[0] = getPaint();mPaints[0].setColor(mCurrentBatteryColor);mPaints[0].setStyle(Paint.Style.FILL);//绘制电池mPaints[1] = getPaint();mPaints[1].setStyle(Paint.Style.STROKE);mPaints[1].setStrokeWidth(mBatteryWidth);mPaints[1].setColor(mBatteryStrokeColor);//绘制泡泡mPaints[2] = getPaint();mPaints[2].setStyle(Paint.Style.STROKE);mPaints[2].setStrokeWidth(8f);//绘制电量的文本mPaints[3] = getPaint();mPaints[3].setTextSize(mBatteryTextSize);mPaints[3].setColor(mBatteryTextColor);Paint.FontMetrics metrics = mPaints[3].getFontMetrics();mPaints[4] = getPaint();mPaints[4].setColor(mCapFillColor);mTextHeight = (metrics.bottom - metrics.top) / 2 - metrics.descent / 2;mRandom = new Random();mBubblePonits = new ArrayList<>();}private Paint getPaint() {Paint paint = new Paint();paint.setDither(true);paint.setAntiAlias(true);return paint;}public ChargeBubbleView(Context context) {this(context, null);}public ChargeBubbleView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public ChargeBubbleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initAttrs(context, attrs);init();}private void initAttrs(Context context, AttributeSet attrs) {TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ChargeBubbleView);if (array != null) {mBatteryWidth = array.getDimension(R.styleable.ChargeBubbleView_charge_battery_width, 30f);mBatteryStrokeColor = array.getColor(R.styleable.ChargeBubbleView_charge_battery_color, Color.LTGRAY);mCurrentBatteryColor = array.getColor(R.styleable.ChargeBubbleView_charge_battery_current_color, Color.GREEN);hasBatteryCap = array.getBoolean(R.styleable.ChargeBubbleView_charge_battery_cap, true);hasWaterBezier = array.getBoolean(R.styleable.ChargeBubbleView_charge_battery_water_bezier, false);hasText = array.getBoolean(R.styleable.ChargeBubbleView_charge_battery_has_text, true);mBatteryTextColor = array.getColor(R.styleable.ChargeBubbleView_charge_battery_text_color, Color.RED);mCapFillColor = array.getColor(R.styleable.ChargeBubbleView_charge_battery_cap_fill_color, Color.CYAN);mBatteryTextSize = array.getDimension(R.styleable.ChargeBubbleView_charge_battery_text_size, 60f);array.recycle();}density = getResources().getDisplayMetrics().density;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);mWidth = MeasureSpec.getSize(widthMeasureSpec);mHeight = MeasureSpec.getSize(heightMeasureSpec);mBatteryPercent = hasBatteryCap ? (mHeight - mWidth / 6f) / 100f : mHeight / 100f;// 电池边框,初始化绘制一次即可mBatteryStrokePath = getBatteryStroke(hasBatteryCap, mWidth, mHeight);}private Path getBatteryStroke(boolean hasCap, float width, float height) {Path path = new Path();if (hasCap) {float capWidth = width / 3f;float capHeight = capWidth / 2f;path.moveTo(0, capHeight);path.lineTo(0, height);path.lineTo(width, height);path.lineTo(width, capHeight);path.lineTo(capWidth * 2, capHeight);path.lineTo(capWidth * 2, 0);path.lineTo(capWidth, 0);path.lineTo(capWidth, capHeight);path.close();} else {path.moveTo(0, 0);path.lineTo(0, height);path.lineTo(width, height);path.lineTo(width, 0);path.close();}return path;}private Path[] getRealBatteryPath() {Path[] path = new Path[2];float realBatteryHeight = mCurrentBattery * mBatteryPercent;if (hasWaterBezier) {path[0] = new Path();path[1] = new Path();float realY = mHeight - realBatteryHeight;float p0x = 0;float p0y = realY;float pc1x = mWidth / 4;float pc1y = realY - 50;float p1x = mWidth / 2;float p1y = realY;float pc2x = p1x + pc1x;float pc2y = realY + 50;float p2x = mWidth;float p2y = realY;path[0].moveTo(p0x + diff, p0y);path[0].quadTo(pc1x + diff, pc1y, p1x + diff, p1y);path[0].quadTo(pc2x + diff, pc2y, p2x + diff, p2y);path[0].lineTo(mWidth + diff, mHeight);path[0].lineTo(0 + diff, mHeight);path[0].close();float p0x2 = 0;float p0y2 = realY;float pc1x2 = mWidth / 4;float pc1y2 = realY - 50;float p1x2 = mWidth / 2;float p1y2 = realY;float pc2x2 = p1x + pc1x;float pc2y2 = realY + 50;float p2x2 = mWidth;float p2y2 = realY;path[1].moveTo(p0x2 - mWidth + diff, p0y2);path[1].quadTo(pc1x2 - mWidth + diff, pc1y2, p1x2 - mWidth + diff, p1y2);path[1].quadTo(pc2x2 - mWidth + diff, pc2y2, p2x2 - mWidth + diff, p2y2);path[1].lineTo(diff, mHeight);path[1].lineTo(-mWidth + diff, mHeight);path[1].close();if (mWaterAnimator == null) {mWaterAnimator = ObjectAnimator.ofFloat(0, mWidth);mWaterAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {diff = (float) animation.getAnimatedValue();}});mWaterAnimator.setDuration(2000);mWaterAnimator.setRepeatCount(-1);mWaterAnimator.start();}} else {path[0] = new Path();path[0].moveTo(0, mHeight - realBatteryHeight);path[0].lineTo(0, mHeight);path[0].lineTo(mWidth, mHeight);path[0].lineTo(mWidth, mHeight - realBatteryHeight);path[0].close();}return path;}@Overrideprotected void onDraw(Canvas canvas) {drawBattery(canvas);drawBatteryStroke(canvas);drawBatteryText(canvas);drawBatteryCap(canvas);}private void drawBatteryStroke(Canvas canvas) {canvas.drawPath(mBatteryStrokePath, mPaints[1]);}private void drawBatteryText(Canvas canvas) {if (hasText) {String batteryText = mCurrentBattery >= 100 ? "充电完成" : (String.valueOf(mCurrentBattery) + "%");Rect bounnds = new Rect();mPaints[3].getTextBounds(batteryText, 0, batteryText.length(), bounnds);if (mCurrentBattery >= 100) {canvas.drawText(batteryText, mWidth / 2 - bounnds.width() / 2, mHeight / 2, mPaints[3]);} else {canvas.drawText(batteryText, mWidth / 2 - bounnds.width() / 2, mHeight - mCurrentBattery * mBatteryPercent - mTextHeight, mPaints[3]);}}}private void drawBatteryCap(Canvas canvas) {if (hasBatteryCap) { // 如果有电池盖子,画一个颜色填充一下,否则有点丑if (mCurrentBattery >= 100) {Path path = new Path();float capWidth = mWidth / 3f;float capHeight = capWidth / 2f;path.moveTo(capWidth, capHeight);path.lineTo(capWidth, 0);path.lineTo(capWidth * 2, 0);path.lineTo(capWidth * 2, capHeight);path.close();canvas.drawPath(path, mPaints[4]);}}}private void drawBattery(Canvas canvas) {//默认电量为 0mRealBatteryPath = getRealBatteryPath();if (mCurrentBattery == 100 || mRealBatteryPath[0].isEmpty()) {if (mWaterAnimator != null)mWaterAnimator.cancel();Path path = getBatteryStroke(hasBatteryCap, mWidth, mHeight);canvas.drawPath(path, mPaints[0]);return;}if (hasWaterBezier) {canvas.drawPath(mRealBatteryPath[0], mPaints[0]);canvas.drawPath(mRealBatteryPath[1], mPaints[0]);} else {canvas.drawPath(mRealBatteryPath[0], mPaints[0]);}}public void setBatteryCapacity(int batteryCapacity) {this.mCurrentBattery = batteryCapacity;invalidate();}public void clearAnimator() {if (mWaterAnimator != null) {mWaterAnimator.cancel();}}}

5、使用方式就像我说的一样,非常简单,如下:

    <nd.no.xww.qqmessagedragview.ChargeBubbleViewandroid:id="@+id/battery"android:layout_width="160dp"app:charge_battery_width="2dp"app:charge_battery_cap="true"app:charge_battery_color="#6A5ACD"app:charge_battery_text_size="20sp"app:charge_battery_water_bezier="true"app:charge_battery_cap_fill_color="#6495ED"app:charge_battery_current_color="#BA55D3"app:charge_battery_text_color="#54FF9F"app:charge_battery_has_text="true"android:layout_height="300dp"android:layout_gravity="center" />

好了,这样的话,你只要拿到该 View 的实例,就可以从过 set 方法,设置电量的值了,还可以调用 clear 来清除动画效果,那么本效果就实现到这里,也算是一个挺完整的自定义 View,有什么额外的功能,可以基于此自己改进。

不过有点可惜,没有达到我预想的效果,这个效果还是太简单了,没什么难度,我的冒泡泡的动画还没实现,下一次再研究研究,接着写到这个效果里面去。

贝塞尔曲线(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(贝塞尔)曲线的轨迹规划在自动驾驶中的应用(二)

    根据(一)中的理论前提,我们通过matlab进行一次仿真验证: clcclear allp0 = [ 0, 0];p1 = [10,0.5];p2= [20,3.5];p3 = [30,4];%设置控 ...

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

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

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

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

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

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

最新文章

  1. 【转】 一些NET的实用类,不错
  2. 这是一篇优雅的Springboot2.0使用手册
  3. git 服务器自动部署项目之GitHooks
  4. NppFTP小插件的使用
  5. Codeforces Round #632 (Div. 2)巧用小技巧
  6. 应急照明市电检测_应急照明如何供电? 如何接线? 图文分析!
  7. 线程属性 pthread_attr_t
  8. 上手Caffe(一)
  9. 2018年黑龙江由俄进口原油2725.2万吨同比增加67.1%
  10. 输入url并按下回车的那一刻发生了什么?
  11. 思必驰十年创业,(现在)是一家怎样的公司?
  12. python 分段拟合(curve fit)
  13. Oracle21c 官方文档:数据库概述之数据库体系架构
  14. latex设置页面边距,页面大小,页边距,geometry宏包
  15. 关于菜鸡学习时服务器购买的注意点
  16. HBuilderX运行到手机或模拟器提示没有找到设备
  17. matlab命令窗口作用是什么,wcodemat这个命令在matlab中是实现什么功能的
  18. 嵌入式系统框架----硬件篇
  19. SpringSecurity(六)注销登录
  20. 冒泡排序及其时间、空间复杂度解析

热门文章

  1. 若依集成企业微信步骤
  2. 搞清楚字符编码09-万国码[5]
  3. java 短信_Java发短信Demo
  4. CAP原理这样理解最简单
  5. iOS8定位代理方法不回调解决方法
  6. 名帖24 杨沂孙 篆书《夏小正八条屏》
  7. Hyperface人脸检测算法
  8. 软件开发的权限系统功能模块设计,分享主流的九种常见权限模型
  9. iOS开发之苹果开发者客服电话
  10. Fun Box测评:想做客厅游戏的电视盒子