仿易訊客戶端loading效果

rowandjj發表於2015-08-05

下面來實現一個loading效果。具體效果如下:
這裡寫圖片描述
首先對這個效果進行拆分,它由以下部分組成:

  • 1 一個”閃電”樣式的圖案;
  • 2 “閃電”圖案背後是一個圓角矩形;
  • 3 “閃電”圖案上面有一層顏色不斷”飄過”

拆分完效果後,思考下如何實現。下面是我的思考過程。

  • 1 android sdk並沒有提供這樣的控制元件,很顯然是需要自定義控制元件;
  • 2 很顯然是一個View而不是ViewGroup,所以可以繼承View;
  • 3 重點是onDraw的邏輯;
  • 4 怎樣繪製”閃電”的圖案?可以通過Path繪製;
  • 5 怎樣繪製”閃電”背後的圓角矩形?canvas.drawRoundRect;
  • 6 怎樣實現”閃電”的動效?仔細觀察,發現上方那層顏色的運動規律是從0~閃電高度不斷擴大,到達閃電高度的時候,高度不斷減小直到0。所以可以通過控制高度的方式實現。只要有兩個變數scanTop/scanBottom記錄繪製的上下界限即可,然後控制scanTop/scanBottom進行變化即可,怎樣控制變化很顯然可以通過post/postDelayed實現。另外一個難點是如何繪製部分”閃電”?思索一番,可以通過canvas.clipRect的方式控制繪製區域,這樣間接實現了我們需要的效果;
  • 7 核心邏輯實現之後,需要考慮到應該讓這個自定義控制元件支援wrap_content.這必然需要重寫onMeasure,並考慮到父容器的MeasureSpec(view的預設實現下wrap_content和match_parent效果一樣);
  • 8 需要讓這個控制元件支援padding。所以得在measure和draw的過程中充分考慮到padding這個因素;
  • 9 當view被detach的時候,需要remove掉動畫;
  • 10 應該提供幾個預設大小,比如small/midium/large,這可以通過自定義屬性實現。

大致思考完之後,可以寫程式碼了。

首先是measure過程:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //需要計算自己實際需要的寬高
        //需要把padding考慮進來
        //需要考慮父容器的測量規則

        int width,height;

        width = (int)mViewMinWidth+getPaddingLeft()+getPaddingRight();
        height = (int)mViewMinHeight+getPaddingTop()+getPaddingBottom();

        setMeasuredDimension(getMeasuredSize(widthMeasureSpec, width), getMeasuredSize(heightMeasureSpec, height));
    }

通過getMeasuredSize計算考慮父容器限制後的實際大小:

private int getMeasuredSize(int measureSpec,int desiredSize){

        int result;

        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);

        switch (mode){
            case MeasureSpec.EXACTLY:
                result = size;
                break;
            default:
                result = desiredSize;
                if(mode == MeasureSpec.AT_MOST)
                    result = Math.min(result,size);
                break;
        }

        return result;

    }

然後是draw的過程:


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        mPaint.setColor(mViewBackground);
        //如果xml中設定layout_width/layout_height大於預設寬高,那麼居中(不允許小於預設寬高)
        if(getWidth()-getPaddingLeft()-getPaddingRight() > (int)mViewMinWidth || getHeight()-getPaddingTop()-getPaddingBottom() > (int)mViewMinHeight){
           canvas.translate((getWidth()-mViewMinWidth)/2.0f,(getHeight()-mViewMinHeight)/2.0f);
        }
        //畫圓角矩形
        canvas.drawRoundRect(mBounds, dp2px(5), dp2px(5), mPaint);
        //平移到圓角矩形中心點,畫閃電
        canvas.translate((mViewMinWidth - mDefaultWidth) / 2.0f, (mViewMinHeight - mDefaultHeight) / 2.0f);
        mPaint.setColor(mBackgroundColor);

        canvas.drawPath(mThunderPath, mPaint);
        mPaint.setColor(mCoverColor);
        //通過clicpRect的方式控制可繪製區域(在外界看來好像有閃動的動畫效果)
        canvas.clipRect(getPaddingLeft(), mScanTop + getPaddingTop(), mDefaultWidth + getPaddingLeft(), mScanBottom + getPaddingTop());
        canvas.drawPath(mThunderPath, mPaint);
    }

mScanTop/mScanBottom變數可以通過post()進行改變:

class AnimRunnable implements Runnable{
        @Override
        public void run() {
            if (!flag) {
                mScanBottom += mGap;
                if (mScanBottom >= mDefaultHeight) {
                    mScanBottom = (int) mDefaultHeight;
                    flag = true;
                }
                postInvalidate();
                post(this);
            } else {
                mScanTop += mGap;
                if (mScanTop >= mDefaultHeight) {
                    mScanTop = mScanBottom = 0;
                    flag = false;
                    postInvalidate();
                    postDelayed(this, 700);
                } else {
                    postInvalidate();
                    post(this);
                }
            }
        }
    }
private void startAnim() {
        mRunnable = new AnimRunnable();
        post(mRunnable);
    }

核心程式碼就這麼多。
完整程式碼在這裡:https://github.com/Rowandjj/ThunderLoadingView

相關文章