在一個(gè)APP啟動(dòng)的時(shí)候呢,一般經(jīng)常見到倒計(jì)時(shí)3秒或幾秒的場景,在這個(gè)場景中,也經(jīng)常看到一個(gè)有動(dòng)畫加載的view,比如下面今天要實(shí)現(xiàn)的效果圖:放個(gè)GitHub傳送門先:CountDownView

分析
正所謂知己知彼百戰(zhàn)百勝,所以我們每去做一件事情之前都要去花費(fèi)一定的時(shí)間去了解一些相關(guān)的東西。那么這樣的一個(gè)效果呢其實(shí)不難,我們只需兩個(gè)東西即可實(shí)現(xiàn)。——canvas和屬性動(dòng)畫。
1.自定義我們需要的屬性:
那么為了考慮擴(kuò)展性,那么有些屬性呢我們不能寫死,自定義屬性是最好的選擇!首先在values文件夾下新建文件attrs.xml
<declare-styleable name="CountDownView">
<!--view半徑-->
<attr name="cd_circle_radius" format="dimension" />
<!--畫筆寬度-->
<attr name="cd_arc_width" format="dimension" />
<!--畫筆顏色-->
<attr name="cd_arc_color" format="color" />
<!--背景顏色-->
<attr name="cd_bg_color" format="color" />
<!--字體顏色-->
<attr name="cd_text_color" format="color" />
<!--字體尺寸-->
<attr name="cd_text_size" format="dimension" />
<!--動(dòng)畫執(zhí)行時(shí)長-->
<attr name="cd_animator_time" format="integer" />
<!--時(shí)間單位-->
<attr name="cd_animator_time_unit" format="string" />
<!--動(dòng)畫進(jìn)退方式-->
<attr name="cd_retreat_type" format="enum">
<!--外層的圓弧逐漸變長-->
<enum name="forward" value="1" />
<!--外層的圓弧逐漸減短-->
<enum name="back" value="2" />
</attr>
<!--加載進(jìn)度的開始位置-->
<attr name="cd_location" format="enum">
<enum name="left" value="1" />
<enum name="top" value="2" />
<enum name="right" value="3" />
<enum name="bottom" value="4" />
</attr>
</declare-styleable>
然后在自定義View中獲取并設(shè)置這些屬性:
首先,來聲明和獲取定義好的屬性:
private Paint mPaintBackGround;//背景畫筆
private Paint mPaintArc;//圓弧畫筆
private Paint mPaintText;//文字畫筆
private int mRetreatType;//圓弧繪制方式(增加和減少)
private float mPaintArcWidth;//最外層圓弧的寬度
private int mCircleRadius;//圓圈的半徑
private int mPaintArcColor = Color.parseColor("#3C3F41");//初始值
private int mPaintBackGroundColor = Color.parseColor("#55B2E5");//初始值
private int mLoadingTime;//時(shí)間,單位秒
private String mLoadingTimeUnit = "";//時(shí)間單位
private int mTextColor = Color.BLACK;//字體顏色
private int mTextSize;//字體大小
private int location;//從哪個(gè)位置開始
private float startAngle;//開始角度
private float mmSweepAngleStart;//起點(diǎn)
private float mmSweepAngleEnd;//終點(diǎn)
private float mSweepAngle;//掃過的角度
private String mText = "";//要繪制的文字
獲取這些屬性值:
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CountDownView);
mRetreatType = array.getInt(R.styleable.CountDownView_cd_retreat_type, 1);
location = array.getInt(R.styleable.CountDownView_cd_location, 1);
mCircleRadius = (int) array.getDimension(R.styleable.CountDownView_cd_circle_radius, dip2px(context, 25));//默認(rèn)25dp
mPaintArcWidth = array.getDimension(R.styleable.CountDownView_cd_arc_width, dip2px(context, 3));//默認(rèn)3dp
mPaintArcColor = array.getColor(R.styleable.CountDownView_cd_arc_color, mPaintArcColor);
mTextSize = (int) array.getDimension(R.styleable.CountDownView_cd_text_size, dip2px(context, 14));//默認(rèn)14sp
mTextColor = array.getColor(R.styleable.CountDownView_cd_text_color, mTextColor);
mPaintBackGroundColor = array.getColor(R.styleable.CountDownView_cd_bg_color, mPaintBackGroundColor);
mLoadingTime = array.getInteger(R.styleable.CountDownView_cd_animator_time, 3);//默認(rèn)3秒
mLoadingTimeUnit = array.getString(R.styleable.CountDownView_cd_animator_time_unit);//時(shí)間單位
if (TextUtils.isEmpty(mLoadingTimeUnit)) {
mLoadingTimeUnit = "";
}
array.recycle();
初始化畫筆等操作:
private void init() {
//背景設(shè)為透明,然后造成Views是圓形視覺錯(cuò)覺
this.setBackground(ContextCompat.getDrawable(mContext, android.R.color.transparent));
mPaintBackGround = new Paint();
mPaintBackGround.setStyle(Paint.Style.FILL);
mPaintBackGround.setAntiAlias(true);
mPaintBackGround.setColor(mPaintBackGroundColor);
mPaintArc = new Paint();
mPaintArc.setStyle(Paint.Style.STROKE);
mPaintArc.setAntiAlias(true);
mPaintArc.setColor(mPaintArcColor);
mPaintArc.setStrokeWidth(mPaintArcWidth);
mPaintText = new Paint();
mPaintText.setStyle(Paint.Style.STROKE);
mPaintText.setAntiAlias(true);
mPaintText.setColor(mTextColor);
mPaintText.setTextSize(mTextSize);
if (mLoadingTime < 0) {
mLoadingTime = 3;
}
if (location == 1) {//默認(rèn)從左側(cè)開始
startAngle = -180;
} else if (location == 2) {
startAngle = -90;
} else if (location == 3) {
startAngle = 0;
} else if (location == 4) {
startAngle = 90;
}
if (mRetreatType == 1) {
mmSweepAngleStart = 0f;
mmSweepAngleEnd = 360f;
} else {
mmSweepAngleStart = 360f;
mmSweepAngleEnd = 0f;
}
}
2.畫出需要的效果:畫圓弧,畫字體,畫背景:
這里我們使用cancas的drawArc()方法,不了解這個(gè)方法是什么意思的請(qǐng)?zhí)链颂幉榭丛敿?xì)解釋~drawArc()方法詳細(xì)介紹
//畫北景園
canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2 - mPaintArcWidth, mPaintBackGround);
//畫圓弧
RectF rectF = new RectF(0 + mPaintArcWidth / 2, 0 + mPaintArcWidth / 2
, mWidth - mPaintArcWidth / 2, mHeight - mPaintArcWidth / 2);
canvas.drawArc(rectF, startAngle, mSweepAngle, false, mPaintArc);
//畫文字
float mTetxWidth = mPaintText.measureText(mText, 0, mText.length());
float dx = mWidth / 2 - mTetxWidth / 2;
Paint.FontMetricsInt fontMetricsInt = mPaintText.getFontMetricsInt();
float dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
float baseLine = mHeight / 2 + dy;
canvas.drawText(mText, dx, baseLine, mPaintText);
3.改變屬性值,重新繪制;
這一步就是關(guān)于屬性動(dòng)畫的知識(shí)了。
public void start() {
ValueAnimator animator = ValueAnimator.ofFloat(mmSweepAngleStart, mmSweepAngleEnd);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mSweepAngle = (float) valueAnimator.getAnimatedValue();
//獲取到需要繪制的角度,重新繪制
invalidate();
}
});
//這里是時(shí)間獲取和賦值
ValueAnimator animator1 = ValueAnimator.ofInt(mLoadingTime, 0);
animator1.setInterpolator(new LinearInterpolator());
animator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int time = (int) valueAnimator.getAnimatedValue();
mText = time + mLoadingTimeUnit;
}
});
AnimatorSet set = new AnimatorSet();
set.playTogether(animator, animator1);
set.setDuration(mLoadingTime * 1000);
set.setInterpolator(new LinearInterpolator());
set.start();
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
clearAnimation();
if (loadingFinishListener != null) {
loadingFinishListener.finish();
}
}
});
}
4.接口回調(diào)。
這一步就很簡單了,只要監(jiān)聽動(dòng)畫執(zhí)行結(jié)束就是完成了加載,所以我們先來寫一個(gè)接口。
private OnLoadingFinishListener loadingFinishListener;
public void setOnLoadingFinishListener(OnLoadingFinishListener listener) {
this.loadingFinishListener = listener;
}
public interface OnLoadingFinishListener {
void finish();
}
在對(duì)應(yīng)的Activity中回調(diào)接口就可以了。
OK,到這里就算全部結(jié)束了,下面我把源碼放進(jìn)來。
CountDownView.java:
package com.zhuyong.countdownciew;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;
import com.zhuyong.counttimeview.R;
/**
* Created by zhuyong on 2017/8/7.
* 啟動(dòng)頁停留n秒動(dòng)畫view
*/
public class CountDownView extends View {
private Context mContext;//上下文
private Paint mPaintBackGround;//背景畫筆
private Paint mPaintArc;//圓弧畫筆
private Paint mPaintText;//文字畫筆
private int mRetreatType;//圓弧繪制方式(增加和減少)
private float mPaintArcWidth;//最外層圓弧的寬度
private int mCircleRadius;//圓圈的半徑
private int mPaintArcColor = Color.parseColor("#3C3F41");//初始值
private int mPaintBackGroundColor = Color.parseColor("#55B2E5");//初始值
private int mLoadingTime;//時(shí)間,單位秒
private String mLoadingTimeUnit = "";//時(shí)間單位
private int mTextColor = Color.BLACK;//字體顏色
private int mTextSize;//字體大小
private int location;//從哪個(gè)位置開始
private float startAngle;//開始角度
private float mmSweepAngleStart;//起點(diǎn)
private float mmSweepAngleEnd;//終點(diǎn)
private float mSweepAngle;//掃過的角度
private String mText = "";//要繪制的文字
private int mWidth;
private int mHeight;
public CountDownView(Context context) {
this(context, null);
}
public CountDownView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CountDownView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CountDownView);
mRetreatType = array.getInt(R.styleable.CountDownView_cd_retreat_type, 1);
location = array.getInt(R.styleable.CountDownView_cd_location, 1);
mCircleRadius = (int) array.getDimension(R.styleable.CountDownView_cd_circle_radius, dip2px(context, 25));//默認(rèn)25dp
mPaintArcWidth = array.getDimension(R.styleable.CountDownView_cd_arc_width, dip2px(context, 3));//默認(rèn)3dp
mPaintArcColor = array.getColor(R.styleable.CountDownView_cd_arc_color, mPaintArcColor);
mTextSize = (int) array.getDimension(R.styleable.CountDownView_cd_text_size, dip2px(context, 14));//默認(rèn)14sp
mTextColor = array.getColor(R.styleable.CountDownView_cd_text_color, mTextColor);
mPaintBackGroundColor = array.getColor(R.styleable.CountDownView_cd_bg_color, mPaintBackGroundColor);
mLoadingTime = array.getInteger(R.styleable.CountDownView_cd_animator_time, 3);//默認(rèn)3秒
mLoadingTimeUnit = array.getString(R.styleable.CountDownView_cd_animator_time_unit);//時(shí)間單位
if (TextUtils.isEmpty(mLoadingTimeUnit)) {
mLoadingTimeUnit = "";
}
array.recycle();
init();
}
private void init() {
//背景設(shè)為透明,然后造成圓形View的視覺錯(cuò)覺
this.setBackground(ContextCompat.getDrawable(mContext, android.R.color.transparent));
mPaintBackGround = new Paint();
mPaintBackGround.setStyle(Paint.Style.FILL);
mPaintBackGround.setAntiAlias(true);
mPaintBackGround.setColor(mPaintBackGroundColor);
mPaintArc = new Paint();
mPaintArc.setStyle(Paint.Style.STROKE);
mPaintArc.setAntiAlias(true);
mPaintArc.setColor(mPaintArcColor);
mPaintArc.setStrokeWidth(mPaintArcWidth);
mPaintText = new Paint();
mPaintText.setStyle(Paint.Style.STROKE);
mPaintText.setAntiAlias(true);
mPaintText.setColor(mTextColor);
mPaintText.setTextSize(mTextSize);
if (mLoadingTime < 0) {
mLoadingTime = 3;
}
if (location == 1) {//默認(rèn)從左側(cè)開始
startAngle = -180;
} else if (location == 2) {
startAngle = -90;
} else if (location == 3) {
startAngle = 0;
} else if (location == 4) {
startAngle = 90;
}
if (mRetreatType == 1) {
mmSweepAngleStart = 0f;
mmSweepAngleEnd = 360f;
} else {
mmSweepAngleStart = 360f;
mmSweepAngleEnd = 0f;
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//獲取view寬高
mWidth = w;
mHeight = h;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//因?yàn)楸仨毷菆A形的view,所以在這里重新賦值
setMeasuredDimension(mCircleRadius * 2, mCircleRadius * 2);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//畫北景園
canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2 - mPaintArcWidth, mPaintBackGround);
//畫圓弧
RectF rectF = new RectF(0 + mPaintArcWidth / 2, 0 + mPaintArcWidth / 2
, mWidth - mPaintArcWidth / 2, mHeight - mPaintArcWidth / 2);
canvas.drawArc(rectF, startAngle, mSweepAngle, false, mPaintArc);
//畫文字
float mTetxWidth = mPaintText.measureText(mText, 0, mText.length());
float dx = mWidth / 2 - mTetxWidth / 2;
Paint.FontMetricsInt fontMetricsInt = mPaintText.getFontMetricsInt();
float dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
float baseLine = mHeight / 2 + dy;
canvas.drawText(mText, dx, baseLine, mPaintText);
}
public void start() {
ValueAnimator animator = ValueAnimator.ofFloat(mmSweepAngleStart, mmSweepAngleEnd);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mSweepAngle = (float) valueAnimator.getAnimatedValue();
//獲取到需要繪制的角度,重新繪制
invalidate();
}
});
//這里是時(shí)間獲取和賦值
ValueAnimator animator1 = ValueAnimator.ofInt(mLoadingTime, 0);
animator1.setInterpolator(new LinearInterpolator());
animator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int time = (int) valueAnimator.getAnimatedValue();
mText = time + mLoadingTimeUnit;
}
});
AnimatorSet set = new AnimatorSet();
set.playTogether(animator, animator1);
set.setDuration(mLoadingTime * 1000);
set.setInterpolator(new LinearInterpolator());
set.start();
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
clearAnimation();
if (loadingFinishListener != null) {
loadingFinishListener.finish();
}
}
});
}
private OnLoadingFinishListener loadingFinishListener;
public void setOnLoadingFinishListener(OnLoadingFinishListener listener) {
this.loadingFinishListener = listener;
}
public interface OnLoadingFinishListener {
void finish();
}
/**
* 根據(jù)手機(jī)的分辨率從 dp 的單位 轉(zhuǎn)成為 px(像素)
*/
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
我把這個(gè)view封裝成了library和上傳,用得到的朋友可以直接在線依賴到自己的項(xiàng)目中,至于如何集成和使用,請(qǐng)看GitHub:CountDownView
好的,至此全部結(jié)束,感謝?。?!