仿網易郵箱大師進度框
最近用上了網易郵箱大師,發現整個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); // 下
}
}