1. 程式人生 > >Android CardView原始碼分析

Android CardView原始碼分析

轉載來源:https://www.jianshu.com/p/02c4cdb27109

首先放一張CardView的結構圖

在這裡插入圖片描述

對cardview做一個解析:

  • 首先介紹一下CardView經常使用的屬性
    cardview_cardBackgroundColor 設定背景色
    cardview_cardCornerRadius 設定圓角大小
    cardview_cardElevation 設定z軸陰影
    cardview_cardMaxElevation 設定z軸最大高度值
    cardview_cardUseCompatPadding 是否使用CompatPadding
    cardview_cardPreventCornerOverlap 是否使用PreventCornerOverlap ,防止內容和邊角的重疊
    cardview_contentPadding 內容的padding
    xml裡面加上這麼一句話 xmlns:card_view="http://schemas.android.com/apk/res-auto"
  • 從UML圖我們可以看見CardView繼承Framelayout。CardView內部持有了一個CardViewImpl例項和一個CardViewDelegate例項,CardViewImpl主要是CardView內部對於圓角陰影等 cardview重要特性在不同版本上的相容,CardViewImpl是對CardView具有的行為的抽象,在不同版本下有不同的實現,比如CardViewApi21,CardViewJellybeanMr1,CardViewGingerbread。

CardView陰影的實現

  • CardView對於Lollipop以上版本,是直接通過elevation來實現的陰影。
    • android5.0 引入了Z軸的概念,於是就有這種風格,也就是目前比較火的Material Design。
    • android:elevation 設定該元件“浮”起來的高度,to難過過設定該屬性可以讓該元件呈現3D效果。
    • android:translationZ 設定該元件在Z方向(垂直螢幕方向)上的位移。
    • eleavation是靜態的成員,translationZ是用來做動畫。
  • 但是elevation需要有margin配合才能透出陰影,比如elevation:10dp, margin:10dp, 這樣才能配合實現陰影。
  • 對於Lollipop版本以下的,是通過在padding裡面透出陰影來實現的,檢視原始碼發現是通過RoundRectDrawableWithShadow 這個drawble充當背景來實現的,而RoundRectDrawableWithShadow這個裡面是通過RadialGradient和LinearGradient兩種shader(紋理)來實現的漸變模擬的陰影效果。
  • 可以嘗試以下,給CardView直接設定setPadding時是沒有用的,為什麼呢,裡面重寫了setPadding方法,是空實現。
    @Override
    public void setPadding(int left, int top, int right, int bottom) {
        // NO OP
    }
  • 對應的是setContentPadding,我們可以看一下setContentPadding的實現,可以看到CardViewDelegate裡面有這麼一句話
    @Override
    public void setShadowPadding(int left, int top, int right, int bottom) {
        mShadowBounds.set(left, top, right, bottom);
        CardView.super.setPadding(left + mContentPadding.left, top + mContentPadding.top,
                right + mContentPadding.right, bottom + mContentPadding.bottom);
    }
  • 由此明白了,對於5.0以下系統,shadow的陰影由padding漏出,是必須留出來的,而剩下設定的contentPadding才是你設定的padding。
  • 在使用CardView,我踩了個坑,在把cardview的BackgroundDrawable給替換了之後,結果之後再怎麼設定顏色和圓角等屬性都不起作用了,這是為什麼呢?
    private final CardViewDelegate mCardViewDelegate = new CardViewDelegate() {
        private Drawable mCardBackground;

        @Override
        public void setCardBackground(Drawable drawable) {
            mCardBackground = drawable;
            setBackgroundDrawable(drawable);
        }

        @Override
        public boolean getUseCompatPadding() {
            return CardView.this.getUseCompatPadding();
        }

        @Override
        public boolean getPreventCornerOverlap() {
            return CardView.this.getPreventCornerOverlap();
        }

        @Override
        public void setShadowPadding(int left, int top, int right, int bottom) {
            mShadowBounds.set(left, top, right, bottom);
            CardView.super.setPadding(left + mContentPadding.left, top + mContentPadding.top,
                    right + mContentPadding.right, bottom + mContentPadding.bottom);
        }

        @Override
        public void setMinWidthHeightInternal(int width, int height) {
            if (width > mUserSetMinWidth) {
                CardView.super.setMinimumWidth(width);
            }
            if (height > mUserSetMinHeight) {
                CardView.super.setMinimumHeight(height);
            }
        }

        @Override
        public Drawable getCardBackground() {
            return mCardBackground;
        }

        @Override
        public View getCardView() {
            return CardView.this;
        }
    };
  • 可以看到,所以對於圓角背景顏色等操作 都是代理給mCardViewDelegate來完成的,而通過mCardViewDelegate獲取的背景Drawable是它儲存的mCardBackground例項,一旦直接給CardView設定了一個BackgroundDrawble,這個drawble和mCardViewDelegate儲存的mCardBackground就不再是一個,那麼之後對於背景的操作就是對牛彈琴,改變的只是mCardBackground的屬性,而真正的cardview的屬性沒有改變。
  • cardview_cardPreventCornerOverlap 是否使用PreventCornerOverlap ,防止內容和邊角的重疊,之前一直沒搞懂這個屬性的意思,直到我看到了RoundRectDrawableWithShadow中這段原始碼,還記得之前說的嗎,RoundRectDrawableWithShadow實現了5.0以下的陰影
    static float calculateVerticalPadding(float maxShadowSize, float cornerRadius,
            boolean addPaddingForCorners) {
        if (addPaddingForCorners) {
            return (float) (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius);
        } else {
            return maxShadowSize * SHADOW_MULTIPLIER;
        }
    }
  • 這裡addPaddingForCorners對應的就是PreventCornerOverlap這個boolean變數,通過+ (1 - COS_45) * cornerRadius,保證了內容不會超過圓角的邊界。
  • CardView水波紋效果。既然CardView繼承自Framelayout,那麼和Framelayout實現水波紋是一個效果。直接給 CardView 加上android:foreground="?attr/selectableItemBackground" 這個屬性會在 Lollipop 上自動加上 Ripple 效果,在舊版本則是一個變深/變亮的效果。