Android 這些 Drawable 你都會用嗎?Part2

接上文,自從 Android 5.0 發佈開始,能夠看出 Google 愈來愈重視 Android 系統的 UI 設計風格了,最爲明顯的就是提出了 Material Design 設計語言。其中包含了不少 UI 設計的新特性,能夠說爲 Android 系統注入了新鮮的血液。如下文章介紹的都是 Android 5.0 之後引入的 Drawable,一塊兒來看看都有什麼吧!php

11. RippleDrawable

記得谷歌剛發佈 Android 5.0 系統時,用 API 21 的鏡像啓動模擬器後看到一個很明顯的變化就是,不少按鈕上都加了點擊波紋效果,而這些波紋效果就是用 RippleDrawable 來實現的。html

11.1 語法

<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="color" android:radius="dimension">
    <item android:id="@[package:]id/resource_name" android:drawable="@[package:]drawable/drawable_resource" android:top="dimension" android:right="dimension" android:bottom="dimension" android:left="dimension" android:gravity=["top" | "bottom" | "left" | "right" | "center_vertical" | "fill_vertical" | "center_horizontal" | "fill_horizontal" | "center" | "fill" | "clip_vertical" | "clip_horizontal"] />

</ripple>
複製代碼

RippleDrawable 頂層標籤爲 <ripple>,它的兩個屬性的含義分別是:android

android:colorgit

ripple 效果的顏色github

android:radiusapp

ripple 徹底擴散開始的半徑。默認會根據容器大小來計算。eclipse

除此以外,它能夠包含多個 <item> 標籤,每一個 item 表示一個 Drawable,item 的屬性含義分別是:iphone

屬性 含義
android:drawable drawable 資源,可引用現有的的 Drawable
android:id 若是 item 的 id 設置成 @android:id/mask,在初始化時這個 item 不會被繪製,只會在點擊的時候以蒙層的形式限制波紋的範圍在這個 item 以內
android:top、android:right、android:bottom、android:left Drawable 相對於 View 在各個方向的偏移量
android:gravity 尺寸小於容器尺寸時在容器中的擺放位置

11.2 用法示例

定義ide

<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/colorAccent" android:radius="90dp">

    <item android:id="@android:id/mask" android:drawable="@android:color/white" />

    <item android:drawable="@color/colorPrimary" />

</ripple>
複製代碼

使用工具

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="match_parent">

    <Button android:text="I' m a Button" android:layout_width="200dp" android:layout_height="50dp" android:background="@drawable/drawable_ripple" android:id="@+id/button" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
複製代碼

效果圖

ripple-drawable

12. VectorDrawable

從 API 21(Android 5.0) 開始,Google 開始支持使用 Vector,VectorDrawable 應運而生。相比於普通的 Drawable,它具備如下優勢:

  • Vector 圖像能夠自動進行適配,不須要經過分辨率來設置不一樣的圖片
  • Vector 圖像能夠大幅減小圖像的體積,一樣一張圖,用 Vector 來實現,可能只有 PNG 的幾十分之一
  • 使用簡單,不少設計工具均可以直接導出 SVG 圖像,從而轉換成 Vector 圖像
  • 功能強大,不用寫不少代碼就能夠實現很是複雜的動畫
  • 成熟、穩定,目前已經很是普遍地進行使用了

12.1 語法

定義一個 VectorDrawable 的語法以下:

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:name="string" android:width="dimension" android:height="dimension" android:viewportHeight="float" android:viewportWidth="float" android:tint="color" android:tintMode=["add" | "multiply" | "src_top" | "src_in" | "src_over" | "screen"] android:autoMirrored=["true" | "false"] android:alpha="integer" />
    <group android:name="string" android:pivotX="float" android:pivotY="float" android:rotation="integer" android:translationX="float" android:translationY="float" android:scaleX="float" android:scaleY="float">
        <path android:name="string" android:pathData="path" android:fillColor="color" android:fillAlpha="integer" android:strokeColor="color" android:strokeWidth="integer" android:strokeAlpha="integer" android:trimPathStart="float" android:trimPathEnd="float" android:trimPathOffset="float" android:strokeLineCap=["butt" | "round" | "square"] android:strokeLineJoin=["round" | "bevel" | "miter"] android:strokeMiterLimit="integer" android:fillType=["nonZero" | "evenOdd"] />
        <clip-path android:name="string" android:pathData="path" />
    </group>
</vector>
複製代碼

VectorDrawable 的根標籤爲 <vector>,老規矩,先看看它的子元素屬性和含義:

屬性 含義
android:name drawable 的名字
android:width、android:height 內部(intrinsic)寬度和高度。通常使用 dp
android:viewportWidth、android:viewportHeight 矢量圖視圖的寬度和高度。視圖就是矢量圖 path 路徑數據所繪製的虛擬畫布
android:tint 給矢量圖着色
android:tintMode 着色模式。共支持六種模式,默認爲「src_in",詳情請參考 PorterDuff.Mode
android:autoMirrored 自動翻轉
android:alpha 圖片透明度。取值範圍爲 [0, 255]VectorDrawble 支持

一張矢量圖能夠由多個 path 組成,<group> 標籤能夠對多個 path 進行分組,標籤內的屬性值對組內全部 path 都生效,<group> 標籤的各個屬性及其含義分別爲以下:

屬性 含義
android:name 分組的名字
android:pivotX、android:pivotY 縮放和旋轉時候的 X 和 Y 的基準點。該值是相對於 vector 的 viewport 值來指定的
android:translationX、android:translationY X 軸和 Y 軸方向的平移位移。該值一樣是相對於 viewport 值來指定的
android:rotation 旋轉角度
android:scaleX、android:scaleY 分別在 X 軸和 Y 軸方向的縮放比例

接下來就是 <path> 標籤了,<path> 標籤訂了的矢量圖的繪製方法,包括繪製路徑、顏色、邊框樣式等屬性,它的全部屬性及其含義以下:

屬性 含義
android:pathData path 指令。指令格式參考:路徑
android:fillColor path 填充顏色。通常爲純色,API 24 開始支持 Gradient 漸變色,詳情請參考:vectordrawable-gradients-part1vectordrawable-gradients-part1-2/
android:fillAlpha X 軸和 Y 軸方向的平移位移。該值一樣是相對於 viewport 值來指定的
android:fillType path 的填充模式。默認是"noneZero",詳情參考:非零環繞數規則和奇-偶規則Android 關於Path的FillType
android:strokeWidth path 邊框寬度
android:strokeColor path 邊框顏色
android:strokeAlpha path 邊框透明度
android:strokeLineCap path 線頭的形狀。buff 平頭、round 圓頭和 square 方頭。默認爲 buff
android:strokeLineJoin path 拐角的形狀。miter 尖角、 bevel 平角和 round 圓角。默認爲 miter
android:strokeMiterLimit 設置拐角的形狀爲 miter 時,拐角的延長線的最大值。當小到必定程度時,miter 效果將會失效從而變成 bevel 效果
android:trimPathStart 從 path 起始位置截斷路徑的比率。取值範圍爲[0, 1]
android:trimPathEnd 從 path 結束位置截斷路徑的比率。取值範圍爲[0, 1]
android:trimPathOffset path 截取起點的偏移量。取值範圍爲[0, 1],因爲 path 的起點和終點能夠看做的首尾相連的,所以起點和終點是一塊兒發生偏移的

12.2 用法示例

定義

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="200dp" android:height="200dp" android:viewportHeight="600" android:viewportWidth="600">
    <path android:strokeWidth="15" android:strokeColor="#000000" android:strokeLineCap="butt" android:pathData="M5 10 l200 0"/>
    <!--路徑起點/終點日後偏移 10%,重新起點開始日後截斷至 50% 的路徑-->
    <path android:strokeWidth="15" android:strokeColor="#000000" android:strokeLineCap="butt" android:trimPathStart="0.5" android:trimPathOffset="0.1" android:pathData="M5 80 l200 0"/>
    <!--路徑起點/終點日後偏移 50%,重新終點往前截斷至 70% 部分-->
    <!--也能夠理解爲重新起點日後截取至 70% 部分-->
    <path android:strokeWidth="15" android:strokeColor="#000000" android:strokeLineCap="butt" android:trimPathEnd="0.7" android:trimPathOffset="0.5" android:pathData="M5 150 l200 0"/>

    <path android:strokeWidth="15" android:strokeColor="#000000" android:strokeLineCap="square" android:strokeLineJoin="round" android:pathData="M5 230 l200 0 l-100 30"/>
    <path android:strokeWidth="15" android:strokeColor="#000000" android:strokeLineCap="butt" android:strokeLineJoin="miter" android:pathData="M5 290 l200 0 l-100 30"/>
    <path android:strokeWidth="15" android:strokeColor="#000000" android:strokeLineCap="round" android:strokeLineJoin="miter" android:strokeMiterLimit="7" android:pathData="M5 350 l200 0 l-100 30"/>

    <group android:name="name" android:translateX="10" android:translateY="10" android:rotation="90" android:pivotX="300" android:pivotY="300">
        <path android:name="noneZero" android:strokeWidth="2" android:strokeColor="#ffffff" android:fillColor="#3C8FC1" android:pathData="M20 120 a100 100 0 1 1 200 0 a100 100 0 1 1 -200 0 M40 120 a80 80 0 1 1 160 0 a80 80 0 1 1 -160 0"/>
        <path android:name="evenOdd" android:strokeWidth="5" android:strokeColor="#ffffff" android:strokeAlpha="128" android:fillColor="#3C8FC1" android:fillType="evenOdd" android:pathData="M260 120 a100 100 0 1 1 200 0 a100 100 0 1 1 -200 0 M280 120 a80 80 0 1 1 160 0 a80 80 0 1 1 -160 0"/>
    </group>
</vector>
複製代碼

使用

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="match_parent">

    <androidx.appcompat.widget.AppCompatImageView android:layout_width="match_parent" android:layout_height="match_parent" app:srcCompat="@drawable/drawable_vector" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
複製代碼

效果圖

vector-drawable

12.3 兼容性問題

因爲 VectorDrawable 是從 Android 5.0 以後引入的,若是想要在舊設備上運行,就必需要進行兼容性設置,因爲篇幅有限,能夠參考Android Vector曲折的兼容之路,這裏再也不贅述。

13. AnimatedVectorDrawable

當你覺得 VectorDrawable 除了替代傳統圖標別無它用那你就實在 too young 了,與 VetorDrawable 一塊兒誕生的還有 AnimatedVectorDrawable。還記得 VectorDrawable 中的 grouppath 有個 name 屬性嗎?這時候它們就派上用場了,AnimatedVectorDrawable 能夠經過 name 屬性爲 group 和 path 綁定一個屬性動畫,讓這些 path 能夠動起來,作出比較炫酷的動畫效果。在 API 25 以前,由於渲染是在 UI 線程進行的的,所以性能不是很好,加上兼容性問題,目前使用得並很少。自從 API 25 以後,Google 將 AnimatedVectorDrawable 的渲染放在了 RenderThered 中執行,這顯然減輕了很多 UI 線程的壓力,Google 官方描述是:

This means animations in AnimatedVectorDrawable can remain smooth even when there is heavy workload on the UI thread.

所以,若是運行在新設備上,你們大可沒必要操心性能問題了。

13.1 語法

<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@[package:]drawable/drawable_resource" >
     <target android:name="string" android:animation="@[package:]animator/animator_resource" />
 </animated-vector>
複製代碼

AnimatedVectorDrawable 的根標籤爲 <animated-vector>android:drawable 屬性用來指定 VectorDrawable 資源,<target> 標籤將它的子元素 name 屬性指定的 VectorDrawable 中須要添加動畫效果的 path 或者 group 與 animation 屬性中的 animator 資源綁定起來。animation 資源一樣能夠經過標籤訂義或者指向現有的 animator 文件。

13.2 用法示例

下面之前面文章中提到的 Demo 中的一個效果爲例,展現一個 AnimatedVectorDrawable 的基本用法。

定義

須要添加動畫效果的 VectorDrawable,一共有兩個 path:

<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt" android:drawable="@drawable/ic_arrow">

    <target android:name="left">
        <aapt:attr name="android:animation">
            <objectAnimator android:duration="1000" android:interpolator="@android:interpolator/anticipate_overshoot" android:propertyName="translateX" android:repeatCount="infinite" android:repeatMode="reverse" android:valueFrom="0" android:valueTo="-10" android:valueType="floatType"/>
        </aapt:attr>
    </target>

    <target android:name="right">
        <aapt:attr name="android:animation">
            <objectAnimator android:duration="1000" android:interpolator="@android:interpolator/anticipate_overshoot" android:propertyName="translateX" android:repeatCount="infinite" android:repeatMode="reverse" android:valueFrom="0" android:valueTo="10" android:valueType="floatType"/>
        </aapt:attr>
    </target>

</animated-vector>
複製代碼

使用

在代碼中監聽和控制動畫:

class AnimatedVectorDrawableActivity : AppCompatActivity() {
    private lateinit var animatable2Compat: Animatable2Compat

    @RequiresApi(Build.VERSION_CODES.M)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_animated_vector_drawable)

        var image = findViewById<ImageView>(R.id.image)
        var animatedVectorDrawableCompat = AnimatedVectorDrawableCompat.create(this, R.drawable.drawable_animated_vector)
        image.setImageDrawable(animatedVectorDrawableCompat)
        animatable2Compat = image.drawable as Animatable2Compat
        animatable2Compat.registerAnimationCallback(object : Animatable2Compat.AnimationCallback() {
            override fun onAnimationStart(drawable: Drawable?) {
                Log.e("gpj", "onAnimationStart")
            }
            override fun onAnimationEnd(drawable: Drawable?) {
                Log.e("gpj", "onAnimationEnd")
            }
        })
        animatable2Compat.start()
    }

    override fun onDestroy() {
        super.onDestroy()
        animatable2Compat.stop()
    }
}
複製代碼

效果圖

animated-vector-drawable

14 AnimatedStateListDrawable

前面提到的 StateListDrawable 只能使用靜態的資源在不一樣的狀態之間進行切換,一樣的,在 Android 5.0 以後,狀態列表裏可使用動態資源了,它就是 AnimatedStateListDrawable

14.1 語法

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" android:constantSize=["true" | "false"] android:dither=["true" | "false"] android:variablePadding=["true" | "false"] android:autoMirrored=["true" | "false"] android:enterFadeDuration="integer" android:exitFadeDuration="integer">
    <item android:id="@[+][package:]id/resource_name" android:drawable="@[package:]drawable/drawable_resource" android:state_pressed=["true" | "false"] android:state_focused=["true" | "false"] android:state_hovered=["true" | "false"] android:state_selected=["true" | "false"] android:state_checkable=["true" | "false"] android:state_checked=["true" | "false"] android:state_enabled=["true" | "false"] android:state_activated=["true" | "false"] android:state_window_focused=["true" | "false"] />
    <transition android:drawable="@[package:]drawable/drawable_resource" android:fromId="@[package:]id/item_name" android:toId="@[package:]id/item_name" />
</selector>
複製代碼

能夠發現,相對於 StateListDrawable,這裏只多出一個 <transition> 標籤,它的各個屬性含義分別是:

android:drawable

定義或者指向一個 AnimatedVectorDrawable 資源。不難理解,這裏須要指定狀態變化的動畫。

android:fromIdandroid:toId

分別指定狀態變化的起始和結束 item 的 id。詳情請看示例。

14.2 用法示例

定義

<?xml version="1.0" encoding="utf-8"?>
<animated-selector xmlns:android="http://schemas.android.com/apk/res/android" android:visible="true" android:dither="true">

    <!--勾選狀態-->
    <item android:id="@+id/checked" android:drawable="@drawable/ic_checked" android:state_checked="true" />

    <!--未勾選狀態-->
    <item android:id="@+id/unchecked" android:drawable="@drawable/ic_unchecked" />

    <!--未勾選狀態過分到勾選狀態-->
    <transition android:drawable="@drawable/toggle_unchecked_checked" android:fromId="@id/unchecked" android:toId="@id/checked" />

    <!--勾選狀態過分到未勾選狀態-->
    <transition android:drawable="@drawable/toggle_checked_unchecked" android:fromId="@id/checked" android:toId="@id/unchecked" />

</animated-selector>
複製代碼

正常狀態到勾選狀態過分動畫:

<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt" android:drawable="@drawable/ic_checked">

    <!--打勾 path 動畫-->
    <target android:name="tick">
        <aapt:attr name="android:animation">
            <objectAnimator android:duration="200" android:interpolator="@android:interpolator/accelerate_cubic" android:propertyName="trimPathEnd" android:valueFrom="0" android:valueTo="1" android:valueType="floatType" />
        </aapt:attr>
    </target>

    <!--圓圈 path 動畫-->
    <target android:name="circle">
        <aapt:attr name="android:animation">
            <objectAnimator android:duration="500" android:interpolator="@android:interpolator/accelerate_decelerate" android:propertyName="strokeColor" android:valueFrom="#A0A0A0" android:valueTo="#1E9618" android:valueType="intType" />
        </aapt:attr>
    </target>
</animated-vector>

複製代碼

勾選狀態到未勾選狀態過分動畫:

<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt" android:drawable="@drawable/ic_checked">

    <!--打勾 path 動畫-->
    <target android:name="tick">
        <aapt:attr name="android:animation">
            <objectAnimator android:duration="100" android:interpolator="@android:interpolator/decelerate_cubic" android:propertyName="trimPathEnd" android:valueFrom="1" android:valueTo="0" android:valueType="floatType" />
        </aapt:attr>
    </target>

    <!--圓圈 path 動畫-->
    <target android:name="circle">
        <aapt:attr name="android:animation">
            <objectAnimator android:duration="500" android:interpolator="@android:interpolator/accelerate_decelerate" android:propertyName="strokeColor" android:valueFrom="#1E9618" android:valueTo="#A0A0A0" android:valueType="intType" />
        </aapt:attr>
    </target>
</animated-vector>

複製代碼

使用

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="match_parent">

    <androidx.appcompat.widget.AppCompatCheckBox android:layout_width="wrap_content" android:layout_height="50dp" android:button="@drawable/drawable_animated_state_list" android:paddingEnd="8dp" android:paddingLeft="8dp" android:paddingRight="8dp" android:paddingStart="8dp" android:text="I'm a CheckBox" android:textColor="#ff00ff" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

複製代碼

效果圖

animated-state-list-drawable

15. AnimatedImageDrawable

你們都知道,在 Android 8.0 以前,若是咱們要在設備上顯示 GIF 圖,通常都要藉助一些第三方的庫(如 Glide)來實現。記得 Android 8.0 剛發佈的時候,提到一個 ImageDecoder 類,它除了能夠解析 PNG、JEPG 類型的文件以外,還能夠解析 WebP 和 GIF,而 GIF 文件解析出來的正是 AnimatedImageDrawable。因爲目前資源有限,Google 也沒有提供使用 XML 來定義 AnimatedImageDrawable 的例子,這裏就在 Kotlin 代碼裏面介紹 AnimatedImageDrawable 和 ImageDecoder 的簡單使用。

使用

@RequiresApi(Build.VERSION_CODES.P)
class AnimatedImageDrawableActivity : AppCompatActivity() {
    var mAnimatedImageDrawable: AnimatedImageDrawable? = null

    private val cacheAsset: CacheAsset by lazy {
        CacheAsset(this)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_animated_image_drawable)

        button.setOnClickListener {
            ImageDecoder.createSource(cacheAsset.file("gif_example.gif")).also { source ->
                ImageDecoder.decodeDrawable(source).also { drawable ->
                    image.setImageDrawable(drawable)
                    if(drawable is AnimatedImageDrawable) {
                        mAnimatedImageDrawable = drawable
                        drawable.start()
                    }
                }
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        mAnimatedImageDrawable?.run { stop() }
    }
}
複製代碼

效果圖

animated-image-drawable

到此,常見的 Drawable 的用法已經所有講完了,若是要加深理解,建議把 Demo 跑一遍。

附:文章中的 Demo 地址:github.com/guanpj/Draw…

相關文章
相關標籤/搜索