自定義View_手擼一個啟動頁倒數計時View
在一個APP啟動的時候呢,一般經常見到倒數計時3秒或幾秒的場景,在這個場景中,也經常看到一個有動畫載入的view,比如下面今天要實現的效果圖:放個GitHub傳送門先:CountDownView
分析
正所謂知己知彼百戰百勝,所以我們每去做一件事情之前都要去花費一定的時間去了解一些相關的東西。那麼這樣的一個效果呢其實不難,我們只需兩個東西即可實現。——canvas和屬性動畫。
1.自定義我們需要的屬性:
那麼為了考慮擴充套件性,那麼有些屬性呢我們不能寫死,自定義屬性是最好的選擇!首先在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" />
<!--動畫執行時長-->
<attr name="cd_animator_time" format="integer" />
<!--時間單位-->
<attr name="cd_animator_time_unit" format="string" />
<!--動畫進退方式-->
<attr name="cd_retreat_type" format="enum">
<!--外層的圓弧逐漸變長-->
<enum name="forward" value="1" />
<!--外層的圓弧逐漸減短-->
<enum name="back" value="2" />
</attr>
<!--載入進度的開始位置-->
<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中獲取並設定這些屬性:
首先,來宣告和獲取定義好的屬性:
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;//時間,單位秒
private String mLoadingTimeUnit = "";//時間單位
private int mTextColor = Color.BLACK;//字型顏色
private int mTextSize;//字型大小
private int location;//從哪個位置開始
private float startAngle;//開始角度
private float mmSweepAngleStart;//起點
private float mmSweepAngleEnd;//終點
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));//預設25dp
mPaintArcWidth = array.getDimension(R.styleable.CountDownView_cd_arc_width, dip2px(context, 3));//預設3dp
mPaintArcColor = array.getColor(R.styleable.CountDownView_cd_arc_color, mPaintArcColor);
mTextSize = (int) array.getDimension(R.styleable.CountDownView_cd_text_size, dip2px(context, 14));//預設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);//預設3秒
mLoadingTimeUnit = array.getString(R.styleable.CountDownView_cd_animator_time_unit);//時間單位
if (TextUtils.isEmpty(mLoadingTimeUnit)) {
mLoadingTimeUnit = "";
}
array.recycle();
初始化畫筆等操作:
private void init() {
//背景設為透明,然後造成Views是圓形視覺錯覺
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) {//預設從左側開始
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()方法,不瞭解這個方法是什麼意思的請跳至此處檢視詳細解釋~drawArc()方法詳細介紹
//畫北景園
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.改變屬性值,重新繪製;
這一步就是關於屬性動畫的知識了。
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();
}
});
//這裡是時間獲取和賦值
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.介面回撥。
這一步就很簡單了,只要監聽動畫執行結束就是完成了載入,所以我們先來寫一個介面。
private OnLoadingFinishListener loadingFinishListener;
public void setOnLoadingFinishListener(OnLoadingFinishListener listener) {
this.loadingFinishListener = listener;
}
public interface OnLoadingFinishListener {
void finish();
}
在對應的Activity中回撥介面就可以了。
OK,到這裡就算全部結束了,下面我把原始碼放進來。
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.
* 啟動頁停留n秒動畫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;//時間,單位秒
private String mLoadingTimeUnit = "";//時間單位
private int mTextColor = Color.BLACK;//字型顏色
private int mTextSize;//字型大小
private int location;//從哪個位置開始
private float startAngle;//開始角度
private float mmSweepAngleStart;//起點
private float mmSweepAngleEnd;//終點
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));//預設25dp
mPaintArcWidth = array.getDimension(R.styleable.CountDownView_cd_arc_width, dip2px(context, 3));//預設3dp
mPaintArcColor = array.getColor(R.styleable.CountDownView_cd_arc_color, mPaintArcColor);
mTextSize = (int) array.getDimension(R.styleable.CountDownView_cd_text_size, dip2px(context, 14));//預設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);//預設3秒
mLoadingTimeUnit = array.getString(R.styleable.CountDownView_cd_animator_time_unit);//時間單位
if (TextUtils.isEmpty(mLoadingTimeUnit)) {
mLoadingTimeUnit = "";
}
array.recycle();
init();
}
private void init() {
//背景設為透明,然後造成圓形View的視覺錯覺
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) {//預設從左側開始
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);
//因為必須是圓形的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();
}
});
//這裡是時間獲取和賦值
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();
}
/**
* 根據手機的解析度從 dp 的單位 轉成為 px(畫素)
*/
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
我把這個view封裝成了library和上傳,用得到的朋友可以直接線上依賴到自己的專案中,至於如何整合和使用,請看GitHub:CountDownView
好的,至此全部結束,感謝!!!
相關文章
- 自定義View( 啟動頁倒數計時)View
- 教你玩轉自定義View—手擼一個倒數計時控制元件如此簡單View控制元件
- Android View篇之啟動頁倒數計時動畫的實現AndroidView動畫
- 微信小程式之自定義倒數計時元件微信小程式元件
- Android自定義view系列:手擼一個帶點兒科技感的儀表盤!AndroidView
- 線上直播系統原始碼,預設倒數計時,自定義輸入時間倒數計時原始碼
- XAF自定義啟動頁
- 倒數計時一定時間跳轉到另一個頁面
- 自定義控制元件實踐-倒數計時控制元件控制元件
- [Unity]記一個倒數計時介面Unity
- 如何寫好一個自定義ViewView
- js cookie 頁面倒數計時JSCookie
- Android自定義view實現數字時鐘AndroidView
- JavaScript 倒數計時關閉頁面JavaScript
- 直播系統app原始碼,自定義可以暫停的倒數計時APP原始碼
- android自定義view(自定義數字鍵盤)AndroidView
- js自動倒數計時程式碼,倒數計時完畢時自動停止迴圈JS
- 修改原始碼,自定義chromium啟動頁原始碼
- 搶購倒數計時自定義控制元件的實現與優化控制元件優化
- 手寫一個自定義PromisePromise
- 自定義View-扭曲動效View
- 自定義View-波浪動效View
- 自定義View合輯(1)-時鐘View
- 自定義VIEWView
- 如何使用原生技術寫一個倒數計時時鐘
- 動手擼一個微信小程式學生課程表頁面微信小程式
- 搶購倒數計時自定義控制元件的實現與最佳化控制元件
- RxJava:自己動手擼一個RxBinding(一)。RxJava
- Jquery 實現頁面倒數計時的功能jQuery
- JavaScript倒數計時JavaScript
- js——倒數計時JS
- JS倒數計時JS
- 【自定義View】抖音網紅文字時鐘-上篇View
- win10 自帶倒數計時如何設定_win10開啟計時器倒數計時的方法Win10
- iOS倒數計時設計思路和一個系統時間的坑iOS
- 自定義View公式View公式
- 網頁倒數計時跳轉程式碼例項網頁
- 防手機鎖屏解鎖自定義ViewView