一個非常漂亮的自定義Loading倾芝,有加載成功和失敗兩種狀態(tài)讨勤。

這還只是張圖片

本文原創(chuàng),這篇可不能匿名轉(zhuǎn)載晨另。

背景:我一哥們公司做智能設(shè)備的潭千,該動畫用在手機和家中網(wǎng)絡(luò)連接時用,他讓我看了下需求借尿。剛看到這動畫時感覺產(chǎn)品\UI設(shè)計的不錯刨晴,想著試試。昨天開始做的路翻,本來感覺很簡單狈癞,但做起來貌似沒那么簡單;最后花了近一天時間終于搞定了茂契〉埃看看效果還行!

niceloading.gif
  • 如果有想直接用的同道中人掉冶,看前半部分就行真竖;如果想批評指正我的思考的看看后半部分

1.直接上代碼(NiceLoadingView)

package com.hadisi.niceloading;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;

/**
 * Created by hadisi5216 on 2016/7/12.
 */

public class NiceLoadingView extends View {

    private Context mContext;
    private Paint mPaint;

    private int widthSpecSize;
    private int heightSpecSize;
    private int radiusSmall = 38;
    private int radiusbig = 76;
    private int moveX;
    private int XPoint;

    private int mState = -1;//0失敗,1成功厌小,-1默認
    private boolean mflag;

    private ValueAnimator animator;

    public NiceLoadingView(Context context) {
        super(context);
    }

    public NiceLoadingView(Context context, AttributeSet attrs) {
        super(context, attrs);

    }

    public NiceLoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        mPaint = new Paint();
    }

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

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setColor(0xFFFFBC53);
        mPaint.setAntiAlias(true);
        if (Math.abs(moveX) > widthSpecSize * 5 / 4) {
            XPoint = (moveX < 0) ? XPoint = widthSpecSize * 7 / 4 - Math.abs(moveX) : widthSpecSize - widthSpecSize * 7 / 4 + Math.abs(moveX);
            canvas.drawCircle(XPoint, heightSpecSize / 2, radiusSmall, mPaint);
        }
        if (Math.abs(moveX) > widthSpecSize && Math.abs(moveX) < widthSpecSize * 3 / 2) {
            XPoint = (moveX < 0) ? XPoint = widthSpecSize * 3 / 2 - Math.abs(moveX) : widthSpecSize - widthSpecSize * 3 / 2 + Math.abs(moveX);
            canvas.drawCircle(XPoint, heightSpecSize / 2, radiusSmall, mPaint);
        }
        if (Math.abs(moveX) > widthSpecSize * 3 / 4 && Math.abs(moveX) < widthSpecSize * 5 / 4) {
            XPoint = (moveX < 0) ? XPoint = widthSpecSize * 5 / 4 - Math.abs(moveX) : widthSpecSize - widthSpecSize * 5 / 4 + Math.abs(moveX);
            canvas.drawCircle(XPoint, heightSpecSize / 2, radiusSmall, mPaint);
        }
        if (Math.abs(moveX) > widthSpecSize / 2 && Math.abs(moveX) < widthSpecSize) {
            XPoint = (moveX < 0) ? XPoint = widthSpecSize - Math.abs(moveX) : widthSpecSize - widthSpecSize + Math.abs(moveX);
            canvas.drawCircle(XPoint, heightSpecSize / 2, radiusSmall, mPaint);
        }
        if (Math.abs(moveX) > widthSpecSize / 4 && Math.abs(moveX) < widthSpecSize * 3 / 4) {
            XPoint = (moveX < 0) ? XPoint = widthSpecSize * 3 / 4 - Math.abs(moveX) : widthSpecSize - widthSpecSize * 3 / 4 + Math.abs(moveX);
            canvas.drawCircle(XPoint, heightSpecSize / 2, radiusSmall, mPaint);
        }
        if (Math.abs(moveX) > 0 && Math.abs(moveX) < widthSpecSize / 2) {
            XPoint = (moveX < 0) ? XPoint = widthSpecSize / 2 - Math.abs(moveX) : widthSpecSize - widthSpecSize / 2 + Math.abs(moveX);
            canvas.drawCircle(XPoint, heightSpecSize / 2, radiusSmall, mPaint);
        }
        //中間大圓
        if (Math.abs(moveX) > 0 && Math.abs(moveX) < widthSpecSize * 5 / 4) {
            radiusbig = 2 * radiusSmall - radiusSmall * (Math.abs(moveX)) / (widthSpecSize * 5 / 4);
            radiusbig = (radiusbig > radiusSmall) ? radiusbig : radiusSmall;
            canvas.drawCircle(widthSpecSize / 2, heightSpecSize / 2, radiusbig, mPaint);
        }
        if (Math.abs(moveX) < 12 && mState >= 0) {
            if (mState == 0) {
                canvas.drawCircle(widthSpecSize / 2, heightSpecSize / 2, radiusbig, mPaint);
                Bitmap bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.mipmap.connect_failed);
                canvas.drawBitmap(bitmap, null, new Rect(widthSpecSize / 2 - radiusbig, heightSpecSize / 2 - radiusbig, widthSpecSize / 2 + radiusbig, heightSpecSize / 2 + radiusbig), mPaint);
            }
            if (mState == 1) {
                canvas.drawCircle(widthSpecSize / 2, heightSpecSize / 2, radiusbig, mPaint);
                Bitmap bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.mipmap.connect_success);
                canvas.drawBitmap(bitmap, null, new Rect(widthSpecSize / 2 - radiusbig, heightSpecSize / 2 - radiusbig, widthSpecSize / 2 + radiusbig, heightSpecSize / 2 + radiusbig), mPaint);
            }
        }
    }

    public void start() {
        if (animator != null)
            animator.cancel();
        moveX = widthSpecSize * (-9 / 4);
        mState = -1;
        mflag = true;
        post(new Runnable() {
            @Override
            public void run() {
                animator = ValueAnimator.ofFloat(0f, 1.0f);
                animator.setRepeatMode(ValueAnimator.RESTART);
                animator.setRepeatCount(ValueAnimator.INFINITE);
                animator.setInterpolator(new LinearInterpolator());
                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        if (mState < 0) {
                            moveX = (moveX > widthSpecSize * 7 / 4) ? widthSpecSize * (-9 / 4) : moveX + 12;
                        } else {
                            if (moveX > 0)
                                moveX = (moveX > widthSpecSize * 7 / 4) ? widthSpecSize * (-9 / 4) : moveX + 12;
                            else if (moveX < 0 && mflag) {
                                moveX += 12;
                                if (Math.abs(moveX) < 12)
                                    mflag = false;
                            }
                        }
                        postInvalidate();
                    }
                });
                animator.start();
            }
        });
    }

    public void success() {
        mState = 1;
    }

    public void failed() {
        mState = 0;
    }
}

項目已上傳到github恢共,戳著

2.怎么用?

  • 布局文件中
<com.hadisi.niceloading.NiceLoadingView
            android:id="@+id/nice_loading"
            android:layout_width="match_parent"
            android:layout_height="100dp" />
  • 你要用的地方
NiceLoadingView niceLoading = (NiceLoadingView) findViewById(R.id.nice_loading);
……
//開始連接時
niceLoading.start();
……
//連接成功時
niceLoading.success();
……
//連接失敗時
niceLoading.failed();

3.我怎么實現(xiàn)的璧亚!

仔細看效果圖可以得出:
1讨韭、有6個小圓依次從屏幕左側(cè)移入屏幕中間,然后又依次從屏幕中間移出屏幕右側(cè)涨岁。
2拐袜、中間有個大圓在隨著小圓的依次靠近慢慢變大,離開慢慢變猩倚健蹬铺;注意在左側(cè)第1個小圓到達中間時才出現(xiàn)大圓,在最后一個小圓準備向右側(cè)移動時消失秉撇;大圓的半徑在小圓半徑和大圓半徑之間甜攀。
3秋泄、不管何時得到成功和失敗的狀態(tài),動畫終止時都是在小圓依次從左邊進入中間后规阀。
4恒序、動畫完成后顯示成功/失敗圖片和大圓。

  • 1. 6個小圓的運動
    我是這樣想的:當?shù)?個小圓移動到widthSpecSize/4(widthSpecSize 為控件的寬度)時第2個小圓開始移動谁撼、當?shù)?個小圓移動到widthSpecSize/4 時第3個小圓開始移動......當?shù)?個小圓移動到widthSpecSize/4 時第6個小圓開始移動歧胁、第6個小圓移動到widthSpecSize/2 時繼續(xù)移動、當?shù)?個小圓移動到widthSpecSize * 3/4時第5個小圓開始移動......當?shù)?個小圓移動到widthSpecSize * 3/4時第1個小圓開始移動厉碟、最后第1個小球移出屏幕右側(cè)喊巍,到此為一個循環(huán)。
    假設(shè)有一個位移變量moveX箍鼓,moveX在不斷增加崭参,其變化范圍為(a,b);可以看出按照我的想法款咖,第1個小圓在范圍的兩邊時開始移動何暮、第6個小圓在變化范圍的中間部分開始移動。
    我們可以繼續(xù)假設(shè)變化范圍為(-a,a)铐殃,這樣第1個小圓在范圍的絕對值大時開始移動海洼、第6個小圓在變化范圍的絕對值小時開始移動;其實這種重復(fù)的動作很容易想到絕對值控制
    找張紙畫下很容易得到moveX的變化范圍在(-widthSpecSize * 7/4 , widthSpecSize * 7/4)之間富腊。
自己畫的圖贰军,有點丑

對照圖很快可以得出下面代碼

if (Math.abs(moveX) > widthSpecSize * 5 / 4) {
    XPoint = (moveX < 0) ? XPoint = widthSpecSize * 7 / 4 - Math.abs(moveX) : widthSpecSize - widthSpecSize * 7 / 4 + Math.abs(moveX);
    canvas.drawCircle(XPoint, heightSpecSize / 2, radiusSmall, mPaint);
}
if (Math.abs(moveX) > widthSpecSize && Math.abs(moveX) < widthSpecSize * 3 / 2) {
    XPoint = (moveX < 0) ? XPoint = widthSpecSize * 3 / 2 - Math.abs(moveX) : widthSpecSize - widthSpecSize * 3 / 2 + Math.abs(moveX);
    canvas.drawCircle(XPoint, heightSpecSize / 2, radiusSmall, mPaint);
}
if (Math.abs(moveX) > widthSpecSize * 3 / 4 && Math.abs(moveX) < widthSpecSize * 5 / 4) {
    XPoint = (moveX < 0) ? XPoint = widthSpecSize * 5 / 4 - Math.abs(moveX) : widthSpecSize - widthSpecSize * 5 / 4 + Math.abs(moveX);
    canvas.drawCircle(XPoint, heightSpecSize / 2, radiusSmall, mPaint);
}
if (Math.abs(moveX) > widthSpecSize / 2 && Math.abs(moveX) < widthSpecSize) {
    XPoint = (moveX < 0) ? XPoint = widthSpecSize - Math.abs(moveX) : widthSpecSize - widthSpecSize + Math.abs(moveX);
    canvas.drawCircle(XPoint, heightSpecSize / 2, radiusSmall, mPaint);
}
if (Math.abs(moveX) > widthSpecSize / 4 && Math.abs(moveX) < widthSpecSize * 3 / 4) {
    XPoint = (moveX < 0) ? XPoint = widthSpecSize * 3 / 4 - Math.abs(moveX) : widthSpecSize - widthSpecSize * 3 / 4 + Math.abs(moveX);
    canvas.drawCircle(XPoint, heightSpecSize / 2, radiusSmall, mPaint);
}
if (Math.abs(moveX) > 0 && Math.abs(moveX) < widthSpecSize / 2) {
    XPoint = (moveX < 0) ? XPoint = widthSpecSize / 2 - Math.abs(moveX) : widthSpecSize - widthSpecSize / 2 + Math.abs(moveX);
    canvas.drawCircle(XPoint, heightSpecSize / 2, radiusSmall, mPaint);
}
  • 2. 中間大圓的運動
    中間變化的大圓在左側(cè)第1個小圓到達中間時才出現(xiàn)大圓,在第1個小圓準備向右側(cè)移動時消失蟹肘,變化范圍(-widthSpecSize * 5/4 , widthSpecSize * 5/4)之間。大圓的半徑在小圓半徑和大圓半徑之間俯树,我們用radiusbig = radiusbig - radiusSmall * (Math.abs(moveX)) / (widthSpecSize * 5/4)計算大圓半徑帘腹,可以得到慢慢變大和變小的效果,然后控制在小于radiusSmall時用radiusSmall许饿。
if (Math.abs(moveX) > 0 && Math.abs(moveX) < widthSpecSize * 5 / 4) {
    radiusbig = radiusbig - radiusSmall * (Math.abs(moveX)) / (widthSpecSize * 5 / 4);
    radiusbig = (radiusbig > radiusSmall) ? radiusbig : radiusSmall;
    canvas.drawCircle(widthSpecSize / 2, heightSpecSize / 2, radiusbig, mPaint);
}
  • 3. 動畫終止的控制
    正常當一個循環(huán)結(jié)束時我們需要重新給moveX賦值為widthSpecSize * (-7/4)阳欲,當收到成功或失敗狀態(tài)時需要判斷當前的狀態(tài),等到動畫進行到結(jié)束狀態(tài)(小圓依次從左邊進入中間后)陋率。見下面代碼球化,mState為當前狀態(tài)(0失敗,1成功瓦糟,-1默認)筒愚。
    我重新賦值時將moveX設(shè)為 * widthSpecSize * (-9/4)因為一個循環(huán)結(jié)束后有點停頓會感覺舒服點菩浙,這個無所謂巢掺,自己感覺而已*
if (mState < 0) {
    moveX = (moveX > widthSpecSize * 7 / 4) ? widthSpecSize * (-9 / 4) : moveX + 12;
} else {
    if (moveX > 0)
        moveX = (moveX > widthSpecSize * 7 / 4) ? widthSpecSize * (-9 / 4) : moveX + 12;
    else if (moveX < 0 && mflag) {
        moveX += 12;
        if (Math.abs(moveX) < 12)
            mflag = false;
    }
}
  • 4. 顯示成功/失敗圖片
    這個簡單句伶,在收到成功或失敗狀態(tài),待動畫完成時先畫一個大圓陆淀,再畫一個bitmap
if (Math.abs(moveX) < 12 && mState >= 0) {
            if (mState == 0) {
                canvas.drawCircle(widthSpecSize / 2, heightSpecSize / 2, radiusbig, mPaint);
                Bitmap bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.mipmap.connect_failed);
                canvas.drawBitmap(bitmap, null, new Rect(widthSpecSize / 2 - radiusbig, heightSpecSize / 2 - radiusbig, widthSpecSize / 2 + radiusbig, heightSpecSize / 2 + radiusbig), mPaint);
            }
            if (mState == 1) {
                canvas.drawCircle(widthSpecSize / 2, heightSpecSize / 2, radiusbig, mPaint);
                Bitmap bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.mipmap.connect_success);
                canvas.drawBitmap(bitmap, null, new Rect(widthSpecSize / 2 - radiusbig, heightSpecSize / 2 - radiusbig, widthSpecSize / 2 + radiusbig, heightSpecSize / 2 + radiusbig), mPaint);
            }
        }

4.優(yōu)化

  • 可以優(yōu)化考余,將paint的顏色等屬性、大小圓的半徑轧苫、優(yōu)化畫小圓的邏輯楚堤,使小圓個數(shù)可變等抽象出來..........

其實核心的就是想法,隨便怎么優(yōu)化含懊。反正我就弄到這了身冬,油而不膩,我覺得挺好绢要,不需要太多優(yōu)化吏恭。吼吼....

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市重罪,隨后出現(xiàn)的幾起案子樱哼,更是在濱河造成了極大的恐慌,老刑警劉巖剿配,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搅幅,死亡現(xiàn)場離奇詭異,居然都是意外死亡呼胚,警方通過查閱死者的電腦和手機茄唐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蝇更,“玉大人沪编,你說我怎么就攤上這事∧昀” “怎么了蚁廓?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長厨幻。 經(jīng)常有香客問我相嵌,道長,這世上最難降的妖魔是什么况脆? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任饭宾,我火速辦了婚禮,結(jié)果婚禮上格了,老公的妹妹穿的比我還像新娘看铆。我一直安慰自己,他們只是感情好盛末,可當我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布性湿。 她就那樣靜靜地躺著纬傲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪肤频。 梳的紋絲不亂的頭發(fā)上叹括,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天,我揣著相機與錄音宵荒,去河邊找鬼汁雷。 笑死,一個胖子當著我的面吹牛报咳,可吹牛的內(nèi)容都是我干的侠讯。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼暑刃,長吁一口氣:“原來是場噩夢啊……” “哼厢漩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起岩臣,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤溜嗜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后架谎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體炸宵,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年谷扣,在試婚紗的時候發(fā)現(xiàn)自己被綠了土全。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡会涎,死狀恐怖裹匙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情末秃,我是刑警寧澤幻件,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站蛔溃,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏篱蝇。R本人自食惡果不足惜贺待,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望零截。 院中可真熱鬧麸塞,春花似錦、人聲如沸涧衙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至雁比,卻和暖如春稚虎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背偎捎。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工蠢终, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人茴她。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓寻拂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親丈牢。 傳聞我的和親對象是個殘疾皇子祭钉,可洞房花燭夜當晚...
    茶點故事閱讀 43,472評論 2 348

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