接下来便详细解析一下如何完成这个功能,了解其中的原理,这样就能举一反三,实现其他类似的动画效果了。
详细代码请看大屏幕
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的宽和高,** @param widthMeasureSpec* @param heightMeasureSpec*/@Overrideprotected 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);}有了上面的设置,接下来我们就可以按部就班的画图了。
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);}绘制动画
/*** 绘制交流动画** @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);}}我们首先获取当前的进度,然后依次给它填充背景,这就是已完成的进度表示。
/*** 设置交流动画*/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() {@Overridepublic 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() {@Overridepublic 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即可。