帶你掌握一款多特效的智能loadingView

先上效果圖:

一、登陸效果展現html

二、關注效果展現java

一、【畫圓角矩形】

畫圖首先是onDraw方法(我會把圓代碼寫上,一步一步剖析):首先在view中定義個屬性:private RectF rectf = new RectF();//能夠理解爲,裝載控件按鈕的區域面試

rectf.left = current_left;
rectf.top = 0;      //(這2點肯定空間區域左上角,current_left,是爲了後面動畫矩形變成等邊矩形準備的,這裏你能夠當作0)  
rectf.right = width - current_left; 
rectf.bottom = height;       //(經過改變current_left大小,更新繪製,就會實現了動畫效果)
//畫圓角矩形 
//參數1:區域
//參數2,3:圓角矩形的圓角,其實就是矩形圓角的半徑
//參數4:畫筆
canvas.drawRoundRect(rectf, circleAngle, circleAngle, paint);

二、【肯定控件的大小】

上面是畫圓角,那width和height怎麼來呢固然是經過onMeasure;canvas

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    height = measuredHeight(heightMeasureSpec);  //這裏是測量控件大小
    width = measureWidth(widthMeasureSpec);  //咱們常常能夠看到咱們設置控件wrap_content,match_content或者固定值
    setMeasuredDimension(width, height);
}

下面以measureWidth爲例:微信

private int measureWidth(int widthMeasureSpec) {
        int result;
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        int specSize = MeasureSpec.getSize(widthMeasureSpec);
        //這裏是精準模式,好比match_content,或者是你控件裏寫明瞭控件大小
        if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
        } else {
//這裏是wrap_content模式,其實這裏就是給一個默認值
//下面這段註銷代碼是最開始若是用戶不設置大小,給他一個默認固定值。這裏以字體長度來決定更合理
//result = (int) getContext().getResources().getDimension(R.dimen.dp_150);
//這裏是我設置的長度,固然你寫自定義控件能夠設置你想要的邏輯,根據你的實際狀況
result = buttonString.length() * textSize + height * 5 / 3;
if (specMode == MeasureSpec.AT_MOST) {
    result = Math.min(result, specSize);
}
        }
        return result;
    }

三、【繪製文字text】

這裏我是用本身的方式實現:當文字長度超過控件長度時,文字須要來回滾動。因此自定義控件由於你須要什麼樣的功能能夠本身去實現(固然這個方法也是在onDraw裏,爲何這麼個順序講,目的但願我但願你能按部就班的理解,若是你以爲onDraw方代碼太雜,你能夠用個方法獨立出去,你能夠跟做者同樣用private void drawText(Canvas canvas) {}), //繪製文字的路徑(文字過長時,文字來回滾動須要用到)架構

private Path textPath = new Path():app

textRect.left = 0;
textRect.top = 0;
textRect.right = width;
textRect.bottom = height; //這裏肯定文字繪製區域,其實就是控件區域
Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt();
//這裏是獲取文字繪製的y軸位置,能夠理解上下居中
int baseline = (textRect.bottom + textRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
//這裏判斷文字長度是否大於控件長度,固然我控件2邊須要留文字的間距,因此不是大於width,這麼說只是更好的理解
//這裏是當文字內容大於控件長度,啓動回滾效果。建議先看下面else裏的正常狀況
if ((buttonString.length() * textSize) > (width - height * 5 / 3)) {
    textPath.reset();
    //由於要留2遍間距,以heigh/3爲間距
    textPath.moveTo(height / 3, baseline);
    textPath.lineTo(width - height / 3, baseline);
    //這裏的意思是文字從哪裏開始寫,能夠是居中,這裏是右邊
    textPaint.setTextAlign(Paint.Align.RIGHT);
    //這裏是以路徑繪製文字,scrollSize能夠理解爲文字在x軸上的便宜量,同時,個人混動效果就是經過改變scrollSize
    //刷新繪製來實現
    canvas.drawTextOnPath(buttonString, textPath, scrollSize, 0, textPaint);
    if (isShowLongText) {
        //這裏是繪製遮擋物,由於繪製路徑沒有間距這方法,因此繪製遮擋物相似於間距方式
        canvas.drawRect(new Rect(width - height / 2 - textSize / 3, 0, width - height / 2, height),paintOval);
        canvas.drawRect(new Rect(height / 2, 0, height / 2 + textSize / 3, height), paintOval);
        //這裏有個bug 有個小點-5  因畫筆粗細產生
        canvas.drawArc(new RectF(width - height, 0, width - 5, height), -90, 180, true, paintOval);
        canvas.drawArc(new RectF(0, 0, height, height), 90, 180, true, paintOval);
    }

    if (animator_text_scroll == null) { 
        //這裏是計算混到最右邊和最左邊的距離範圍
        animator_text_scroll = ValueAnimator.ofInt(buttonString.length() * textSize - width + height * 2 / 3,-textSize);
        //這裏是動畫的時間,scrollSpeed能夠理解爲每一個文字滾動控件外所需的時間,能夠作成控件屬性提供出去 
        animator_text_scroll.setDuration(buttonString.length() * scrollSpeed);
        //設置動畫的模式,這裏是來回滾動
        animator_text_scroll.setRepeatMode(ValueAnimator.REVERSE);
        //設置插值器,讓整個動畫流暢
        animator_text_scroll.setInterpolator(new LinearInterpolator());
        //這裏是滾動次數,-1無限滾動
        animator_text_scroll.setRepeatCount(-1);
        animator_text_scroll.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
    //改變文字路徑x軸的偏移量
    scrollSize = (int) animation.getAnimatedValue();
    postInvalidate();
}
        });
        animator_text_scroll.start();
    }
} else {
    //這裏是正常狀況,isShowLongText,是我在啓動控件動畫的時候,是否啓動 文字有漸變效果的標識,
    //若是是長文字,啓動漸變效果的話,若是控件變小,文字內容在當前控件外,會顯得很難看,因此根據這個標識,關閉,這裏你能夠先忽略(同時由於根據路徑繪製text不能有間距效果,這個標識仍是判斷是否在控件2遍繪製遮擋物,這是做者的解決方式,若是你有更好的方式能夠在下方留言)
    isShowLongText = false;
    /**
     * 簡單的繪製文字,沒有考慮文字長度超過控件長度
     * */
    //這裏是居中顯示
    textPaint.setTextAlign(Paint.Align.CENTER);
    //參數1:文字
    //參數2,3:繪製文字的中心點
    //參數4:畫筆
    canvas.drawText(buttonString, textRect.centerX(), baseline, textPaint);
}

四、【自定義控件屬性】

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="SmartLoadingView">
        <attr name="textStr" format="string" />
        <attr name="errorStr" format="string" />
        <attr name="cannotclickBg" format="color" />
        <attr name="errorBg" format="color" />
        <attr name="normalBg" format="color" />
        <attr name="cornerRaius" format="dimension" />
        <attr name="textColor" format="color" />
        <attr name="textSize" format="dimension" />
        <attr name="scrollSpeed" format="integer" />
    </declare-styleable>
</resources>

這裏以,文案爲例, textStr。好比你再佈局種用到app:txtStr="文案內容"。在自定義控件裏獲取以下:ide

public SmartLoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    //自定義控件的3參方法的attrs就是咱們設置自定義屬性的關鍵
    //好比咱們再attrs.xml裏自定義了咱們的屬性,
    TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.SmartLoadingView);
    //這裏是獲取用戶有沒有設置整個屬性
    //這裏是從用戶那裏獲取有沒有設置文案
    String title = typedArray.getString(R.styleable.SmartLoadingView_textStr);
    if (TextUtils.isEmpty(title)){
       //若是獲取來的屬性是空,那麼能夠默認一個屬性
       //(做者忘記設置了!由於已經發布後期優化,老尷尬了)
       buttonString ="默認文案";
    }else{
       //若是有設置文案
       buttonString = title;
    }

}

五、【設置點擊事件,啓動動畫】

爲了點擊事件的直觀,也能夠把處理防止重複點擊事件封裝在裏面佈局

//這是我自定義登陸點擊的接口
public interface LoginClickListener {
    void click();
}

public void setLoginClickListener(final LoginClickListener loginClickListener) {
    this.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
if (loginClickListener != null) {
    //防止重複點擊
    if (!isAnimRuning) {
        start();
        loginClickListener.click();
    }

}
        }
    });
}

六、【動畫講解】

6.一、第一個動畫,矩形到正方形,以及矩形到圓角矩形(這裏是2個動畫,只是同時進行)

矩形到正方形(爲了簡化,我把源碼一些其餘屬性去掉了,這樣方便理解)post

//其中  default_all_distance = (w - h) / 2;除以2是由於2遍都往中間縮短
private void set_rect_to_circle_animation() {
    //這是一個屬性動畫,current_left 會在duration時間內,從0到default_all_distance勻速變化
    //想添加多樣化的話  還能夠加入插值器。
    animator_rect_to_square = ValueAnimator.ofInt(0, default_all_distance);
    animator_rect_to_square.setDuration(duration);
    animator_rect_to_square.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
//這裏的current_left跟onDraw相關,還記得嗎
//onDraw裏的控件區域 
//控件左邊區域 rectf.left = current_left;
//控件右邊區域 rectf.right = width - current_left;
current_left = (int) animation.getAnimatedValue();
//刷新繪製
invalidate();
        }
    });

矩形到圓角矩形。就是從一個沒有圓角的變成徹底圓角的矩形,固然我展現的時候只有第三個圖,最後一個按鈕才明顯了。

其餘的我直接設置成了圓角按鈕,由於我把圓角作成了一個屬性。

還記得onDraw裏的canvas.drawRoundRect(rectf, circleAngle, circleAngle, paint);circleAngle就是圓角的半徑

能夠想象一下若是全是圓角,那麼circleAngle會是多少,固然是height/2;沒錯吧,因此

由於我把圓角作成了屬性obtainCircleAngle是從xml文件獲取的屬性,若是不設置,則爲0,就沒有任何圓角效果

animator_rect_to_angle = ValueAnimator.ofInt(obtainCircleAngle, height / 2);
animator_rect_to_angle.setDuration(duration);
animator_rect_to_angle.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        //這裏試想下若是是一個正方形,恰好是圓形的圓角,那就是一個圓
        circleAngle = (int) animation.getAnimatedValue();
        //刷新繪畫
        invalidate();
    }
});

2個屬性動畫作好後,用 private AnimatorSet animatorSet = new AnimatorSet();把屬性動畫加進去,能夠設置2個動畫同時進行,仍是前後順序 這裏是同時進行所用用with

animatorSet
        .play(animator_rect_to_square).with(animator_rect_to_angle);

6.二、變成圓形後,有一個loading加載動畫

這裏就是畫圓弧,只是不斷改變,圓弧的起始點和終點,最終呈現loading狀態,也是在onDraw裏

//繪製加載進度
if (isLoading) {
    //參數1:繪製圓弧區域
    //參數2,3:繪製圓弧起始點和終點
    canvas.drawArc(new RectF(width / 2 - height / 2 + height / 4, height / 4, width / 2 + height / 2 - height / 4, height / 2 + height / 2 - height / 4), startAngle, progAngle, false, okPaint);

    //這裏是我經過實踐,實現最佳loading動畫
    //固然這裏有不少方式,由於我自定義這個view想把全部東西都放在這個類裏面,你也能夠有你的方式
    //若是有更好的方式,歡迎留言,告知我一下
    startAngle += 6;
    if (progAngle >= 270) {
        progAngle -= 2;
        isAdd = false;
    } else if (progAngle <= 45) {
        progAngle += 6;
        isAdd = true;
    } else {
        if (isAdd) {
progAngle += 6;
        } else {
progAngle -= 2;
        }
    }
    //刷新繪製,這裏不用擔憂有那麼多刷新繪製,會不會影響性能
    //
    postInvalidate();
}

6.三、loading狀態,到打勾動畫

那麼這裏首先要把loading動畫取消,那麼直接改變isLoading=false;不會只它同時啓動打勾動畫;打勾動畫的動畫,這裏比較麻煩,也是我在別人自定義動畫裏學習的,經過PathMeasure,實現路徑動畫

/** * 路徑--用來獲取對勾的路徑 */private Path path = new Path();/** * 取路徑的長度 */private PathMeasure pathMeasure;
//初始化打勾動畫路徑;private void initOk() {    //對勾的路徑    path.moveTo(default_all_distance + height / 8 * 3, height / 2);    path.lineTo(default_all_distance + height / 2, height / 5 * 3);    path.lineTo(default_all_distance + height / 3 * 2, height / 5 * 2);    pathMeasure = new PathMeasure(path, true);}
/**
 * 路徑--用來獲取對勾的路徑
 */
private Path path = new Path();
/**
 * 取路徑的長度
 */
private PathMeasure pathMeasure;
//初始化打勾動畫路徑;
private void initOk() {
    //對勾的路徑
    path.moveTo(default_all_distance + height / 8 * 3, height / 2);
    path.lineTo(default_all_distance + height / 2, height / 5 * 3);
    path.lineTo(default_all_distance + height / 3 * 2, height / 5 * 2);
    pathMeasure = new PathMeasure(path, true);
}
//初始化打勾動畫
private void set_draw_ok_animation() {
    animator_draw_ok = ValueAnimator.ofFloat(1, 0);
    animator_draw_ok.setDuration(duration);
    animator_draw_ok.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
startDrawOk = true;
isLoading = false;
float value = (Float) animation.getAnimatedValue();
effect = new DashPathEffect(new float[]{pathMeasure.getLength(), pathMeasure.getLength()}, value * pathMeasure.getLength());
okPaint.setPathEffect(effect);
invalidate();

        }
    });
}

//啓動打勾動畫只須要調用
animator_draw_ok.start();

onDraw裏繪製打勾動畫

//繪製打勾,這是onDraw的,startDrawOk是判斷是否開啓打勾動畫的標識
if (startDrawOk) {
    canvas.drawPath(path, okPaint);
}

6.四、loading狀態下回到失敗樣子(有點相似聯網失敗了)

以前6.1提到了矩形到圓角矩形和矩形到正方形的動畫,

那麼這裏只是前面2個動畫反過來,再加上聯網失敗的文案,和聯網失敗的背景圖即刻

6.五、loading狀態下啓動擴散全屏動畫(重點)

這裏我經過loginSuccess裏參數的類型啓動不一樣效果:

一、啓動擴散全屏動畫
public void loginSuccess(Animator.AnimatorListener endListener) {}

二、啓動打勾動畫
public void loginSuccess(AnimationOKListener animationOKListener) {}

啓動擴散全屏是本文的重點,裏面還涉及到了一個自定義view

CirclBigView,這個控件是全屏的,並且是從一個小圓不斷改變半徑變成大圓的動畫,那麼有人會問,全屏確定很差啊,會影響佈局,
可是這裏,我把它放在了activity的視圖層:
ViewGroup activityDecorView = (ViewGroup) ((Activity) getContext()).getWindow().getDecorView();
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
activityDecorView.addView(circlBigView, layoutParams);

這個靈感也是前不久在學習微信,拖拽退出的思路里發現的。所有代碼以下:

public void toBigCircle(Animator.AnimatorListener endListener) {
    //把縮小到圓的半徑,告訴circlBigView
    circlBigView.setRadius(this.getMeasuredHeight() / 2);
    //把當前背景顏色告訴circlBigView
    circlBigView.setColorBg(normal_color);
    int[] location = new int[2];
    //測量當前控件所在的屏幕座標x,y
    this.getLocationOnScreen(location);
    //把當前座標告訴circlBigView,同時circlBigView會計算當前點,到屏幕4個點的最大距離,便是當前控件要擴散到的半徑
    //具體建議讀者看完本博客後,去下載玩耍下。
    circlBigView.setXY(location[0] + this.getMeasuredWidth() / 2, location[1]);
    if (circlBigView.getParent() == null) {
        ViewGroup activityDecorView = (ViewGroup) ((Activity) getContext()).getWindow().getDecorView();
        ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        activityDecorView.addView(circlBigView, layoutParams);
    }
    circlBigView.startShowAni(endListener);
    isAnimRuning = false;
}

結束語

由於項目是把以前的功能寫成了控件,因此有不少地方不完善。但願有建議的大牛和小夥伴,提示提示我,讓我完善的更好。謝謝

Android開發資料+面試架構資料 免費分享 點擊連接 便可領取

《Android架構師必備學習資源免費領取(架構視頻+面試專題文檔+學習筆記)》

相關文章
相關標籤/搜索