自定義View_手?jǐn)]一個(gè)啟動(dòng)頁倒計(jì)時(shí)View

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

countdownview.gif

分析


正所謂知己知彼百戰(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é)束,感謝?。?!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,942評(píng)論 25 709
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,195評(píng)論 4 61
  • 國內(nèi)自定義View的文章汗牛充棟,但是,即便是你全部看完也未必掌握這一知識(shí)(實(shí)際上,我也看了很多,但是一旦涉及自定...
    SnowDragonYY閱讀 1,716評(píng)論 3 36
  • 有人的地方就是江湖,年輕的時(shí)候總心懷一個(gè)江湖夢,以為自己有朝一日文武雙全,能仗劍走天涯。 乘物以游心 古龍小...
    學(xué)做靈魂有香氣的女子閱讀 476評(píng)論 0 3
  • 何為隱士?天生不需要借助外界的力量而達(dá)到自家身心的完美平衡的人。他們是自然之子是天生的哲人,他們親近和陶醉于自然造...
    春花的無憂雜貨鋪閱讀 2,233評(píng)論 1 0

友情鏈接更多精彩內(nèi)容