UI組件——SwipeRefrshLayout最詳細的源碼解析——UI繪製

  • Ui繪製概述
    SwipeRefreshLayout的繪製基本上分爲三步,第一步是自定義一個CircleImageView,這個CircleImageView加入了5.0的新特效,既有個陰影的效果,不知道平常大家注意到沒,第二步是自定義了一個MaterialProgressDrawable作爲ImageView的 Drawable,既通過imageView.setImageDrawable(Drawable)賦值給CircleImageView,第三步是將CircleImageView添加到SwipeRefreshLayout中,同時在SwipeRefreshLayout中處理滑動事件,這裏只先將UI的繪製,動畫及滑動事件的處理以後再講。
  • CircleImageView的繪製
    在SwipeRefreshLayout的構造中我們可以找到一個方法createProgressView();,Ctrl+鼠標左鍵跟隨源碼看下
  private void createProgressView() {
        mCircleView = new CircleImageView(getContext(), CIRCLE_BG_LIGHT);
        mProgress = new MaterialProgressDrawable(getContext(), this);
        mProgress.setBackgroundColor(CIRCLE_BG_LIGHT);
        mCircleView.setImageDrawable(mProgress);
        mCircleView.setVisibility(View.GONE);
        addView(mCircleView);
    }

很明顯的在這裏創建了CirleImageView和MaterialProgressDrawable,然後將MaterialProgressDrawable通過setImageDrawable給了CircleImageView,最後將CircleImageView添加到SwipeRefreshLayout中,我們先看看CircleImageView是如何自定義的。當跟隨源碼進入CircleImageView的時候,我們發現這裏只有一個構造,和我們平時看到的不太一樣,可能你以前見到的有好幾個構造,爲什麼這裏只有一個呢,只要看看構造裏面有何不同,也許你就猜到爲什麼了,因爲我們這裏的自定義全是用Java代碼來寫出來的佈局,並沒有用那些xml屬性,我們我們無需多寫具有AttributeSet等參數的構造

     CircleImageView(Context context, int color) {
     ........此處省略很多代碼,只看構造
}

接着逐句解析源碼

 final float density = getContext().getResources().getDisplayMetrics().density;

在構造裏面第一句就是獲取density,這個是什麼東西呢?Google的原話是:The logical density of the display (設備的邏輯密度).這是個什麼意思呢?舉個例子你就明白了,比如dip爲160的設備,它的density是1,那麼當dip爲120的設備,它的density就是0.75,當然,這個東西不用你來算,你獲取到的density就是當前設備的density,直接用就行了,這個值就是爲了適配各種屏幕密度的。怎麼用?等下看源碼是怎麼用的,你就知道了。

final int shadowYOffset = (int) (density * Y_OFFSET);
        final int shadowXOffset = (int) (density * X_OFFSET);

        mShadowRadius = (int) (density * SHADOW_RADIUS);

這裏的三行代碼分別是設置陰影在水平和垂直方向的偏移量以及陰影的半徑的,Y_OFFSET、X_OFFSET和SHADOW_RADIUS是常量,分別爲1.75f、0f、3.5f。

接下來看關於5.0新特效的(陰影)的處理,關於是不是5.0

 private boolean elevationSupported() {
        return android.os.Build.VERSION.SDK_INT >= 21;
    }

傻子都能看懂,還需要解釋嗎

看下如果是5.0的處理

 if (elevationSupported()) {
            circle = new ShapeDrawable(new OvalShape());
            ViewCompat.setElevation(this, SHADOW_ELEVATION * density);

這裏直接new ShapeDrawable(ovalShape) 可以這麼理解,這裏我們需要一個OvalShape(橢圓類型)的ShapeDrawable作爲我們CircleImageView的Background,所有在5.0以上我們就直接new了,但是要注意加上ViewCompat.setElevation(this, SHADOW_ELEVATION * density);這句是設置投影高度的,不設置就沒陰影效果了。

接下來看5.0以下的版本的兼容處理,就是else裏面的

else {
            OvalShape oval = new OvalShadow(mShadowRadius);
            circle = new ShapeDrawable(oval);
            ViewCompat.setLayerType(this, ViewCompat.LAYER_TYPE_SOFTWARE, circle.getPaint());
            circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset,
                    KEY_SHADOW_COLOR);
            final int padding = mShadowRadius;
            // set padding so the inner image sits correctly within the shadow.
            setPadding(padding, padding, padding, padding);
        }

先看第一行OvalShape oval = new OvalShadow(mShadowRadius)這是一個重寫了OvalShape的類,它幹了什麼事情呢,我們看下源碼

private class OvalShadow extends OvalShape {
        private RadialGradient mRadialGradient;
        private Paint mShadowPaint;

        OvalShadow(int shadowRadius) {
            super();
            mShadowPaint = new Paint();
            mShadowRadius = shadowRadius;
            updateRadialGradient((int) rect().width());
        }

        @Override
        protected void onResize(float width, float height) {
            super.onResize(width, height);
            updateRadialGradient((int) width);
        }

        @Override
        public void draw(Canvas canvas, Paint paint) {
            final int viewWidth = CircleImageView.this.getWidth();
            final int viewHeight = CircleImageView.this.getHeight();
            canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2, mShadowPaint);
            canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2 - mShadowRadius, paint);
        }

        private void updateRadialGradient(int diameter) {
            mRadialGradient = new RadialGradient(diameter / 2, diameter / 2,
                    mShadowRadius, new int[] { FILL_SHADOW_COLOR, Color.TRANSPARENT },
                    null, Shader.TileMode.CLAMP);
            mShadowPaint.setShader(mRadialGradient);
        }
    }

它重寫了兩個方法,onResize 和 draw,我們先看下draw幹了什麼

 canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2, mShadowPaint);
            canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2 - mShadowRadius, paint);

這裏畫了兩個圓,圓心是一樣的,只是半徑不一樣,就是下面這樣的:
這裏寫圖片描述
然後在看一下構造和onResize裏都調用的方法updateRadialGradient(int),這個方法是幹嘛的呢,它是爲着色器設置一個梯度的漸變效果,設置從中間到邊緣逐漸變成透明。通過這兩個圓達到實現陰影效果的曲線救國方針,巴特,還沒完呢,接着看else裏面的代碼

circle = new ShapeDrawable(oval);
//設置圖層
 ViewCompat.setLayerType(this, ViewCompat.LAYER_TYPE_SOFTWARE, circle.getPaint());
 //設置陰影層        circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset,
                    KEY_SHADOW_COLOR);
            final int padding = mShadowRadius;
            // set padding so the inner image sits correctly within the shadow.
            setPadding(padding, padding, padding, padding);

首先將我們畫的兩個圓放到ShapeDrawable中,然後設置圖層,接着設置陰影層

最後別忘了把我們的ShapeDrawable設置爲CircleImageView的Background

circle.getPaint().setColor(color);
ViewCompat.setBackground(this, circle);

最最後,看看我們自定義View必看的一個方法onMeasure

 if (!elevationSupported()) {
            setMeasuredDimension(getMeasuredWidth() + mShadowRadius * 2, getMeasuredHeight()
                    + mShadowRadius * 2);
        }

這是5.0以下版本需要我們加上陰影部分的寬度,既mShadowRadius * 2;5.0以上版本就不需要我們自己把陰影部分加上去了,系統已經默認測量了
至此:CircleImageView的繪製的源碼分析完了。


如有錯誤,請留言更正,以免誤導其他開發者!!!

發佈了54 篇原創文章 · 獲贊 17 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章