仿网易邮箱大师进度框
最近用上了网易邮箱大师,发现整个APP的风格非常简洁美观。特别喜欢它的进度框,就尝试用属性动画做了一个,算是对属性动画学习的实践。
先上效果图:
图像构成
这个图像由三段弧线构成。用到的方法是canvas.drawArc()
需要注意的是paint的设置。
// 抗锯齿
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
// 默认颜色
paint.setColor(Color.parseColor("#B33425"));
// 设置为只画线不填充
paint.setStyle(Paint.Style.STROKE);
// 设置默认的线宽
paint.setStrokeWidth(strokWidth);
// 将线的两头设置为半圆
paint.setStrokeCap(Paint.Cap.ROUND);
绘制这三段弧线
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (animatorSet == null) {
// 如果动画不存在,就启动动画
startAnimation();
}
// 设置线宽
paint.setStrokeWidth(strokWidth);
// 画红色弧线
paint.setColor(Color.parseColor("#B33425"));
canvas.drawArc(getRectF(), 0f + angle, 80f, false, paint);
// 隔开120度,再画灰线
paint.setColor(Color.parseColor("#5D5C5A"));
canvas.drawArc(getRectF(), 0f + angle + 120, 80f, false, paint);
// 隔开240度, 再画蓝线
paint.setColor(Color.parseColor("#2972A6"));
canvas.drawArc(getRectF(), 0f + angle + 240, 80f, false, paint);
}
动画构成
仔细观察,可以看出整个动画由三个小动画组成:
1. 旋转,通过控制弧线起始角度
2. 进度条的粗细变化,通过控制paint的strokeWidth
3. 绘画空间的缩放,通过控制drawArc的RectF参数
定义三个域来保存数据,并通过动画操纵它们:
// 绘制角度
private float angle = 0;
// 线宽
private float strokWidth = 5;
// 距离边框的空白大小
private float padding = 0;
开始动画:
public void startAnimation() {
// 简单的ValueAnimator,从0度到360度变化。
ValueAnimator circleAnimator = ValueAnimator.ofFloat(0, 360);
circleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
angle = (float) animation.getAnimatedValue();
invalidate();
}
});
// 一直正向循环
circleAnimator.setRepeatMode(ValueAnimator.RESTART);
circleAnimator.setRepeatCount(ValueAnimator.INFINITE);
// 控制线宽,从5像素到20像素
ValueAnimator thickAnimator = ValueAnimator.ofFloat(5, 20);
thickAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
strokWidth = (float) animation.getAnimatedValue();
invalidate();
}
});
// 反向循环
thickAnimator.setRepeatMode(ValueAnimator.REVERSE);
thickAnimator.setRepeatCount(ValueAnimator.INFINITE);
// 控制绘制空间的空白变化,会让弧线变曲
ValueAnimator paddingAnimator = ValueAnimator.ofFloat(10, 100);
paddingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
padding = (float) animation.getAnimatedValue();
invalidate();
}
});
paddingAnimator.setRepeatMode(ValueAnimator.REVERSE);
paddingAnimator.setRepeatCount(ValueAnimator.INFINITE);
// 同时播放三个动画
animatorSet = new AnimatorSet();
animatorSet.play(circleAnimator).with(thickAnimator).with(paddingAnimator);
animatorSet.setDuration(1500);
animatorSet.start();
}
绘制空间的变化:
// 获取绘制空间,通过padding控制坐标
private RectF getRectF() {
return new RectF(0f + strokWidth / 2 + padding, // 左
0f + strokWidth / 2 + padding, // 上
getWidth() - strokWidth / 2 - padding, // 右
getWidth() - strokWidth / 2 - padding); // 下
}
附上全文件
/**
* Created by JayChen on 2016/10/13.
*/
public class TripProgress extends View {
private Paint paint;
// 绘制角度
private float angle = 0;
// 线宽
private float strokWidth = 5;
// 距离边框的空白大小
private float padding = 0;
private AnimatorSet animatorSet;
public TripProgress(Context context, AttributeSet attrs) {
super(context, attrs);
// 抗锯齿
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
// 默认颜色
paint.setColor(Color.parseColor("#B33425"));
// 设置为只画线不填充
paint.setStyle(Paint.Style.STROKE);
// 设置默认的线宽
paint.setStrokeWidth(strokWidth);
// 将线的两头设置为半圆
paint.setStrokeCap(Paint.Cap.ROUND);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (animatorSet == null) {
// 如果动画不存在,就启动动画
startAnimation();
}
// 设置线宽
paint.setStrokeWidth(strokWidth);
// 画红色弧线
paint.setColor(Color.parseColor("#B33425"));
canvas.drawArc(getRectF(), 0f + angle, 80f, false, paint);
// 隔开120度,再画灰线
paint.setColor(Color.parseColor("#5D5C5A"));
canvas.drawArc(getRectF(), 0f + angle + 120, 80f, false, paint);
// 隔开240度, 再画蓝线
paint.setColor(Color.parseColor("#2972A6"));
canvas.drawArc(getRectF(), 0f + angle + 240, 80f, false, paint);
}
public void startAnimation() {
// 简单的ValueAnimator,从0度到360度变化。
ValueAnimator circleAnimator = ValueAnimator.ofFloat(0, 360);
circleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
angle = (float) animation.getAnimatedValue();
invalidate();
}
});
// 一直正向循环
circleAnimator.setRepeatMode(ValueAnimator.RESTART);
circleAnimator.setRepeatCount(ValueAnimator.INFINITE);
// 控制线宽,从5像素到20像素
ValueAnimator thickAnimator = ValueAnimator.ofFloat(5, 20);
thickAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
strokWidth = (float) animation.getAnimatedValue();
invalidate();
}
});
// 反向循环
thickAnimator.setRepeatMode(ValueAnimator.REVERSE);
thickAnimator.setRepeatCount(ValueAnimator.INFINITE);
// 控制绘制空间的空白变化,会让弧线变曲
ValueAnimator paddingAnimator = ValueAnimator.ofFloat(10, 100);
paddingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
padding = (float) animation.getAnimatedValue();
invalidate();
}
});
paddingAnimator.setRepeatMode(ValueAnimator.REVERSE);
paddingAnimator.setRepeatCount(ValueAnimator.INFINITE);
// 同时播放三个动画
animatorSet = new AnimatorSet();
animatorSet.play(circleAnimator).with(thickAnimator).with(paddingAnimator);
animatorSet.setDuration(1500);
animatorSet.start();
}
// 获取绘制空间,通过padding控制坐标
private RectF getRectF() {
return new RectF(0f + strokWidth / 2 + padding, // 左
0f + strokWidth / 2 + padding, // 上
getWidth() - strokWidth / 2 - padding, // 右
getWidth() - strokWidth / 2 - padding); // 下
}
}