從源碼上談一談 MaterialCardView

做爲一個程序員,沒事的時候就去瞅一瞅源碼仍是挺有意思的。android

在進入源碼以前,先來看張 CardView 和 MaterialCardView 的對比圖。程序員

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".main.CardTestActivity">

    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="160dp"
        android:layout_margin="12dp"
        app:cardBackgroundColor="@android:color/holo_green_light"
        app:cardCornerRadius="10dp"/>

    <com.google.android.material.card.MaterialCardView
        android:layout_width="match_parent"
        android:layout_height="160dp"
        android:layout_margin="12dp"
        app:cardBackgroundColor="@android:color/holo_orange_light"
        app:cardCornerRadius="10dp"
        app:strokeColor="@android:color/holo_blue_light"
        app:strokeWidth="5dp"/>

</LinearLayout>
複製代碼

MaterialCardView 其實是 CardView 的拓展,它繼承與 CardView,因此 CardView 具有的它都具有,除此以外,它還在 CardView 的基礎上增長了能夠繪製邊框這一特性 (劃重點:MaterialCardView 能夠繪製邊框),所以相對於 CardView,它就多出了 strokeColor 和 strokeWidth 這兩個屬性,拋開這兩個屬性,你徹底能夠把它當成一個 CardView 來使用,但我不推薦這麼來使用,由於不必。bash

MaterialCardView 的源碼其實很簡單(若須要看完整源碼的請移步最後面),主要須要關注的是有三個參數的構造方法:app

public MaterialCardView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray attributes = ThemeEnforcement.obtainStyledAttributes(context, attrs, styleable.MaterialCardView, defStyleAttr, style.Widget_MaterialComponents_CardView, new int[0]);
        this.cardViewHelper = new MaterialCardViewHelper(this);
        //讀取 strokeColor 和 strokeWidth 屬性的值,並繪製邊框、調整 ContentPadding
        this.cardViewHelper.loadFromAttributes(attributes);
        attributes.recycle();
    }
複製代碼

這裏實例化一個 MaterialCardViewHelper 對象,而後經過該對象調用 loadFromAttributes(TypedArray attributes) 方法來讀取 strokeColor 和 strokeWidth 屬性的值,並繪製邊框、調整 ContentPadding。ui

/**
     * From MaterialCardViewHelper.class
     * 讀取 strokeColor 和 strokeWidth 屬性的值,並繪製邊框、調整 ContentPadding
     * @param attributes
     */
    public void loadFromAttributes(TypedArray attributes) {
        //讀取 strokeColor 和 strokeWidth 屬性的值
        this.strokeColor = attributes.getColor(styleable.MaterialCardView_strokeColor, -1);
        this.strokeWidth = attributes.getDimensionPixelSize(styleable.MaterialCardView_strokeWidth, 0);
        //繪製邊框
        this.updateForeground();
        //調整 ContentPadding
        this.adjustContentPadding();
    }
複製代碼

繪製邊框 經過 updateForeground() 方法,該方法中實際上是經過 View 的 setForeground(Drawable foreground) 方法設置一下前景圖像,而這個邊框圖像是經過 createForegroundDrawable() 方法建立的。this

/**
     * From MaterialCardViewHelper.class
     * 繪製邊框(該邊框是繪製在前景圖像上的)
     */
    void updateForeground() {
        this.materialCardView.setForeground(this.createForegroundDrawable());
    }

    /**
     * From MaterialCardViewHelper.class
     * 建立一個指定角半徑、寬度和顏色的邊框 Drawable
     * @return
     */
    private Drawable createForegroundDrawable() {
        GradientDrawable fgDrawable = new GradientDrawable();
        fgDrawable.setCornerRadius(this.materialCardView.getRadius());
        if (this.strokeColor != -1) {
            fgDrawable.setStroke(this.strokeWidth, this.strokeColor);
        }
        return fgDrawable;
    }
複製代碼

最後由於繪製了邊框,因此須要對 MaterialCardView 內部放置內容的空間進行調整,它是經過 adjustContentPadding() 方法來完成的,其實就是四周在 ContentPadding 的基礎上加上邊框寬度做爲新的 ContentPadding。google

/**
     * From MaterialCardViewHelper.class
     * 調整 ContentPadding(四周在 ContentPadding 的基礎上加上邊框寬度)
     */
    private void adjustContentPadding() {
        int contentPaddingLeft = this.materialCardView.getContentPaddingLeft() + this.strokeWidth;
        int contentPaddingTop = this.materialCardView.getContentPaddingTop() + this.strokeWidth;
        int contentPaddingRight = this.materialCardView.getContentPaddingRight() + this.strokeWidth;
        int contentPaddingBottom = this.materialCardView.getContentPaddingBottom() + this.strokeWidth;
        this.materialCardView.setContentPadding(contentPaddingLeft, contentPaddingTop, contentPaddingRight, contentPaddingBottom);
    }
複製代碼

特別提醒:

  • 對於 MaterialCardView,若是你使用了 app:cardUseCompatPadding="true",而後邊框寬度設置得比較小,你會發現看不到這個邊框(它並非不存在,只是看不到而已,你能夠增大邊框寬度,但怎麼感受上效果沒有想象中那麼好呢,因此最好仍是把這個 cardUseCompatPadding 去掉吧!!!)。
  • 由於 MaterialCardView 的邊框是繪製在前景圖像上的,因此 android:foreground 就沒什麼用了,固然,在代碼中 MaterialCardView 的 setForeground(Drawable foreground) 仍是能夠用的,只是會把邊框給覆蓋掉。

MaterialCardView 源碼:

public class MaterialCardView extends CardView {

    /**
     * MaterialCardViewHelper 是 MaterialCardView 的一個輔助類,繪製邊框的全部操做都在它裏面進行
     */
    private final MaterialCardViewHelper cardViewHelper;

    public MaterialCardView(Context context) {
        this(context, (AttributeSet)null);
    }

    public MaterialCardView(Context context, AttributeSet attrs) {
        this(context, attrs, attr.materialCardViewStyle);
    }

    public MaterialCardView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray attributes = ThemeEnforcement.obtainStyledAttributes(context, attrs, styleable.MaterialCardView, defStyleAttr, style.Widget_MaterialComponents_CardView, new int[0]);
        this.cardViewHelper = new MaterialCardViewHelper(this);
        //讀取 strokeColor 和 strokeWidth 屬性的值,並繪製邊框、調整 ContentPadding
        this.cardViewHelper.loadFromAttributes(attributes);
        attributes.recycle();
    }

    /**
     * 設置邊框顏色
     * @param strokeColor
     */
    public void setStrokeColor(@ColorInt int strokeColor) {
        this.cardViewHelper.setStrokeColor(strokeColor);
    }

    /**
     * 獲取邊框顏色
     * @return
     */
    @ColorInt
    public int getStrokeColor() {
        return this.cardViewHelper.getStrokeColor();
    }

    /**
     * 設置邊框寬度
     * @param strokeWidth
     */
    public void setStrokeWidth(@Dimension int strokeWidth) {
        this.cardViewHelper.setStrokeWidth(strokeWidth);
    }

    /**
     * 獲取邊框寬度
     * @return
     */
    @Dimension
    public int getStrokeWidth() {
        return this.cardViewHelper.getStrokeWidth();
    }

    /**
     * 設置角半徑
     * @param radius
     */
    public void setRadius(float radius) {
        super.setRadius(radius);
        //設置角半徑時,更新一下邊框
        this.cardViewHelper.updateForeground();
    }
}
複製代碼

MaterialCardViewHelper 源碼:

@RestrictTo({Scope.LIBRARY_GROUP})
class MaterialCardViewHelper {
    private static final int DEFAULT_STROKE_VALUE = -1;
    //MaterialCardView 對象,由實例化 MaterialCardViewHelper 時經過它的構造方法傳入
    private final MaterialCardView materialCardView;
    private int strokeColor;
    private int strokeWidth;

    public MaterialCardViewHelper(MaterialCardView card) {
        this.materialCardView = card;
    }

    /**
     * 讀取 strokeColor 和 strokeWidth 屬性的值,並繪製邊框、調整 ContentPadding
     * @param attributes
     */
    public void loadFromAttributes(TypedArray attributes) {
        //讀取 strokeColor 和 strokeWidth 屬性的值
        this.strokeColor = attributes.getColor(styleable.MaterialCardView_strokeColor, -1);
        this.strokeWidth = attributes.getDimensionPixelSize(styleable.MaterialCardView_strokeWidth, 0);
        //繪製邊框
        this.updateForeground();
        //調整 ContentPadding
        this.adjustContentPadding();
    }

    /**
     * 設置邊框顏色,並更新邊框
     * @param strokeColor
     */
    void setStrokeColor(@ColorInt int strokeColor) {
        this.strokeColor = strokeColor;
        this.updateForeground();
    }

    /**
     * 獲取邊框顏色
     * @return
     */
    @ColorInt
    int getStrokeColor() {
        return this.strokeColor;
    }

    /**
     * 設置邊框寬度,並更新邊框、調整 ContentPadding
     * @param strokeWidth
     */
    void setStrokeWidth(@Dimension int strokeWidth) {
        this.strokeWidth = strokeWidth;
        this.updateForeground();
        this.adjustContentPadding();
    }

    /**
     * 獲取邊框寬度
     * @return
     */
    @Dimension
    int getStrokeWidth() {
        return this.strokeWidth;
    }

    /**
     * 繪製邊框(該邊框是繪製在前景圖像上的)
     */
    void updateForeground() {
        this.materialCardView.setForeground(this.createForegroundDrawable());
    }

    /**
     * 建立一個指定角半徑、寬度和顏色的邊框 Drawable
     * @return
     */
    private Drawable createForegroundDrawable() {
        GradientDrawable fgDrawable = new GradientDrawable();
        fgDrawable.setCornerRadius(this.materialCardView.getRadius());
        if (this.strokeColor != -1) {
            fgDrawable.setStroke(this.strokeWidth, this.strokeColor);
        }
        return fgDrawable;
    }

    /**
     * 調整 ContentPadding(四周在 ContentPadding 的基礎上加上邊框寬度)
     */
    private void adjustContentPadding() {
        int contentPaddingLeft = this.materialCardView.getContentPaddingLeft() + this.strokeWidth;
        int contentPaddingTop = this.materialCardView.getContentPaddingTop() + this.strokeWidth;
        int contentPaddingRight = this.materialCardView.getContentPaddingRight() + this.strokeWidth;
        int contentPaddingBottom = this.materialCardView.getContentPaddingBottom() + this.strokeWidth;
        this.materialCardView.setContentPadding(contentPaddingLeft, contentPaddingTop, contentPaddingRight, contentPaddingBottom);
    }
}
複製代碼
相關文章
相關標籤/搜索