学向勤中得,萤窗万卷书。这篇文章主要讲述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即可。
以上便是主要的原理, 对于源码, 可以访问《ChargeProgress》。
关于作者:
1. 简书 http://www.jianshu.com/users/18281bdb07ce/latest_articles
2. 博客 http://crazyandcoder.github.io/
3. github https://github.com/crazyandcoder
【android 自定义view+属性动画实现充电进度条】4. 开源中国 https://my.oschina.net/crazyandcoder/blog
推荐阅读
- Android实现远程控制PC(Android[客户端]+Qt[服务器端])
- android中的回调简单认识
- Android 外置SDCard读写权限总结
- Glow Android 优化实践
- Android中使用Notification实现宽通知栏(Notification示例二)
- Android之HttpURLConnection
- AndroidPageObjectTest_Simple.java
- Android之RecyclerView入门
- android环境下的即时通讯