Android動畫:行雲流水的矢量圖標動畫

前言

咱們在平常使用各類app的時候,會發現原來越多下面這類型的矢量圖標動畫。圖標動畫是material design所推薦的圖標效果。固然對我來講,炫酷的效果就是我學習矢量圖標動畫的一個很充分理由。 html

adp-delightful-details
adp-delightful-details

VectorDrawable

SVG和VectorDrawable
  • 矢量圖:矢量圖和傳統的png、jpg等圖片格式,是典型的漁和魚的區別。矢量圖存儲的是圖片畫出來的方法,而不是像素點的排列,因此不管矢量圖放大多少倍,只要按照等比例縮放後的路徑把圖標畫出來便可,不存在馬賽克的問題。咱們電腦中顯示的文字就是這麼一個原理。
  • svg是最多見的矢量圖格式,而在Android裏面,咱們使用的是VectorDrawable。
  • 通常來講,矢量圖的生成是不須要咱們去關心的,若是須要本身去找矢量圖的話,能夠去iconfont找一找。
  • SVG2VectorDrawable是一個頗有用的AndroidStudio上面的插件,幫助咱們把svg轉化爲vectorDrawable。
VectorDrawable文件和svg指令

瞭解一些svg指令,知道矢量圖是怎麼畫出來的,對咱們之後的開發有好處,咱們能夠從一個簡單的VecotrDrawable文件入手。android

一個綠色的小勾

<?xml version="1.0" encoding="utf-8"?>
<!--res/drawable/vd_check.xml-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="100dp"
    android:height="100dp"
    android:viewportHeight="24"
    android:viewportWidth="24">
    <path
        android:name="check"
        android:pathData="M4,10 L9,16 L20,4"
        android:strokeColor="#35931d"
        android:strokeWidth="3" />
</vector>
複製代碼

這個綠色和諧的小勾是我用上面的vd_check文件畫出來的,咱們來解讀下這個文件:git

  1. vector標籤:表示這是一個矢量圖。
    • viewportHeight/viewWidth:矢量圖的長寬,以後畫圖也是按此長寬來畫。圖標的左上角是(0,0),右下角是(viewWidth,viewHeight)。
  2. group標籤:group有一些path沒有的屬性,若是要用這些屬性作動畫,那就只能path外嵌套多一層group標籤了。
    • name:動畫會經過name尋找到此對象。
    • rotation|scaleX|pivotX..:這些屬性都很熟悉了吧
  3. path標籤:連續的線或面,矢量圖就是有一個或多個path組成的。
    • name:動畫會經過name尋找到此對象。
    • storkeColor: 線段的顏色。
    • strokeWidth: 線段的寬度。
    • strokeAlpha: 線段的透明度。
    • strokeLineCap: 線段末端的樣式 butt(斷開)|round(圓角)|square(直角)
    • fillColor: 填充的顏色。
    • fillAlpha:填充透明度。
  4. pathData屬性:pathData是Path的一個屬性,他裏面即是用來描繪path的svg語言。咱們只須要認識幾個關鍵詞就能夠看懂了。
關鍵字 解釋
M x,y 把畫筆移動到從(x,y)這個點。通常表明着一段path的開始。
L x,y 畫一條鏈接到(x,y)的線段。
Q x1,y1 x,y 貝塞爾二階曲線。通過(x1,y1)到達(x,y)。
C x1,y1 x2,y2 x,y 貝賽爾三階線。通過(x1,y1)和(x2,y2)到達(x,y)。
Z 閉合path。畫一段到起點的線段。

如今回過頭看和諧小勾的pathData,就很簡單了:github

M4,10 L9,16 L20,4bash

從(4,10)開始,畫一條到(9,16)的線段,再畫一條到(20,4)的線段。一頓一拉,綠色小勾躍然紙上。app

固然,若是遇到比小勾更加複雜的狀況,好比一個完美的心形,或者廣州塔的圖標,那仍是乖乖的找ui幫你生成svg比較好。svg

animated-vector

既然咱們有了矢量圖,那就讓矢量圖動起來吧。提及作動畫,固然是屬性動畫來一發啦!學習

  • group和path各有一些獨自的屬性,因此按需去嵌套group吧。
  • 注意加name屬性,咱們的動畫會經過name去找到對應的做用對象。 這是我修改後的vector,增長了一個group。
<?xml version="1.0" encoding="utf-8"?><!--vd_check.xml-->
<!--vd_check.xml-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="100dp"
    android:height="100dp"
    android:viewportHeight="24"
    android:viewportWidth="24">
    <group
        android:name="g_rotation"
        android:pivotX="12"
        android:pivotY="12"
        android:rotation="0">
            <path
                android:name="check"
                android:pathData="M4,10 L9,16 L20,4"
                android:strokeAlpha="1.0"
                android:strokeColor="@color/colorPrimary"
                android:strokeLineCap="round"
                android:strokeWidth="1" />
    </group>
</vector>
複製代碼

咱們要加什麼動畫呢?嗯、、旋轉,透明度,顏色,我全都要!動畫

<?xml version="1.0" encoding="utf-8"?>
<!--/res/animator/rotation_round.xml-->
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:propertyName="rotation"
    android:valueFrom="0"
    android:valueTo="360" />
複製代碼
<?xml version="1.0" encoding="utf-8"?>
<!--/res/animator/alpha_animator.xml-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="sequentially">
    <objectAnimator
        android:duration="500"
        android:propertyName="strokeAlpha"
        android:valueFrom="1f"
        android:valueTo="0f" />
    <objectAnimator
        android:duration="500"
        android:propertyName="strokeAlpha"
        android:valueFrom="0f"
        android:valueTo="1f" />
</set>
複製代碼
<?xml version="1.0" encoding="utf-8"?>
<!--res/animator/stroke_color_animator.xml-->
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:propertyName="strokeColor"
    android:valueFrom="@color/colorPrimary"
    android:valueTo="@color/colorAccent"
    android:duration="1000"/>
複製代碼

AnimatedVector華麗登場,把vector和動畫文件黏合在一塊兒。使用起來很簡單,先經過drawable屬性指定vector,而後經過target標籤把動畫和對象綁定在一塊兒。ui

<?xml version="1.0" encoding="utf-8"?>
<!--avd_check.xml-->
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/vd__check">
    <target
        android:name="g_rotation"
        android:animation="@animator/rotation_around" />
    <target
        android:name="check"
        android:animation="@animator/stroke_color_animator" />
    <target
        android:name="check"
        android:animation="@animator/alpha_animator" />
</animated-vector>
複製代碼

最後須要在代碼中觸發。把avd_check.xml當作圖片賦給ImageView,須要調用動畫時,獲得ImageView的drawable,強轉爲Animatable後,調用start()方法。

<ImageView
   android:id="@+id/img_check"
   android:layout_width="48dp"
   android:layout_height="48dp"
   app:srcCompat="@drawable/avd_check" />
複製代碼

··· img_check.setOnClickListener { val drawable = img_check.drawable (drawable as Animatable).start() } ···

而後效果就出來了。

--

固然,若是你只是求方便的話,動畫不須要單獨寫一個文件,直接寫在target標籤裏面也是能夠的。

trimPath 路徑裁剪

trimPath其實和上面的動畫如出一轍,只是運用了幾個矢量圖標特有的屬性而已。咱們先來看看trimPath能作什麼。

adp-delightful-details

trimPath一共有三個相關的屬性:trimPathStart,trimPathEnd,trimPathOffset,都是float類型的數值,數值範圍從0到1。分別表示path從哪裏開始,到哪裏結束,距離起點多遠。至於怎麼用,就看咱們的想象力了。

接下來,用咱們的小勾來作下實驗吧。

照舊也是須要寫一個動畫文件

<?xml version="1.0" encoding="utf-8"?>
<!--trim_path_animator.xml-->
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:interpolator="@android:interpolator/linear"
    android:propertyName="trimPathEnd"
    android:valueFrom="0.0"
    android:valueTo="1.0"
    android:valueType="floatType" />
複製代碼

修改一下animatedVector文件

<?xml version="1.0" encoding="utf-8"?><!--avd_check.xml-->
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/vd__check">
    <target
        android:name="check"
        android:animation="@animator/trim_path_animator" />
</animated-vector>
複製代碼

bang!

2018.10.23_15.06.09.gif

ps:pathTrim只能對一條完整的path作動畫,若是你的pathdata是有斷開的,好比(省略座標):「M,L,L M,L Z」,出現了兩個m,那path就會分紅了兩段,這時候pathTrim只會做用於第一條線段了。

Morphing paths

重頭戲來了,path變幻。咱們想想,既然strokeAplha,rotation這些屬性都能作動畫,那pathData這個屬性,確定也能作動畫啦。因而有了下面這些效果。

adp-delightful-details(資源缺少,重複利用)
*
adp-delightful-details

簡單來講就是給屬性動畫裏面的valueFrom和valueTo分別寫兩條不同的path,那path就會自動變幻了。 須要注意的是,兩條path的繪製指令須要在數量和結構上都相同。好比第一條path的指令(省略了座標)是"M,L,L,C,Z",那第二條path的指令也應該是"M,L,L,C,Z"這種形式。

好,咱們能夠來試一試手。因爲如今的勾的指令太少了,很差發揮個人小宇宙,因此我多加了幾個指令。而目標,就是把小勾變成小圓圈吧。因而乎我就創造瞭如下兩條path。他們都用了一個m指令和4個c指令(是的,c只能也能畫直線的)。 爲了方便管理,我把這兩個path都放在一個xml裏面了。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="check_path">M4,10 C10,16 10,16 10,16 C13,13 13,13 13,13 C16,10 16,10 16,10 C20,6 20,6 20,6</string>
    <string name="circle_path">M4,12 C4,7.6 7.6,4 12,4 C16.4,4 20,7.6 20,12 C20,16.4 16.4,20 12,20 C 7.6,20 4,16.4 4,12</string>
</resources>
複製代碼

而後也是動畫和animatedVector:

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="500"
    android:interpolator="@android:interpolator/linear"
    android:propertyName="pathData"
    android:valueFrom="@string/check_path"
    android:valueTo="@string/circle_path"
    android:valueType="pathType" />
複製代碼
<?xml version="1.0" encoding="utf-8"?><!--avd_check.xml-->
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/vd__check">
    <target
        android:name="check"
        android:animation="@animator/path_animator" />
</animated-vector>
複製代碼

接下來,噔噔噔噔噔。

2018.10.22_15.38.48.gif
咳咳。因爲時間關係,咱們就不在外觀上深究了,你們意會意會。

可是你會發現,個人勾變成圓以後,再也變不回來了,動畫不能倒過來作。因而乎咱們須要引入最後一個概念,animatedSelecotr。

animated-selector

animated-selector容許定義有多個vector,根據不一樣狀態使用不一樣的vector,而且經過animated-vector定義不一樣vector以前切換的動畫。 因此咱們接下來的步驟是:

  1. 定義兩個vector:勾和圓
  2. 定義兩個animated-vector:勾轉化爲圓,圓轉化爲勾
  3. 定義animated-selector把上述的文件組合起來。

動手動手: 圓的vector文件。和勾的大同小異。注意,我把name改爲了circle。

<?xml version="1.0" encoding="utf-8"?><!--vd_circle.xml-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="100dp"
    android:height="100dp"
    android:viewportHeight="24"
    android:viewportWidth="24">
    <path
        android:name="circle"
        android:pathData="@string/circle_path"
        android:strokeAlpha="1.0"
        android:strokeColor="@color/colorPrimary"
        android:strokeLineCap="round"
        android:strokeWidth="1" />
</vector>
複製代碼

圓和勾的相互轉化,須要兩個文件。因爲勾轉化爲圓已經在上面寫過了(avd_check.xml,爲了改名副其實,已經更名爲avd_check2circl.xml)。這裏是圓轉化爲勾。能夠看到,動畫是能夠直接寫在animated-vector裏面的。

<?xml version="1.0" encoding="utf-8"?>
<!--huan -->
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:aapt="http://schemas.android.com/aapt"
    android:drawable="@drawable/vd_circle">
    <target android:name="circle">
        <aapt:attr name="android:animation">
            <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
                android:duration="500"
                android:interpolator="@android:interpolator/fast_out_slow_in"
                android:propertyName="pathData"
                android:valueFrom="@string/circle_path"
                android:valueTo="@string/check_path"
                android:valueType="pathType" />
        </aapt:attr>
    </target>
</animated-vector>
複製代碼

接下來就剩下animated-selector了。

  • 兩個item分別指定兩個vector,而且經過state_checked表示兩種狀態。實際上還有stated_checkable,state_selected等系統定義的狀態,也能夠執行定義新的狀態變量。
  • transition則是表示不一樣vector之間轉換的動畫。屬性很清晰明瞭,fromid和toId表示變換先後的兩個item的id。drawable是antemator-vector。
<?xml version="1.0" encoding="utf-8"?>
<animated-selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/check"
        android:drawable="@drawable/vd__check"
        android:state_checked="true" />
    <item
        android:id="@+id/circle"
        android:drawable="@drawable/vd_circle"
        android:state_checked="false" />

    <transition
        android:drawable="@drawable/avd_check2circle"
        android:fromId="@id/check"
        android:toId="@id/circle" />

    <transition
        android:drawable="@drawable/avd_circle2check"
        android:fromId="@id/circle"
        android:toId="@id/check" />
</animated-selector>
複製代碼

使用的時候須要放在app:srcCompat裏面。

<ImageView
    android:id="@+id/img_check_selector"
    android:layout_width="48dp"
    android:layout_height="48dp"
    app:srcCompat="@drawable/asl_check" />
複製代碼

而後再代碼中經過setImageState方法設置不一樣的狀態,圖標就會自行變化了。

img_check_selector.setOnClickListener {
            isCheckSelect = !isCheckSelect
            img_check_selector.setImageState(intArrayOf(if (isCheckSelect) android.R.attr.state_checked else -android.R.attr.state_checked), true)
        }
複製代碼

2018.10.22_17.17.21.gif

app:srcCompat

srcCompat是專門針對vector drawable的,因此最好仍是使用srcCompat代替android:src。

後語

到這裏,咱們能夠看到矢量圖標動畫的強大之處,無視馬賽克,充滿想象力,讓咱們的app更生動,更符合Material Design。可是也有vector Drawable的生成麻煩,編寫各類animated-selector,animated-vector文件繁瑣等缺點。只能說有得就有失了。

與其感慨路難行,不如立刻出發。

*最後的最後,感謝你們的閱讀,歡迎留言。 *

參考資料

相關文章
相關標籤/搜索