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

這還只是張圖片

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

背景:我一哥們公司做智能設(shè)備的,該動(dòng)畫用在手機(jī)和家中網(wǎng)絡(luò)連接時(shí)用,他讓我看了下需求。剛看到這動(dòng)畫時(shí)感覺產(chǎn)品\UI設(shè)計(jì)的不錯(cuò),想著試試。昨天開始做的,本來感覺很簡(jiǎn)單,但做起來貌似沒那么簡(jiǎn)單;最后花了近一天時(shí)間終于搞定了??纯葱Ч€行!

niceloading.gif
  • 如果有想直接用的同道中人,看前半部分就行;如果想批評(píng)指正我的思考的看看后半部分

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默認(rèn)
    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;
    }
}

項(xiàng)目已上傳到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);
……
//開始連接時(shí)
niceLoading.start();
……
//連接成功時(shí)
niceLoading.success();
……
//連接失敗時(shí)
niceLoading.failed();

3.我怎么實(shí)現(xiàn)的!

仔細(xì)看效果圖可以得出:
1、有6個(gè)小圓依次從屏幕左側(cè)移入屏幕中間,然后又依次從屏幕中間移出屏幕右側(cè)。
2、中間有個(gè)大圓在隨著小圓的依次靠近慢慢變大,離開慢慢變小;注意在左側(cè)第1個(gè)小圓到達(dá)中間時(shí)才出現(xiàn)大圓,在最后一個(gè)小圓準(zhǔn)備向右側(cè)移動(dòng)時(shí)消失;大圓的半徑在小圓半徑和大圓半徑之間。
3、不管何時(shí)得到成功和失敗的狀態(tài),動(dòng)畫終止時(shí)都是在小圓依次從左邊進(jìn)入中間后。
4、動(dòng)畫完成后顯示成功/失敗圖片和大圓。

  • 1. 6個(gè)小圓的運(yùn)動(dòng)
    我是這樣想的:當(dāng)?shù)?個(gè)小圓移動(dòng)到widthSpecSize/4(widthSpecSize 為控件的寬度)時(shí)第2個(gè)小圓開始移動(dòng)、當(dāng)?shù)?個(gè)小圓移動(dòng)到widthSpecSize/4 時(shí)第3個(gè)小圓開始移動(dòng)......當(dāng)?shù)?個(gè)小圓移動(dòng)到widthSpecSize/4 時(shí)第6個(gè)小圓開始移動(dòng)、第6個(gè)小圓移動(dòng)到widthSpecSize/2 時(shí)繼續(xù)移動(dòng)、當(dāng)?shù)?個(gè)小圓移動(dòng)到widthSpecSize * 3/4時(shí)第5個(gè)小圓開始移動(dòng)......當(dāng)?shù)?個(gè)小圓移動(dòng)到widthSpecSize * 3/4時(shí)第1個(gè)小圓開始移動(dòng)、最后第1個(gè)小球移出屏幕右側(cè),到此為一個(gè)循環(huán)。
    假設(shè)有一個(gè)位移變量moveX,moveX在不斷增加,其變化范圍為(a,b);可以看出按照我的想法,第1個(gè)小圓在范圍的兩邊時(shí)開始移動(dòng)、第6個(gè)小圓在變化范圍的中間部分開始移動(dòng)。
    我們可以繼續(xù)假設(shè)變化范圍為(-a,a),這樣第1個(gè)小圓在范圍的絕對(duì)值大時(shí)開始移動(dòng)、第6個(gè)小圓在變化范圍的絕對(duì)值小時(shí)開始移動(dòng);其實(shí)這種重復(fù)的動(dòng)作很容易想到絕對(duì)值控制
    找張紙畫下很容易得到moveX的變化范圍在(-widthSpecSize * 7/4 , widthSpecSize * 7/4)之間。
自己畫的圖,有點(diǎn)丑

對(duì)照?qǐng)D很快可以得出下面代碼

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. 中間大圓的運(yùn)動(dòng)
    中間變化的大圓在左側(cè)第1個(gè)小圓到達(dá)中間時(shí)才出現(xiàn)大圓,在第1個(gè)小圓準(zhǔn)備向右側(cè)移動(dòng)時(shí)消失,變化范圍(-widthSpecSize * 5/4 , widthSpecSize * 5/4)之間。大圓的半徑在小圓半徑和大圓半徑之間,我們用radiusbig = radiusbig - radiusSmall * (Math.abs(moveX)) / (widthSpecSize * 5/4)計(jì)算大圓半徑,可以得到慢慢變大和變小的效果,然后控制在小于radiusSmall時(shí)用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. 動(dòng)畫終止的控制
    正常當(dāng)一個(gè)循環(huán)結(jié)束時(shí)我們需要重新給moveX賦值為widthSpecSize * (-7/4),當(dāng)收到成功或失敗狀態(tài)時(shí)需要判斷當(dāng)前的狀態(tài),等到動(dòng)畫進(jìn)行到結(jié)束狀態(tài)(小圓依次從左邊進(jìn)入中間后)。見下面代碼,mState為當(dāng)前狀態(tài)(0失敗,1成功,-1默認(rèn))。
    我重新賦值時(shí)將moveX設(shè)為 * widthSpecSize * (-9/4)因?yàn)橐粋€(gè)循環(huán)結(jié)束后有點(diǎn)停頓會(huì)感覺舒服點(diǎn),這個(gè)無所謂,自己感覺而已*
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. 顯示成功/失敗圖片
    這個(gè)簡(jiǎn)單,在收到成功或失敗狀態(tài),待動(dòng)畫完成時(shí)先畫一個(gè)大圓,再畫一個(gè)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)化畫小圓的邏輯,使小圓個(gè)數(shù)可變等抽象出來..........

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

最后編輯于
?著作權(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),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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