Android 自定义view和属性动画实现充电进度条效果
近期项目中需要使用到一种类似手机电池充电进度的动画效果,以前没学属性动画的时候,是用图片+定时器的方式来完成的,最近一直在学习动画这一块,再加上复习一下自定义view的相关知识点,所以打算用属性动画和自定义view的方式来完成这个功能,将它开源出来,供有需要的人了解一下相关的内容。
本次实现的功能类似下面的效果:
接下来便详细解析一下如何完成这个功能,了解其中的原理,这样就能举一反三,实现其他类似的动画效果了。
详细代码请看大屏幕
https://github.com/crazyandcoder/chargeprogress
图形解析
一般,我们自定义view时,是将该view进行化解,分成一个一个小部分,然后在重叠起来进行绘制,对于这个项目,也是按照相同的步骤进行。我们用word来简单解析一下该动画所包含的基本结构。
对于这个充电进度view,我将它分成了abcd四个部分,下面来详细说明各个部分的组成。
a部分
对于a而言,它是位于整个view的顶部,居中显示,是一个圆角矩形。
b部分
对于b而言,它是整个view的重要组成部分,包含c和d两部分,其中b主要属性就是背景色的设置。
c部分
对于c而言,c就是每一个进度的样式,显示的是未完成的进度条样式。
d部分
对于d而言,它跟c是一样的,只不过是已经完成的进度样式,区别在于颜色的不一样。
其实,这个进度view图形结构还是比较简单的,只是一些简单的矩形,组合而成,因此对于以上的分析,我们轻易的得出一些重要的属性。
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="charging_progress"> <!--item个数--> <attr name="cgv_item_count" format="integer" /> <!--边界宽度--> <attr name="cgv_border_width" format="dimension" /> <!--边界颜色--> <attr name="cgv_border_color" format="color" /> <!--圆角半径--> <attr name="cgv_border_cornor_radius" format="dimension" /> <!--充电内每个进度item模块的宽度--> <attr name="cgv_item_width" format="dimension" /> <!--充电内每个进度item模块的高度--> <attr name="cgv_item_height" format="dimension" /> <!--充电内每个进度item模块的前景色,充电中的颜色--> <attr name="cgv_item_charging_src" format="color" /> <!--充电内每个进度item模块的背景色,未充电的颜色--> <attr name="cgv_item_charging_background" format="color" /> <!--view 的背景--> <attr name="cgv_background" format="color" /> </declare-styleable> </resources>
对于以上属性,我们在自定义view的时候需要在xml文件中进行设置,如果没有设置的话,我们给出一个默认。然后我们在代码中进行获取这些属性值。
//边界宽度 private float border_width; //item个数 private int item_count; //边界宽度 private float item_width; //边界高度 private float item_height; //view内部的进度前景色 private int item_charging_src; //view内部的进度背景色 private int item_charging_background; //view背景色 private int background; //<!--边界颜色--> private int border_color; //圆角半径 private float border_cornor_radius; //获取xml中设定的属性值 typedarray array = mcontext.obtainstyledattributes(attrs, r.styleable.charging_progress); border_width = array.getdimension(r.styleable.charging_progress_cgv_border_width, dp2px(2)); item_height = array.getdimension(r.styleable.charging_progress_cgv_item_height, dp2px(10)); item_width = array.getdimension(r.styleable.charging_progress_cgv_item_width, dp2px(20)); item_charging_src = array.getcolor(r.styleable.charging_progress_cgv_item_charging_src, 0xffffea00); item_charging_background = array.getcolor(r.styleable.charging_progress_cgv_item_charging_background, 0xff544645); background = array.getcolor(r.styleable.charging_progress_cgv_background, 0xff463938); border_color = array.getcolor(r.styleable.charging_progress_cgv_border_color, 0xffb49d7c); border_cornor_radius = array.getdimension(r.styleable.charging_progress_cgv_border_cornor_radius, dp2px(2)); item_count = array.getint(r.styleable.charging_progress_cgv_item_count, 10); array.recycle();
已经获取了自定义属性的值,那么接下来,我们就来具体绘制这些组合图形。
对于一个自定义view,首先要做的就是测量view的大小,而本项目中view的宽度和高度,宽度是好计算的,我们设置view的宽度等于item_widht 乘以2 。但是对于高度的话,因为我们设置了progress的级数,也就是item_count,也设置了item的高度和宽度,所以对于高度,我们可以通过计算item_count 乘以 item_height,再加上间隔数和顶部矩形的就是整个view的高度。同时,我们设定,顶部矩形的高度等于item_height,宽度等于item_widht的一半,中间间隔等于item_height 除以2
/** * 测量view的宽和高, * * @param widthmeasurespec * @param heightmeasurespec */ @override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { super.onmeasure(widthmeasurespec, heightmeasurespec); //总间隔数=(item_count+1) 乘以间隔高度(间隔高度等于item_height的一半) //总数=item_count 乘以 item_height + 总间隔数 + 顶部一个矩形(高度等于item的高度,宽度等于item的宽度的一半) mheight = (int) (item_count * item_height + (item_count + 1) * item_height / 2 + item_height); mwidth = (int) (2 * item_width); setmeasureddimension(mwidth, mheight); }
有了上面的设置,接下来我们就可以按部就班的画图了。
对于坐标中心点是设定在左上角,也就是(0,0)处。
画顶部矩形
知道了坐标系的原点,那么顶部矩形的坐标就可以计算了。
首先设置画笔。
mpaint.setstyle(paint.style.stroke); mpaint.setstrokewidth(border_width); mpaint.setcolor((border_color));
由于顶部矩形的width等于item_widht的一半,所以它的width等于整个view的width的1/6,
int left = mwidth * 3 / 8; int top = 0; int right = 5 * mwidth / 8; int bottom = (int) item_height / 2; //顶部的矩形 rectf toprect = new rectf(left, top, right, bottom); canvas.drawroundrect(toprect, border_cornor_radius, border_cornor_radius, mpaint);
接下来绘制底部的矩形,也就是包含进度item的矩形
//总的进度背景 rectf border = new rectf(0, bottom, mwidth, mheight); canvas.drawroundrect(border, border_cornor_radius, border_cornor_radius, mpaint);
接下来绘制每个item的矩形,对于每个item的坐标,实际上是有规律可循的。
//绘制所有的进度 for (int i = 1; i <= item_count; i++) { mpaint.setstyle(paint.style.fill); mpaint.setcolor((item_charging_background)); rectf backrect = new rectf(mwidth / 4, (i + 1) * item_height / 2 + (i - 1) * item_height, 3 * mwidth / 4, item_height / 2 + i * (3 * item_height / 2)); canvas.drawroundrect(backrect, border_cornor_radius, border_cornor_radius, mpaint); }
绘制动画
对于交流动画,就是从进度0到100的动画显示,依次显示。其实也是对于坐标的计算而已。接下来最终要的功能就是动画的使用了,我们使用的是属性动画呢?因为,常规的动画它不支持啊,很简单。
对于android属性动画的学习,可以查看这篇文章,稍微了解一下。《android动画了解》
1、交流动画
/** * 绘制交流动画 * * @param canvas */ private void drawacanimaiton(canvas canvas) { int j = getprogress() / item_count; //已经充好的进度 for (int i = item_count; i >= (item_count - j); i--) { rectf backrect = new rectf(mwidth / 4, (i + 1) * item_height / 2 + (i - 1) * item_height, 3 * mwidth / 4, item_height / 2 + i * (3 * item_height / 2)); canvas.drawroundrect(backrect, border_cornor_radius, border_cornor_radius, mpaint); mpaint.setstyle(paint.style.fill); mpaint.setcolor(item_charging_src); canvas.drawroundrect(backrect, border_cornor_radius, border_cornor_radius, mpaint); } }
我们首先获取当前的进度,然后依次给它填充背景,这就是已完成的进度表示。
然后使用动画即可,我们设置进度为100,也就是充满,然后设置动画时间是10秒钟,对于下面的动画执行原理是什么呢?其实很简单,获取当前的进度,然后从0开始,依次绘制进度,知道绘制的进度为100就是总的进度,最后再循环执行动画即可。
/** * 设置交流动画 */ public void setacanimation() { chargetype = ac; animac = objectanimator.ofint(this, "progress", 100); animac.setduration(10 * 1000); animac.setinterpolator(new linearinterpolator()); animac.setrepeatcount(valueanimator.infinite); animac.addupdatelistener(new valueanimator.animatorupdatelistener() { @override public void onanimationupdate(valueanimator animation) { invalidate(); } }); animac.start(); }
2、直流动画
对于直流动画就稍微比较复杂了。当我们设置了进度后,需要我们预先绘制已完成的进度,然后在下一个进度进行闪烁表示动画,那么该如何完成呢?
首先看绘制代码:
/** * 直流动画 * * @param canvas */ private void drawdcaniamtion(canvas canvas) { int j = getprogress() / item_count; //已经充好的进度 for (int i = item_count; i > (item_count - j); i--) { rectf backrect = new rectf(mwidth / 4, (i + 1) * item_height / 2 + (i - 1) * item_height, 3 * mwidth / 4, item_height / 2 + i * (3 * item_height / 2)); canvas.drawroundrect(backrect, border_cornor_radius, border_cornor_radius, mpaint); mpaint.setstyle(paint.style.fill); mpaint.setcolor(item_charging_src); canvas.drawroundrect(backrect, border_cornor_radius, border_cornor_radius, mpaint); } //下一个进度,隐藏和显示交替执行动画 int i = item_count - j; if (i > 0) { rectf backrect = new rectf(mwidth / 4, (i + 1) * item_height / 2 + (i - 1) * item_height, 3 * mwidth / 4, item_height / 2 + i * (3 * item_height / 2)); mpaint.setstyle(paint.style.fill); if (show) { mpaint.setcolor((item_charging_src)); } else { mpaint.setcolor((item_charging_background)); } canvas.drawroundrect(backrect, border_cornor_radius, border_cornor_radius, mpaint); } }
首先绘制已完成的进度,然后在绘制闪烁的部分。
/** * 直流动画 * * @param progress */ public void setdcanimation(final int progress) { chargetype = dc; animatordc = valueanimator.offloat(0, 1); animatordc.setinterpolator(new linearinterpolator()); animatordc.setduration(1000); animatordc.setrepeatcount(-1); animatordc.setrepeatmode(valueanimator.restart); animatordc.addupdatelistener(new valueanimator.animatorupdatelistener() { @override public void onanimationupdate(valueanimator animation) { float value = (float) animation.getanimatedvalue(); if (value > 0.5) { show = true; } else { show = false; } setprogress(progress); } }); animatordc.start(); }
到这里,就很明了了。对于直流动画,我们使用属性动画中这个valueanimator类,它的意思就是从0到1平滑的过渡,在设定的时间内。我们的原理是当达到0.5以上后就设定灰色进度,当小于0.5的话就设置亮色进度,然后在刷新一下view即可。
以上所述是小编给大家介绍的android 自定义view和属性动画实现充电进度条效果,希望对大家有所帮助
上一篇: Android五大布局与实际应用详解
下一篇: MySQL中触发器入门简单实例与介绍