仿狗東載入效果—支援載入成功和載入失敗動畫效果和顏色自定義的view
最近在爬坑自定義View,看到狗東支付時有一個支付成功後的動畫效果。
遂決定自己也擼一個,加入了自己的一些想法,把實現的思路分享一下。
特點:
- 載入的view元素顏色支援自定義
- 載入成功和載入失敗會有一個動畫效果
先上效果圖
自定義屬性
新建 res/values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="PowerfulLoadingView">
<!--載入檢視背景的顏色-->
<attr name="bg_color" format="color"/>
<!--載入條的顏色-->
<attr name="loading_bar_color" format="color"/>
<!--鉤和叉的顏色-->
<attr name="tick_cross_color" format="color"/>
</declare-styleable>
</resources>
老規矩,構造方法三連擊。然後進行自定義屬性值的讀取。如果未指定自定義屬性,則使用預設值。
這裡new了兩個畫筆,一個用於畫線和圓弧,一個用於畫實心圓
private static final int DEFAULT_CONTENT_COLOR = Color.WHITE;
private static final int DEFAULT_LOADING_BAR_COLOR = Color.rgb(65, 105, 225);
private static final int DEFAULT_BG_COLOR = Color.argb(55, 0, 0, 0);
private int mLoadingBarColor = DEFAULT_LOADING_BAR_COLOR;
private int mLoadingBgColor = DEFAULT_BG_COLOR;
private int mTickOrCrossColor = DEFAULT_CONTENT_COLOR;
public PowerfulLoadingView(Context context) {
super(context);
init(context, null);
}
public PowerfulLoadingView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public PowerfulLoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, @Nullable AttributeSet attrs) {
mContext = context;
//用於畫實心圓
mPaintFill = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaintFill.setStyle(Paint.Style.FILL);
//用於畫線和圓弧
mPaintStroke = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaintStroke.setStyle(Paint.Style.STROKE);
mPaintStroke.setStrokeWidth(STROKE_WIDTH);
if (attrs != null) {
TypedArray array = mContext.obtainStyledAttributes(attrs, R.styleable.PowerfulLoadingView);
for (int i = 0; i < array.getIndexCount(); i++) {
int attr = array.getIndex(i);
switch (attr) {
case R.styleable.PowerfulLoadingView_bg_color:
mLoadingBgColor = array.getColor(attr, DEFAULT_BG_COLOR);
break;
case R.styleable.PowerfulLoadingView_loading_bar_color:
mLoadingBarColor = array.getColor(attr, DEFAULT_LOADING_BAR_COLOR);
break;
case R.styleable.PowerfulLoadingView_tick_cross_color:
mTickOrCrossColor = array.getColor(attr, DEFAULT_CONTENT_COLOR);
break;
}
}
array.recycle();
}
}
載入中效果實現
看下載入效果,是一條圓弧在圍繞中心轉動。這裡採用 ValueAnimator 來實現。
定義一個0~360的範圍,表示角度360度變化。在 setDuration 設定的時間內,AnimatedValue 會從0遞增到360,值每變化一次,都會呼叫 onDraw 方法進行重繪。每次進入onDraw方法,都會通過 drawCircle 先繪製一個圓形的背景,然後通過 getAnimatedValue 方法獲取當前的值(表示角度值),然後通過 drawArc 方法繪製圓弧。只需要改變繪製圓弧時的傳入的角度引數,就可以實現旋轉的效果。
private ValueAnimator mCircleAngleAnimator;
public void startLoading() {
clearAllAnimator();
//初始化載入條動畫,並迴圈播放
mCircleAngleAnimator = ValueAnimator.ofFloat(0, 360);
mCircleAngleAnimator.setDuration(ANIMATOR_TIME);
mCircleAngleAnimator.setRepeatMode(ValueAnimator.RESTART);
mCircleAngleAnimator.setRepeatCount(ValueAnimator.INFINITE);
mCircleAngleAnimator.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//繪製轉動的載入條
if (mCircleAngleAnimator != null && mCircleAngleAnimator.isRunning()) {
drawBackground(canvas, mLoadingBgColor);
mPaintStroke.setColor(mLoadingBarColor);
mRectF.set(STROKE_WIDTH * 2, STROKE_WIDTH * 2,
getWidth() - STROKE_WIDTH * 2, getHeight() - STROKE_WIDTH * 2);
canvas.drawArc(mRectF, (float) mCircleAngleAnimator.getAnimatedValue(), 270, false, mPaintStroke);
invalidate();
}
......
}
//繪製背景
private void drawBackground(Canvas canvas, int color) {
mPaintFill.setColor(color);
canvas.drawCircle(getWidth() / 2f, getHeight() / 2f, getWidth() / 2f, mPaintFill);
}
載入成功和載入失敗效果實現
確定鉤和叉的大小和位置
繪製載入完成的動畫之前,需要先確定鉤和叉的大小和位置。這一步在 onMeasure 中完成。
分析一下,可以知道,確定一個鉤的大小和位置,需要確定三個點的座標。確定一個叉的大小和位置,需要確定四個點的座標。所以這裡定義兩個 Point 物件的陣列來儲存這些座標資訊。
這裡採用view整個畫布的中心點,來做為參考點,來確定這個7個點的座標。具體可以自行調整。
private Point[] mTickPoint = new Point[3];
private Point[] mCrossPoint = new Point[4];
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
measureTickPosition(width / 2, height / 2);
measureCrossPosition(width / 2, height / 2);
}
//測量鉤的大小和位置
private void measureTickPosition(int centerX, int centerY) {
Point position = new Point();
position.x = centerX / 2;
position.y = centerY;
mTickPoint[0] = position;
position = new Point();
position.x = centerX / 10 * 9;
position.y = centerY + centerY / 3;
mTickPoint[1] = position;
position = new Point();
position.x = centerX + centerX / 2;
position.y = centerY / 3 * 2;
mTickPoint[2] = position;
}
//測量叉的大小和位置
private void measureCrossPosition(int centerX, int centerY) {
Point position = new Point();
position.x = centerX / 3 * 2;
position.y = centerY / 3 * 2;
mCrossPoint[0] = position;
position = new Point();
position.x = centerX / 3 * 2;
position.y = centerY + centerY / 3;
mCrossPoint[1] = position;
position = new Point();
position.x = centerX + centerX / 3;
position.y = centerY + centerY / 3;
mCrossPoint[2] = position;
position = new Point();
position.x = centerX + centerX / 3;
position.y = centerY / 3 * 2;
mCrossPoint[3] = position;
}
載入成功的效果實現
向外暴露方法 loadSucceed(),來實現載入成功的入口。載入完成後,先有個以畫布中心為圓心的實心圓不斷縮小的過渡動畫,然後同時播放鉤出現的動畫和放大再回彈的動畫。同樣採用屬性動畫來實現。動畫的實現,跟上面的載入條基本一致,也是利用 ValueAnimator 的值不斷變化,然後不斷重繪,來實現動畫效果。
- 繪製向圓心縮的動畫,先繪製一個固定的背景實心圓,然後通過 ValueAnimator 值的變化,在背景上繪製半徑不斷減小的實心圓。
- 繪製一個鉤,只需要根據之前儲存在 mTickPoint 陣列中的三個點的座標,通過 drawLine 繪製兩條線即可。然後通過ValueAnimator 值變化,改變繪製的透明度,從全透明到不透明,免得鉤出現的很突兀,有一個過渡效果。
- 放大再回彈的效果。通過 ObjectAnimator 的縮放動畫來完成。先放大,再縮小到圓尺寸即可。
動畫完成後,如果需要進行進一步的邏輯操作,可以傳入一個監聽介面,當動畫完成後,會回撥 onAnimationEnd 方法,在該方法中進行相應操作即可。
private ValueAnimator mCircleRadiusAnimator;
private ValueAnimator mTickAnim;
private AnimatorSet mAnimatorSet = new AnimatorSet();
private ObjectAnimator mScaleAnimator;
public void loadSucceed(@Nullable Animator.AnimatorListener listener) {
clearAllAnimator();
//初始化向圓心縮小的圓的動畫
mCircleRadiusAnimator = ValueAnimator.ofFloat(0, getWidth() / 2f);
mCircleRadiusAnimator.setDuration(ANIMATOR_TIME / 2);
//初始化打鉤的動畫,並註冊監聽
mTickAnim = ValueAnimator.ofInt(0, 255);
mTickAnim.setDuration(ANIMATOR_TIME / 2);
if (listener != null) {
mTickAnim.addListener(listener);
}
//放大再回彈的動畫
mScaleAnimator = getScaleAnimator();
mAnimatorSet.play(mTickAnim).after(mCircleRadiusAnimator).with(mScaleAnimator);
mAnimatorSet.start();
}
//獲取放大再回彈的動畫
private ObjectAnimator getScaleAnimator() {
PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat(SCALE_X, 1f, 1.2f, 1f);
PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat(SCALE_Y, 1f, 1.2f, 1f);
return ObjectAnimator
.ofPropertyValuesHolder(this, scaleX, scaleY)
.setDuration(ANIMATOR_TIME / 2);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
......
//繪製向圓心縮小的圓
if (mCircleRadiusAnimator != null && mCircleRadiusAnimator.isRunning()) {
drawBackground(canvas, mLoadingBarColor);
mPaintFill.setColor(mTickOrCrossColor);
canvas.drawCircle(getWidth() / 2f, getHeight() / 2f,
getWidth() / 2f - (float) mCircleRadiusAnimator.getAnimatedValue(), mPaintFill);
invalidate();
}
//繪製鉤
if (mTickAnim != null && mTickAnim.isRunning()) {
drawBackground(canvas, mLoadingBarColor);
mPaintStroke.setAlpha((int) mTickAnim.getAnimatedValue());
mPaintStroke.setColor(mTickOrCrossColor);
mPaintStroke.setStrokeCap(Paint.Cap.ROUND); //畫線時,線頭為圓形
canvas.drawLine(mTickPoint[0].x, mTickPoint[0].y, mTickPoint[1].x, mTickPoint[1].y, mPaintStroke);
canvas.drawLine(mTickPoint[1].x, mTickPoint[1].y, mTickPoint[2].x, mTickPoint[2].y, mPaintStroke);
invalidate();
}
......
}
載入失敗的效果實現
向外暴露方法 loadFailed(),來實現載入失敗的入口。大部分的實現和上面的一致。唯一的區別就是繪製叉。繪製叉也很簡單,根據儲存在陣列 mCrossPoint 中的四個點的座標,通過 drawLine 繪製出兩條交叉的線即可完成。
private ValueAnimator mCircleRadiusAnimator;
private ValueAnimator mCrossAnim;
private AnimatorSet mAnimatorSet = new AnimatorSet();
private ObjectAnimator mScaleAnimator;
public void loadFailed(@Nullable Animator.AnimatorListener listener) {
clearAllAnimator();
//初始化向圓心縮小的圓的動畫
mCircleRadiusAnimator = ValueAnimator.ofFloat(0, getWidth() / 2f);
mCircleRadiusAnimator.setDuration(ANIMATOR_TIME / 2);
//初始化打叉的動畫,並註冊監聽
mCrossAnim = ValueAnimator.ofInt(0, 255);
mCrossAnim.setDuration(ANIMATOR_TIME / 2);
if (listener != null) {
mCrossAnim.addListener(listener);
}
//放大再回彈的動畫
mScaleAnimator = getScaleAnimator();
mAnimatorSet.play(mCrossAnim).after(mCircleRadiusAnimator).with(mScaleAnimator);
mAnimatorSet.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//繪製向圓心縮小的圓
if (mCircleRadiusAnimator != null && mCircleRadiusAnimator.isRunning()) {
drawBackground(canvas, mLoadingBarColor);
mPaintFill.setColor(mTickOrCrossColor);
canvas.drawCircle(getWidth() / 2f, getHeight() / 2f,
getWidth() / 2f - (float) mCircleRadiusAnimator.getAnimatedValue(), mPaintFill);
invalidate();
}
//繪製叉
if (mCrossAnim != null && mCrossAnim.isRunning()) {
drawBackground(canvas, mLoadingBarColor);
mPaintStroke.setAlpha((int) mCrossAnim.getAnimatedValue());
mPaintStroke.setColor(mTickOrCrossColor);
canvas.drawLine(mCrossPoint[0].x, mCrossPoint[0].y, mCrossPoint[2].x, mCrossPoint[2].y, mPaintStroke);
canvas.drawLine(mCrossPoint[1].x, mCrossPoint[1].y, mCrossPoint[3].x, mCrossPoint[3].y, mPaintStroke);
invalidate();
}
}
囉嗦完畢,歡迎交流指教哦。