CardView 簡介
本文鏈接:
https://blog.csdn.net/ShawnXiaFei/article/details/81568537
CardView 是 Google 官方發布 MD 風格卡片布局控件,開發者可以很方便的使用它將布局做成卡片效果。在使用 CardView 之前,多少應該對它有一定的了解,下面將對其實現做簡單的介紹。
CardView 是 Google 官方發布 MD 風格卡片布局控件,開發者可以很方便的使用它將布局做成卡片效果。在使用 CardView 之前,多少應該對它有一定的了解,下面將對其實現做簡單的介紹。
自定義屬性
CardView 繼承自 FrameLayout,並在其基礎上添加了圓角和陰影等效果。為了更方便的使用這些效果,Google 提供了一系列的自定義屬性,這些屬性在類注釋中都有列出來,如下:
CardView 繼承自 FrameLayout,並在其基礎上添加了圓角和陰影等效果。為了更方便的使用這些效果,Google 提供了一系列的自定義屬性,這些屬性在類注釋中都有列出來,如下:
/**
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardBackgroundColor
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardCornerRadius
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardElevation
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardMaxElevation
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardUseCompatPadding
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardPreventCornerOverlap
* @attr ref android.support.v7.cardview.R.styleable#CardView_contentPadding
* @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingLeft
* @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingTop
* @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingRight
* @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingBottom
*/
public class CardView extends FrameLayout {
這些屬性的作用和用法如下:
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardBackgroundColor
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardCornerRadius
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardElevation
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardMaxElevation
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardUseCompatPadding
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardPreventCornerOverlap
* @attr ref android.support.v7.cardview.R.styleable#CardView_contentPadding
* @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingLeft
* @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingTop
* @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingRight
* @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingBottom
*/
public class CardView extends FrameLayout {
這些屬性的作用和用法如下:
CardView_cardBackgroundColor 設置背景色
CardView_cardCornerRadius 設置圓角大小
CardView_cardElevation 設置z軸陰影
CardView_cardMaxElevation 設置z軸最大高度值
CardView_cardUseCompatPadding 是否使用CompadPadding
設置內邊距,V21+的版本和之前的版本具有一樣的計算方式。
部分機器不開這個屬性會導致卡片效果“消失”,如榮耀6(6.0系統)。
CardView_cardCornerRadius 設置圓角大小
CardView_cardElevation 設置z軸陰影
CardView_cardMaxElevation 設置z軸最大高度值
CardView_cardUseCompatPadding 是否使用CompadPadding
設置內邊距,V21+的版本和之前的版本具有一樣的計算方式。
部分機器不開這個屬性會導致卡片效果“消失”,如榮耀6(6.0系統)。
CardView_cardPreventCornerOverlap 是否使用PreventCornerOverlap
在V20和之前的版本中添加內邊距,這個屬性為了防止內容和邊角的重疊
在V20和之前的版本中添加內邊距,這個屬性為了防止內容和邊角的重疊
CardView_contentPadding 內部邊距,子View與CardView的距離
CardView_contentPaddingLeft 內部左側邊距
CardView_contentPaddingTop 內部頂部邊距
CardView_contentPaddingRight 內部右側邊距
CardView_contentPaddingBottom 內部底部邊距
CardViewImpl 接口
跟着源碼往下看,接下來就是做多 API 版本適配的代碼,這段代碼使得不同版本的 Android 能達到相同或者相似的效果,盡可能的做到了兼容。這里 CardViewImpl 的幾個子類實現請自行查閱,這里不多說了。
CardView_contentPaddingLeft 內部左側邊距
CardView_contentPaddingTop 內部頂部邊距
CardView_contentPaddingRight 內部右側邊距
CardView_contentPaddingBottom 內部底部邊距
CardViewImpl 接口
跟着源碼往下看,接下來就是做多 API 版本適配的代碼,這段代碼使得不同版本的 Android 能達到相同或者相似的效果,盡可能的做到了兼容。這里 CardViewImpl 的幾個子類實現請自行查閱,這里不多說了。
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();
}
上面這段代碼很有意思,首先它是static{}包裹的靜態代碼塊,而靜態代碼塊是屬於類的,只會在類被加載到內存時執行一次,以后不管如何實例化,new 出多少實例對象,靜態代碼塊都不會再執行了。其次,IMPL 對象是是static final修飾的,這就意味着 IMPL 對象也是屬於類,並且只能被初始化一次。
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();
}
上面這段代碼很有意思,首先它是static{}包裹的靜態代碼塊,而靜態代碼塊是屬於類的,只會在類被加載到內存時執行一次,以后不管如何實例化,new 出多少實例對象,靜態代碼塊都不會再執行了。其次,IMPL 對象是是static final修飾的,這就意味着 IMPL 對象也是屬於類,並且只能被初始化一次。
final 修飾的對象,若是基本類型+String,則其值不能修改;若是復雜類型,則其引用不能修改。
基本類型+String的值、復雜類型的引用,存儲在棧中;復雜類型的實體類容存儲在堆中。final 是指明棧中的類容不能修改。
基本類型+String的值、復雜類型的引用,存儲在棧中;復雜類型的實體類容存儲在堆中。final 是指明棧中的類容不能修改。
那么,一旦 CardView 被加載到內存,IMPL 對象(地址)就不會再變化了,也就會被后續系統中所有實例化的 CardView 對象共享。而縱觀整個 CardView 的源碼,我們會發現 IMPL 對象幾乎出現在 CardView 的所有方法中,那么是不是系統中所有的 CardView 實例化對象都會有相同的表現呢?
實際使用中我們發現,即便一個APP內部的多個CardView也能有不同的表現,更不用說整個系統上的所有APP了,那這又是怎么做到的呢?
我們接着看下 CardViewImpl 接口的定義:
/**
* Interface for platform specific CardView implementations.
*/
interface CardViewImpl {
void initialize(CardViewDelegate cardView, Context context, ColorStateList backgroundColor,
float radius, float elevation, float maxElevation);
void setRadius(CardViewDelegate cardView, float radius);
float getRadius(CardViewDelegate cardView);
void setElevation(CardViewDelegate cardView, float elevation);
float getElevation(CardViewDelegate cardView);
void initStatic();
void setMaxElevation(CardViewDelegate cardView, float maxElevation);
float getMaxElevation(CardViewDelegate cardView);
float getMinWidth(CardViewDelegate cardView);
float getMinHeight(CardViewDelegate cardView);
void updatePadding(CardViewDelegate cardView);
void onCompatPaddingChanged(CardViewDelegate cardView);
void onPreventCornerOverlapChanged(CardViewDelegate cardView);
void setBackgroundColor(CardViewDelegate cardView, @Nullable ColorStateList color);
ColorStateList getBackgroundColor(CardViewDelegate cardView);
}
不難發現,這里面幾乎所有方法都有一個參數——CardViewDelegate,在CardView的方法調用時,會通過早已初始化的 IMPL 調用對應的方法,並傳入一個mCardViewDelegate對象,並通過它進行下一步操作。如:
* Interface for platform specific CardView implementations.
*/
interface CardViewImpl {
void initialize(CardViewDelegate cardView, Context context, ColorStateList backgroundColor,
float radius, float elevation, float maxElevation);
void setRadius(CardViewDelegate cardView, float radius);
float getRadius(CardViewDelegate cardView);
void setElevation(CardViewDelegate cardView, float elevation);
float getElevation(CardViewDelegate cardView);
void initStatic();
void setMaxElevation(CardViewDelegate cardView, float maxElevation);
float getMaxElevation(CardViewDelegate cardView);
float getMinWidth(CardViewDelegate cardView);
float getMinHeight(CardViewDelegate cardView);
void updatePadding(CardViewDelegate cardView);
void onCompatPaddingChanged(CardViewDelegate cardView);
void onPreventCornerOverlapChanged(CardViewDelegate cardView);
void setBackgroundColor(CardViewDelegate cardView, @Nullable ColorStateList color);
ColorStateList getBackgroundColor(CardViewDelegate cardView);
}
不難發現,這里面幾乎所有方法都有一個參數——CardViewDelegate,在CardView的方法調用時,會通過早已初始化的 IMPL 調用對應的方法,並傳入一個mCardViewDelegate對象,並通過它進行下一步操作。如:
/**
* Updates the background color of the CardView
*
* @param color The new color to set for the card background
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardBackgroundColor
*/
public void setCardBackgroundColor(@ColorInt int color) {
IMPL.setBackgroundColor(mCardViewDelegate, ColorStateList.valueOf(color));
}
CardViewDelegate 代理
接下來就簡單說下 CardViewDelegate 對象是如何工作的。
首先是定義,這一系列方法定義與 CardView 提供的方法迷之相似。
* Updates the background color of the CardView
*
* @param color The new color to set for the card background
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardBackgroundColor
*/
public void setCardBackgroundColor(@ColorInt int color) {
IMPL.setBackgroundColor(mCardViewDelegate, ColorStateList.valueOf(color));
}
CardViewDelegate 代理
接下來就簡單說下 CardViewDelegate 對象是如何工作的。
首先是定義,這一系列方法定義與 CardView 提供的方法迷之相似。
/**
* Interface provided by CardView to implementations.
* <p>
* Necessary to resolve circular dependency between base CardView and platform implementations.
*/
interface CardViewDelegate {
void setCardBackground(Drawable drawable);
Drawable getCardBackground();
boolean getUseCompatPadding();
boolean getPreventCornerOverlap();
void setShadowPadding(int left, int top, int right, int bottom);
void setMinWidthHeightInternal(int width, int height);
View getCardView();
}
然后 CardViewDelegate 的實例化是在 CardView 中進行的,在 CardView 代碼末尾可看到其實現:
* Interface provided by CardView to implementations.
* <p>
* Necessary to resolve circular dependency between base CardView and platform implementations.
*/
interface CardViewDelegate {
void setCardBackground(Drawable drawable);
Drawable getCardBackground();
boolean getUseCompatPadding();
boolean getPreventCornerOverlap();
void setShadowPadding(int left, int top, int right, int bottom);
void setMinWidthHeightInternal(int width, int height);
View getCardView();
}
然后 CardViewDelegate 的實例化是在 CardView 中進行的,在 CardView 代碼末尾可看到其實現:
private final CardViewDelegate mCardViewDelegate = new CardViewDelegate() {
······
}
這里沒有使用 static,那么這個 mCardViewDelegate 對象在 CardView 實例化時也會 new 一個新的,然后通過不同 mCardViewDelegate 對象,就做到了一個系統上不同CardView有不同表現。
······
}
這里沒有使用 static,那么這個 mCardViewDelegate 對象在 CardView 實例化時也會 new 一個新的,然后通過不同 mCardViewDelegate 對象,就做到了一個系統上不同CardView有不同表現。
最后這一系列操作的示意圖大致是這樣的:

這一系列的操作,將 CardView 的實現分成多個類,各個類只處理和自己相關的邏輯,簡化了 CardView 自身邏輯。同時,能很方便的做到多平台適配,不需要將各個平台特定的實現代碼全部擠在 CardView 內部。而且能很方便進行擴展,如添加新平台、新特性,而且不會對 CardView 的代碼造成很大改動,只需要添加新的 IMPL,並在static{}中添加新分支即可。
CardView 使用
添加依賴庫
CardView 是隨 MD 推出的補充庫,並非 SDK 的內容,因此在使用 CardView 時,必須先引入依賴庫:
添加依賴庫
CardView 是隨 MD 推出的補充庫,並非 SDK 的內容,因此在使用 CardView 時,必須先引入依賴庫:
implementation 'com.android.support:cardview-v7:xx.x.x'
1
使用 CardView 布局
前面已經介紹了,CardView 繼承自 FrameLayout,那么我們就可以直接在布局中,將CardView作為容器,放入其它控件即可。
如果已有現成的布局,想再引入卡片效果,也只需要在已有布局最外層添加 CardView 即可。
舉個栗子:
1
使用 CardView 布局
前面已經介紹了,CardView 繼承自 FrameLayout,那么我們就可以直接在布局中,將CardView作為容器,放入其它控件即可。
如果已有現成的布局,想再引入卡片效果,也只需要在已有布局最外層添加 CardView 即可。
舉個栗子:
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/dp8"
android:orientation="vertical"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="@dimen/dp8"
app:cardElevation="@dimen/dp8"
app:cardUseCompatPadding="true"
app:contentPadding="@dimen/dp8">
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/dp8"
android:orientation="vertical"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="@dimen/dp8"
app:cardElevation="@dimen/dp8"
app:cardUseCompatPadding="true"
app:contentPadding="@dimen/dp8">
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="測試\n卡片\n效果"
android:textSize="@dimen/sp32" />
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="測試\n卡片\n效果"
android:textSize="@dimen/sp32" />
</android.support.v7.widget.CardView>
前面介紹屬性已經說了,部分機器(如榮耀6,6.0系統)如果不打開 cardUseCompatPadding,將不會呈現出卡片效果。因此建議打開。
前面介紹屬性已經說了,部分機器(如榮耀6,6.0系統)如果不打開 cardUseCompatPadding,將不會呈現出卡片效果。因此建議打開。
效果如下:
類似效果

要實現卡片效果,除了用 CardView 以外,還有其它方法,比如使用shape+elevation。
舉個栗子:
先定義一個shape,用作背景。
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">//shape樣式
//圓角
<corners android:radius="@dimen/dp8" />
//邊框
<stroke
android:width="1dp"
android:color="@color/divider" />
//內邊距
<padding
android:bottom="@dimen/dp8"
android:left="@dimen/dp8"
android:right="@dimen/dp8"
android:top="@dimen/dp8" />
//內部填充
<solid android:color="@color/white" />
</shape>
然后在布局中引用:
android:shape="rectangle">//shape樣式
//圓角
<corners android:radius="@dimen/dp8" />
//邊框
<stroke
android:width="1dp"
android:color="@color/divider" />
//內邊距
<padding
android:bottom="@dimen/dp8"
android:left="@dimen/dp8"
android:right="@dimen/dp8"
android:top="@dimen/dp8" />
//內部填充
<solid android:color="@color/white" />
</shape>
然后在布局中引用:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|center_horizontal"
android:layout_margin="@dimen/dp8"
android:background="@drawable/shape"
android:elevation="@dimen/dp8" //z軸高度,控制陰影效果
android:text="測試\n卡片\n效果"
android:textSize="@dimen/sp32" />
運行效果:
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|center_horizontal"
android:layout_margin="@dimen/dp8"
android:background="@drawable/shape"
android:elevation="@dimen/dp8" //z軸高度,控制陰影效果
android:text="測試\n卡片\n效果"
android:textSize="@dimen/sp32" />
運行效果:

可以看到,與前面使用 CardView 的效果幾乎一樣。
但是,elevation屬性也是隨MD出來的,它只支持 5.0+(也就是API21+)的系統。因此,如果要卡片效果能想兼容低版本系統,那還是應該優先考慮用 CardView。
————————————————
但是,elevation屬性也是隨MD出來的,它只支持 5.0+(也就是API21+)的系統。因此,如果要卡片效果能想兼容低版本系統,那還是應該優先考慮用 CardView。
————————————————