Android 使用 CardView 輕鬆實現卡片式設計

亦楓發表於2016-12-23

Material design中有一種很個性的設計概念:卡片式設計(Cards),這種設計與傳統的List Item有所區別,Cards包含更多的內容元素和擁有自己的UI特徵,關於Cards的設計規範可以參考官網介紹:

material.google.com/components/…

為了更好地實現這種 Cards UI 的設計,Google在v7包中引進了一種全新的控制元件:CardVew,本文將從開發的角度介紹CardView的一些常見使用細節。

Google用一句話介紹了CardView:一個帶圓角和陰影背景的FrameLayout。CardView在Android Lollipop(API 21)及以上版本的系統中適配較好,本文我們以一個具體的例子來學習CardView的基本使用和注意事項,效果圖如下:

Android 使用 CardView 輕鬆實現卡片式設計
CardView-samples-01.png

上圖展示的是一個list列表,列表中的item使用了卡片式設計,主要利用CardView控制元件實現,為了精簡文章內容,這裡我們將item佈局中的核心程式碼羅列出來,加以分析:

<?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">

    <android.support.v7.widget.CardView
        tools:targetApi="lollipop"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:stateListAnimator="@drawable/lift_on_touch"
        android:layout_marginLeft="@dimen/dp_8"
        android:layout_marginRight="@dimen/dp_8"
        android:layout_marginBottom="@dimen/dp_8"
        android:clickable="true"
        android:foreground="?android:attr/selectableItemBackground"
        app:cardCornerRadius="@dimen/dp_4"
        app:cardUseCompatPadding="true"
        app:cardPreventCornerOverlap="false">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <!-- image、text等其他內容 -->
            ......

        </RelativeLayout>

    </android.support.v7.widget.CardView>

</LinearLayout>複製程式碼

可以看出,核心部分在於CardView的屬性使用,下面我們針對幾個特殊的屬性逐一分析,深化了解。

排版技巧


前面我們說過,CardView從本質上屬於FrameLayout,而CardView通常包含了較多的內容元素,為了方便地排版佈局中的各個元素,一般藉助於其他基本佈局容器,比如這裡我們使用了一個RelativeLayout作為CardView的唯一Child。

陰影Padding


在Android Lollipop之前的系統,CardView會自動新增一些額外的padding空間來繪製陰影部分,這也導致了以Lollipop為分界線的不同系統上CardView的尺寸大小不同。為了解決這個問題,有兩種方法:第一種,使用不同API版本的dimension資源適配(也就是藉助values和values-21資料夾中不同的dimens.xml檔案);第二種,就是使用setUseCompatPadding屬性,設定為true(預設值為false),讓CardView在不同系統中使用相同的padding值。

圓角覆蓋


這也是一個解決系統相容的問題。在pre-Lollipop平臺(API 21版本之前)上,CardView不會裁剪內容元素以滿足圓角需求,而是使用新增padding的替代方案,從而使內容元素不會覆蓋CardView的圓角。而控制這個行為的屬性就是cardPreventCornerOverlap,預設值為true。在本例中我們設定了該屬性為false。這裡我們看一下,在pre-Lollipop平臺中,不同cardPreventCornerOverlap值的效果對比圖(左false,右true):

Android 使用 CardView 輕鬆實現卡片式設計
CardView-samples-02.png

顯然,預設值下自動新增padding的方式不可取,所以需要設定該屬性值為false。需要注意的一點是,該屬性的設定在Lollipop及以上版本的系統中沒有任何影響,除非cardUseCompatPadding的值為true。

Ripple效果


Cards一般都是可點選的,為此我們使用了foreground屬性並使用系統的selectableItemBackground值,同時設定clickable為true(如果在java程式碼中使用了cardView.setOnClickListener,就可以不用寫clickable屬性了),從而達到在Lollipop及以上版本系統中實現點選時的漣漪效果(Ripple),如圖:

Android 使用 CardView 輕鬆實現卡片式設計
CardView-samples-03.gif

在pre-Lollipop版本中,則是一個普通的點選變暗的效果,這裡就不截圖展示了,如果想改變老版本的點選效果,也可以通過版本相容的方式另行修改。

lift-on-touch


根據官網Material motion部分對互動動作規範的指導,Cards、Button等檢視應該有一個觸控抬起(lift-on-touch)的互動效果,也就是在三維立體空間上的Z軸發生位移,從而產生一個陰影加深的效果,與Ripple效果共同使用,官網給了一個很好的示例圖:

Android 使用 CardView 輕鬆實現卡片式設計
CardView-samples-04.gif

在實現這個效果也很簡單,可以在res/drawable目錄下建立一個lift_on_touch.xml檔案,內容如下:

<?xml version="1.0" encoding="utf-8"?>
<!-- animate the translationZ property of a view when pressed -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:state_enabled="true"
        android:state_pressed="true">
        <set>
            <objectAnimator
                android:duration="@android:integer/config_shortAnimTime"
                android:propertyName="translationZ"
                android:valueTo="6dp"
                android:valueType="floatType"/>
        </set>
    </item>
    <item>
        <set>
            <objectAnimator
                android:duration="@android:integer/config_shortAnimTime"
                android:propertyName="translationZ"
                android:valueTo="0"
                android:valueType="floatType"/>
        </set>
    </item>
</selector>複製程式碼

即通過屬性動畫動態改變translationZ值,沿著Z軸,從0dp到6dp變化。這裡的6dp值也是有出處的,參考Google I/O 2014 appAssign Elevation to Your Views。然後將其賦值給android:stateListAnimator屬性即可。由於stateListAnimator屬性只適用於Lollipop及以上版本,為了隱藏xml中的版本警告,可以指定tools:targetApi="lollipop"

關於這個功能,需要補充說明一點。這裡的lift_on_touch.xml,嚴格意義上來講,屬於anim資源,同時適用於API 21及以上版本,所以按道理上來講應該將其放置在res/anim-v21目錄下,然後使用@anim/lift_on_touch賦值給stateListAnimator屬性,而不是例子中的@drawable/lift_on_touch方法。但是放置在res/anim-v21目錄下會產生一個“錯誤”提示:

XML file should be in either "animator" or "drawable",not "anim"

雖然這個“錯誤”不影響編譯執行,但是對於追求完美主義的程式設計師們來說還是礙眼,所以本例中我選擇將其放在了res/drawable目錄下,大家可以自行斟酌使用。

關於對lift-on-touch效果的理解,YouToBe網站有個視訊解說,感興趣的話可以參看看,地址如下:

DesignBytes: Paper and Ink: The Materials that Matter

總結說明


CardView還有一些其他屬性可供使用,比如cardElevation設定陰影大小,contentPadding代替普通android:padding屬性等,比較基礎,本文就不一一介紹了,大家可以在官網上參考學習。從上面的介紹可以看出,在使用CardView時基本上都會用到一些標準配置的屬性,我們可以藉助style屬性,將其封裝到styles.xml檔案中,統一管理,比如:

<style name="AppCardView" parent="@style/CardView.Light">
        <item name="cardPreventCornerOverlap">false</item>
        <item name="cardUseCompatPadding">true</item>
        <item name="android:foreground">?attr/selectableItemBackground</item>
        <item name="android:stateListAnimator" tools:targetApi="lollipop">@anim/lift_up</item>
        ......
</style>複製程式碼

最後,附上本文案例專案的GitHub地址:

github.com/Mike-bel/MD…

歡迎關注我


本文由 亦楓 創作並首發於 亦楓的個人部落格 ,同步授權微信公眾號:技術鳥(NiaoTech),歡迎關注。

Android 使用 CardView 輕鬆實現卡片式設計

相關文章