自定義View_仿PostMan加載效果

不知道大家有沒有使用過PostMan來調(diào)試接口辙培,剛打開postman的時候有有一個加載效果,覺得還不錯也不難邢锯,于是就趁著今天有時間練了一把手扬蕊。來看看效果圖吧!

postmanloading.gif

這個效果很簡單丹擎,只要canvas的drawCircle方法和一個屬性動畫的知識即可完成厨相。照例先放個GitHub傳送門:PostManLoading

總體思路


1.畫圓,最里面的實(shí)心圓鸥鹉;
2.再畫圓,外面的三條空心圓庶骄;
3.畫三個移動的小球毁渗。

既然是練手項目,在項目中也不大可能會用到单刁,我就不寫的那么詳細(xì)了~
由于我們要做的是一個方形區(qū)域的效果灸异,所以重寫onMeasure方法把寬高重置為相等,這個不多說了羔飞。

1.畫圓肺樟,最里面的實(shí)心圓:


首先呢,先定義一個半徑和空心圓之間的間隔逻淌,和初始化一些畫筆的操作

private Paint mPaintCircle;
private Paint mPaintPoint;
private int mRandius = 40;
private int mInterval = 50;//空心圓之間的間隔

//初始化操作
mPaintCircle = new Paint();
mPaintCircle.setAntiAlias(true);
mPaintCircle.setStyle(Paint.Style.STROKE);
mPaintCircle.setStrokeWidth(3);
mPaintCircle.setColor(Color.RED);
mPaintPoint = new Paint();
mPaintPoint.setAntiAlias(true);
mPaintPoint.setStyle(Paint.Style.FILL);
mPaintPoint.setColor(Color.RED);

然后要干嘛來著~么伯,畫圓呀!

int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
//繪制實(shí)心圓
canvas.drawCircle(centerX, centerY, mRandius, mPaintPoint);

2.再畫圓卡儒,外面的三條空心圓田柔;

//繪制空心圓俐巴,我們以相等的間隔來依次畫出
canvas.drawCircle(centerX, centerY, mRandius + mInterval, mPaintCircle);
canvas.drawCircle(centerX, centerY, mRandius + mInterval + mInterval, mPaintCircle);
canvas.drawCircle(centerX, centerY, mRandius + mInterval + mInterval + mInterval, mPaintCircle);

3.畫三個移動的小球。

這里是此次博客的核心~
首先硬爆,我們分析滾動小球的圓心是在空心圓的軌跡上不停地變化著欣舵,但是半徑不變,我們暫且定為20個像素缀磕。只要我們在屬性動畫中不停地獲取空心圓上軌跡的每一個坐標(biāo)點(diǎn)然后賦值給小球就可以了缘圈。開始了~我們先定義圓心坐標(biāo):

//第一個小圓圓心坐標(biāo)
private float X1;
private float Y1;
//第二個小圓圓心坐標(biāo)
private float X2;
private float Y2;
//第三個小圓圓心坐標(biāo)
private float X3;
private float Y3;

然后開始寫繪制代碼,至于代碼中的坐標(biāo)為什么要加centerX 和centerY 這個等你看完就知道了袜蚕,別急~

//繪制滾動的小圓球
canvas.drawCircle(centerX + X1, centerY + Y1, 20, mPaintPoint);
canvas.drawCircle(centerX + X2, centerY + Y2, 20, mPaintPoint);
canvas.drawCircle(centerX + X3, centerY + Y3, 20, mPaintPoint);

那么我們?nèi)绾蝸慝@取空心圓上的每一個點(diǎn)的坐標(biāo)呢糟把?不如畫個草圖吧~

草圖.png

看圖,我們要求的就是小紅球的圓心坐標(biāo)也就是空心圓的軌跡上的每一點(diǎn)的坐標(biāo)值廷没。
deg是對應(yīng)整個PostManLoadingView的中心的角度值糊饱,這個是從0~360°時刻變化著的,既然知道角度颠黎,大的空心圓半徑R也是已知另锋,那么要求途中的X和Y值就很簡單了,一個三角函數(shù)就可以:

草圖.png

好的狭归,到了這里應(yīng)該就有人會想到用Math類來求正弦函數(shù)和余弦函數(shù)了~
但是Math.sin(rad)和Math.cos(rad)方法中需要傳入的參數(shù)是指弧度rad而不是角度deg夭坪,所以這里需要把角度值轉(zhuǎn)化成弧度制再調(diào)用Math函數(shù)。這里再放一個轉(zhuǎn)化公式~具體詳細(xì)的解釋請看Android角度與弧度

角度和弧度公式.png

接下來就容易了~

草圖.png

到了這里再來看我們上面留的那個問題就知道為什么了~求出X之后过椎,那么小紅球圓心坐標(biāo)最終的X值是多少呢室梅?很明顯就是centerX +X芥被。好的喜命,繼續(xù)往下走開始擼代碼!這里我用一個屬性動畫ValueAnimator傳入0-360代表角度稳强,進(jìn)而求出每一個度數(shù)時的X值和Y值敷待,不說了间涵,看代碼

ValueAnimator animator = ValueAnimator.ofFloat(0, 360);
animator.setDuration(mRandius * 30);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setRepeatMode(ValueAnimator.RESTART);
animator.setInterpolator(new LinearInterpolator());

然后去監(jiān)聽這個動畫獲取每一個角度值~

animator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator valueAnimator) {
        float deg = (float) valueAnimator.getAnimatedValue();
        float rad = (float) (Math.PI * deg / 180);
        Y1 = (float) (Math.sin(rad) * (mRandius + mInterval));
        X1 = (float) (Math.cos(rad) * (mRandius + mInterval));
    }
});

好的,到了這里基本上就沒有什么東西了榜揖,由于考慮到三個小球轉(zhuǎn)動的速度不一樣勾哩,所以我們就把每隔小球所執(zhí)行動畫的時間設(shè)置不一樣就可以了~,好的Over举哟!
下面上全家福~

PostManLoadingView.java

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;

/**
 * Created by zhuyong on 2017/7/20.
 */

public class PostManLoadingView extends View {

    private Paint mPaintCircle;
    private Paint mPaintPoint;
    private int mRandius = 40;
    private int mInterval = 50;//空心圓之間的間隔
    //第一個小圓圓心坐標(biāo)
    private float X1;
    private float Y1;
    //第二個小圓圓心坐標(biāo)
    private float X2;
    private float Y2;
    //第三個小圓圓心坐標(biāo)
    private float X3;
    private float Y3;

    public PostManLoadingView(Context context) {
        this(context, null);
    }

    public PostManLoadingView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PostManLoadingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        mPaintCircle = new Paint();
        mPaintCircle.setAntiAlias(true);
        mPaintCircle.setStyle(Paint.Style.STROKE);
        mPaintCircle.setStrokeWidth(3);
        mPaintCircle.setColor(Color.RED);

        mPaintPoint = new Paint();
        mPaintPoint.setAntiAlias(true);
        mPaintPoint.setStyle(Paint.Style.FILL);
        mPaintPoint.setColor(Color.RED);

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int mWidth = MeasureSpec.getSize(widthMeasureSpec);
        setMeasuredDimension(mWidth, mWidth);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int centerX = getWidth() / 2;
        int centerY = getHeight() / 2;
        //繪制實(shí)心圓
        canvas.drawCircle(centerX, centerY, mRandius, mPaintPoint);
        //繪制空心圓
        canvas.drawCircle(centerX, centerY, mRandius + mInterval, mPaintCircle);
        canvas.drawCircle(centerX, centerY, mRandius + mInterval + mInterval, mPaintCircle);
        canvas.drawCircle(centerX, centerY, mRandius + mInterval + mInterval + mInterval, mPaintCircle);
        //繪制滾動的小圓球
        canvas.drawCircle(centerX + X1, centerY + Y1, 20, mPaintPoint);
        canvas.drawCircle(centerX + X2, centerY + Y2, 20, mPaintPoint);
        canvas.drawCircle(centerX + X3, centerY + Y3, 20, mPaintPoint);

    }


    public void start() {

        ValueAnimator animator1 = getValueAnimator(mRandius + mInterval);
        animator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float deg = (float) valueAnimator.getAnimatedValue();
                float rad = (float) (Math.PI * deg / 180);
                Y1 = (float) (Math.sin(rad) * (mRandius + mInterval));
                X1 = (float) (Math.cos(rad) * (mRandius + mInterval));
            }
        });
        animator1.start();
        ValueAnimator animator2 = getValueAnimator(mRandius + mInterval + mInterval);
        animator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float deg = (float) valueAnimator.getAnimatedValue();
                float rad = (float) (Math.PI * deg / 180);
                Y2 = (float) (Math.sin(rad) * (mRandius + mInterval + mInterval));
                X2 = (float) (Math.cos(rad) * (mRandius + mInterval + mInterval));
            }
        });
        animator2.start();
        ValueAnimator animator3 = getValueAnimator(mRandius + mInterval + mInterval + mInterval);
        animator3.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float deg = (float) valueAnimator.getAnimatedValue();
                float rad = (float) (Math.PI * deg / 180);

                Y3 = (float) (Math.sin(rad) * (mRandius + mInterval + mInterval + mInterval));
                X3 = (float) (Math.cos(rad) * (mRandius + mInterval + mInterval + mInterval));
                invalidate();
            }
        });
        animator3.start();
    }

    public ValueAnimator getValueAnimator(final int mRandius) {
        ValueAnimator animator = ValueAnimator.ofFloat(0, 360);
        animator.setDuration(mRandius * 30);//這里動畫時間只是隨便寫的思劳,可以自己設(shè)置
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.setRepeatMode(ValueAnimator.RESTART);
        animator.setInterpolator(new LinearInterpolator());
        return animator;
    }

}

xml布局和Activity很簡單我就不放了,最后只要調(diào)start()方法就可以了~

GitHub傳送門:源碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末妨猩,一起剝皮案震驚了整個濱河市潜叛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌壶硅,老刑警劉巖钠导,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件震嫉,死亡現(xiàn)場離奇詭異,居然都是意外死亡牡属,警方通過查閱死者的電腦和手機(jī)票堵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來逮栅,“玉大人悴势,你說我怎么就攤上這事〈敕ィ” “怎么了特纤?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長侥加。 經(jīng)常有香客問我捧存,道長,這世上最難降的妖魔是什么担败? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任昔穴,我火速辦了婚禮,結(jié)果婚禮上提前,老公的妹妹穿的比我還像新娘吗货。我一直安慰自己,他們只是感情好狈网,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布宙搬。 她就那樣靜靜地躺著,像睡著了一般拓哺。 火紅的嫁衣襯著肌膚如雪勇垛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天士鸥,我揣著相機(jī)與錄音窥摄,去河邊找鬼。 笑死础淤,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的哨苛。 我是一名探鬼主播鸽凶,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼建峭!你這毒婦竟也來了玻侥?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤亿蒸,失蹤者是張志新(化名)和其女友劉穎凑兰,沒想到半個月后掌桩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡姑食,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年波岛,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片音半。...
    茶點(diǎn)故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡则拷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出曹鸠,到底是詐尸還是另有隱情煌茬,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布彻桃,位于F島的核電站坛善,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏邻眷。R本人自食惡果不足惜眠屎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望耗溜。 院中可真熱鬧组力,春花似錦、人聲如沸抖拴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽阿宅。三九已至候衍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間洒放,已是汗流浹背蛉鹿。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留往湿,地道東北人妖异。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像领追,于是被迫代替她去往敵國和親他膳。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評論 2 355

推薦閱讀更多精彩內(nèi)容