Android CardView如何顯示出底背景樣式

<androidx.cardview.widget.CardView
    android:id="@+id/actionOneCv"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="1"
    android:foreground="?android:attr/selectableItemBackground"
    app:cardCornerRadius="0dp"
    app:cardElevation="0dp"/>

常使用上述的寫法來實現一個button樣式。

問題一:
如果將其放入在一個dialog的bottom位置作為點擊按鈕,dialog的background設置了圓角,但是顯示button一角卻還是直角。

問題二:
此時dialog更改bg顏色,發現CardView區域還是白色。

針對問題一,當然可以更改CardView來手動設置某一部分是圓角。
但兩個問題的癥結其實都是同一個點,那就是:

設置水波紋foreground的CardView默認背景就是白色

解決方案:將默認背景改為透明,則就能顯示出dialog底背景的樣式了。

<androidx.cardview.widget.CardView
    android:id="@+id/actionOneCv"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="1"
    app:cardBackgroundColor="@color/transparent"
    android:foreground="?android:attr/selectableItemBackground"
    app:cardCornerRadius="0dp"
    app:cardElevation="0dp"/>

可以看到此處是設置app:cardBackgroundColor="@color/transparent".

問題來了,上述的方法只能設置color,如果要設置drawable該怎么辦?
其實解決方案很簡單:CardView本身是繼承自FrameLayout的,這就意味著它是可以包裹內容的,里面包裹其它控件(如ImageView)。通過包裹的控件來實現有drawable的背景即可。

那么又有問題了,既然CardView繼承自FrameLayout,這就說明其是可以設置Background的。但為什么設置后卻顯示不出來呢?

這個就涉及到整個View的構造流程了:

public CardView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
//省略屬性獲取
    IMPL.initialize(mCardViewDelegate, context, backgroundColor, radius,
            elevation, maxElevation);
}

初始化View進入構造方法,會進行IMPL.initialize方法的調用。這個IMPL是什么?

private static final CardViewImpl IMPL;

static {
    if (Build.VERSION.SDK_INT >= 21) {
        IMPL = new CardViewApi21Impl();
    } else if (Build.VERSION.SDK_INT >= 17) {
        IMPL = new CardViewApi17Impl();
    } else {
        IMPL = new CardViewBaseImpl();
    }
    IMPL.initStatic();
}

IMPL是CardView的實現類,不同的版本有不同的實現。此處進入CardViewApi21Impl里面看看initialize實現。

@Override
public void initialize(CardViewDelegate cardView, Context context,
            ColorStateList backgroundColor, float radius, float elevation, float maxElevation) {
    final RoundRectDrawable background = new RoundRectDrawable(backgroundColor, radius);
    cardView.setCardBackground(background);

    View view = cardView.getCardView();
    view.setClipToOutline(true);
    view.setElevation(elevation);
    setMaxElevation(cardView, maxElevation);
}

可以發現在這里面進行了cardView.setCardBackground調用,而background是根據設置的

backgroundColor = a.getColorStateList(R.styleable.CardView_cardBackgroundColor);

賦值RoundRectDrawable生成而來。
cardView.setCardBackground(background);中的CardView是CardViewDelegate,它是一個接口,所以也要看它的實現類。在CardView類中可以找到:

private final CardViewDelegate mCardViewDelegate = new CardViewDelegate() {
    private Drawable mCardBackground;

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

而上面的setBackgroundDrawable則是進入到了View層面了。

至此已經很清楚了,因為initialize在父類(FrameLayout)的構造方法之后調用,導致最終設置的setBackgroundDrawable會始終覆蓋父類的該方法實現。

說到底,就是個實現的先后覆蓋問題

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容