1. 程式人生 > >Android開發筆記(一百三十二)向量圖形與向量動畫

Android開發筆記(一百三十二)向量圖形與向量動畫

向量圖形VectorDrawable

與水波圖形RippleDrawable一樣,向量圖形VectorDrawable也是Android5.0之後新增的圖形類。向量圖不同於一般的圖形,它是由一系列幾何曲線構成的影象,這些曲線以數學上定義的座標點連線而成。具體到實現上,則需開發者提供一個xml格式的向量圖形定義,然後系統根據向量定義自動計算該圖形的繪製區域。因為繪圖結果是動態計算得到,所以不管縮放到多少比例,向量圖形都會一樣的清晰,不像點陣圖那樣拉大後會變模糊。

向量圖形的xml定義有點複雜,其結構可分為三個層次:根標籤、組標籤、路徑標籤。


根標籤vector

首先是vector標籤,它表示當前定義的是一個完整的向量圖形。該標籤支援的主要屬性說明如下:
android:name:指定向量圖形的名稱。
android:width:指定向量圖形的預設寬度,一般使用dp數值。如果在layout佈局檔案中將ImageView的layout_width設定為wrap_content,同時src設定為該向量圖形,則ImageView控制元件的寬度就是此處的android:width。
android:height:指定向量圖形的預設高度,一般使用dp數值。
android:viewportWidth:指定檢視空間的寬度,即虛擬座標系的寬度,後續路徑的座標資訊都位於該檢視空間之內。
android:viewportHeight:指定檢視空間的高度,即虛擬座標系的高度。
android:alpha:指定向量圖形的的透明度,取值為0.0到1.0。

這裡要注意width/height與viewportWidth/viewportHeight兩組寬高的區別,前者指的是向量圖形被外部世界觀察到的尺寸大小,故而採用了帶dp單位的絕對數值;而後者指的是向量圖形為內部幾何路徑所參照的空間範圍,故而採用了不帶單位的相對數值,正因為向量圖形中的幾何路徑以相對座標來標記,所以不管向量圖形縮放到多少比例,其內部的幾何形狀也會按同樣比例縮放。


組標籤group

然後是group標籤,它定義了一組路徑的共同行為(如一起旋轉、一起縮放、一起平移等等)。該標籤支援的主要屬性說明如下:
android:name:指定分組物件的名稱。
android:pivotX:指定旋轉中心點的橫軸座標。
android:pivotY:指定旋轉中心點的縱軸座標。
android:rotation:指定分組物件的旋轉角度。
android:scaleX:指定分組物件在橫軸上的縮放比例。取值0.5表示縮小一半,取值2.0表示放大一倍。
android:scaleY:指定分組物件在縱軸上的縮放比例。
android:translateX:指定分組物件在橫軸上的平移距離。
android:translateY:指定分組物件在縱軸上的平移距離。


路徑標籤path

最後是path標籤,它定義了一個路徑的幾何描述,既可以表示一根曲線,也可以表示一塊平面區域。該標籤支援的主要屬性說明如下:
android:name:指定幾何路徑的名稱。
android:pathData:指定幾何路徑的資料定義。資料格式需符合SVG標準。
android:fillColor:指定平面區域的顏色。若不指定,則不繪製平面區域。
android:fillAlpha:指定平面區域的透明度。
android:strokeColor:指定曲線的顏色。若不指定,則不繪製曲線顏色。
android:strokeWidth:指定曲線的寬度。
android:strokeAlpha:指定曲線的透明度。
android:strokeLineCap:指定曲線的首尾外觀。取值說明有三個:butt(預設值,直線邊緣)、round(圓形邊緣)、square(方形邊緣)。
android:strokeLineJoin:指定兩條曲線相交的邊角外觀。取值說明有三個:miter(預設值,銳角)、round(圓角)、bevel(鈍角)。
android:trimPathStart:指定幾何路徑從哪裡開始繪製。取值為0.0到1.0,比如取值0.4表示只繪製後面十分之六的內容,前面十分之四不予繪製。
android:trimPathEnd:指定幾何路徑到哪裡結束繪製。取值為0.0到1.0,比如取值0.4表示只繪製前面十分之四的內容,後面十分之六不予繪製。
android:trimPathOffset:指定幾何路徑的繪製偏移。取值為0.0到1.0,表示線條從trimPathOffset+trimPathStart處一直繪製到trimPathOffset+trimPathEnd處。

路徑資訊有幾個地方容易混淆,下面把相關細節詳細說明一下:
1、關於butt和square的區別,乍看起來直線邊緣與方形邊緣沒什麼差別,但向量圖形的方形邊緣其實是套上一個方形的帽子,既然是套上去,就會比沒戴帽子的時候高一點,所以使用square的線條會比使用butt的線條要長一點。
2、關於butt和square的區別,miter保留了原樣的尖角,而bevel會把尖角部分切掉一小塊,看起來就變鈍了。
3、trimPathOffset+trimPathEnd的和如果超過1,也會畫出來。只是沒有全部畫出來,而是繪製從起點到trimPathOffset+trimPathEnd-1所處的位置。


可縮放向量圖形SVG標記

前面說到,path標籤的android:pathData屬性,取值需符合SVG標準。SVG全稱為“Scalable Vector Graphics”,意即可縮放的向量圖形,它是一種圖形格式,專門用於描述向量圖形的定義。

SVG標記比較抽象,下面先舉個簡單的例子,有了直觀的概念更方便理解,如下所示:
        android:pathData="
            M 30,50
            L 75 35"
這個標記定義不難,首先“M 30,50”指的是把畫筆移動到座標點(30,50)的位置,後面的“L 75 35”指的是從當前位置畫一根線段到座標點(75,35)。說白了,就是在(30,50)和(75,35)兩點之間畫一根線段。


好了,每行定義一個動作,每行的第一個字元表示動作的型別,後面的數字表示動作經過的座標點。這便是SVG標記的大概格式,萬變不離其宗,掌握了規律學得更好更快。詳細的SVG標記定義說明如下:
移動畫筆“M x0,y0”把畫筆移動到座標點(x0,y0)。
畫線段“L x1 y1” 從當前位置(x0,y0)畫一根線段到座標點(x1,y1)。
畫水平線段“H x1” 從當前位置(x0,y0)畫一根水平線到座標點(x1,y0)。
畫垂直線段“V y1” 從當前位置(x0,y0)畫一根垂直線到座標點(x0,y1)。
畫二次貝塞爾曲線“Q xa ya x1 y1”二次貝塞爾曲線的起點是當前位置,終點是(x1,y1),曲線中部向控制點(xa,ya)凸出。
畫三次貝塞爾曲線“C xa ya xb yb x1 y1”三次貝塞爾曲線的起點是當前位置,終點是(x1,y1),曲線中部有兩個控制點,分別向(xa,ya)和(xb,yb)兩方向凸出。
畫橢圓的圓弧“A radius-x radius-y x-axis-rotation large-arc-flag sweep-flag x1 y1”從當前位置拉出一段圓弧,圓弧的引數比較多,分別說明如下:
--radius-x表示橢圓的橫軸半徑。
--radius-y表示橢圓的縱軸半徑。橫軸半徑等於縱軸半徑時,表示這是個圓圈的圓弧。
--x-axis-rotation表示圓弧的旋轉角度。
--large-arc-flag表示大弧標誌,為0時表示取小弧度,1時取大弧度。
--sweep-flag表示軌跡標誌,為0表示逆時針方向,為1表示順時針方向。
--圓弧經過某點,該點的橫座標為x1
--圓弧經過某點,該點的縱座標為y1
閉合路徑“Z” 連線起點跟終點,即在起點(x0,y0)與終點之間畫一根線段。

再來補充一下SVG標記的若干說明,如下所示:
1、每個命令都有大小寫形式,大寫代表後面的引數是絕對座標,小寫表示相對座標。
2、引數之間用空格或逗號隔開,兩種分隔符的效果是一樣的。
3、關於圓弧的large-arc-flag和sweep-flag兩個標誌,光看文字說明其實不易理解,還是上個圖觀察觀察:


下面使用SVG標記定義一個心形,先上個心形的效果圖:


心形對應的向量圖形定義示例如下:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="256dp"
    android:height="256dp"
    android:viewportHeight="32"
    android:viewportWidth="32">

    <path
        android:fillColor= "#ffaaaa"
        android:pathData= "M20.5,9.5
                        c-1.955,0,-3.83,1.268,-4.5,3
                        c-0.67,-1.732,-2.547,-3,-4.5,-3
                        C8.957,9.5,7,11.432,7,14
                        c0,3.53,3.793,6.257,9,11.5
                        c5.207,-5.242,9,-7.97,9,-11.5
                        C25,11.432,23.043,9.5,20.5,9.5z" />
</vector>


向量動畫AnimatedVectorDrawable

費了老大的勁搞清楚SVG標記,如果僅僅畫個靜態的向量圖形,未免大材小用了。其實向量圖形真正的意義在於向量動畫,通過動態計算幾何路徑的座標,從而實現區域性或整體的動畫效果,這才是向量圖形的殺手鐗呀。


Android提供了AnimatedVectorDrawable這麼一個向量動畫類,但開發者還得通過屬性動畫及其xml標籤方可實現動畫定義。先看看AnimatedVectorDrawable的幾個常用方法:
registerAnimationCallback : 註冊動畫監聽器,需實現Animatable2.AnimationCallback介面的兩個方法:onAnimationStart和onAnimationEnd。
start : 開始播放動畫。
stop : 停止播放。
reverse : 倒過來播放。
再看看如何通過屬性動畫實現向量動畫效果。理論上,向量圖形的三個標籤(vector、group、path)都有可以用來播放動畫的屬性;不過實際開發的時候,常用的只有三類屬性可用作動畫,說明如下:

變換類屬性

這類屬性包括vector標籤的android:alpha,以及group標籤的android:rotation、android:scaleX、android:scaleY、android:translateX、android:translateY等等,這幾個屬性分別對應於補間動畫的灰度動畫、旋轉動畫、縮放動畫、平移動畫。
因為該類屬性實現的是大家熟悉的補間動畫效果,所以這裡就不再做演示了。


路徑類屬性

這類屬性主要指path標籤的android:pathData,通過設定幾何路徑的起始狀態與終止狀態,可實現兩個幾何形狀之間的漸變效果,如一個圓圈從小變大,又如一條曲線變成直線等等。
下面是個從哭喪臉變為笑臉的動畫截圖:


下面是人臉的向量圖形定義檔案vector_face_eye.xml:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:height="200dp"
    android:width="200dp"
    android:viewportHeight="100"
    android:viewportWidth="100" >
  <path
      android:fillColor="@color/yellow"
      android:pathData="@string/path_circle"/>
  <path
      android:name="eye_left"
      android:strokeColor="@android:color/black"
      android:strokeWidth="4"
      android:strokeLineCap="round"
      android:pathData="@string/path_eye_left_sad"/>
  <path
      android:name="eye_right"
      android:strokeColor="@android:color/black"
      android:strokeWidth="4"
      android:strokeLineCap="round"
      android:pathData="@string/path_eye_right_sad"/>
  <path
      android:name="mouth"
      android:strokeColor="@android:color/black"
      android:strokeWidth="4"
      android:strokeLineCap="round"
      android:pathData="@string/path_face_mouth_sad"/>
</vector>


接著是臉部三處器官變化的屬性動畫定義檔案。
下面是左眼的屬性動畫定義檔案anim_smile_eye_left.xml:
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
  android:duration="3000"
  android:propertyName="pathData"
  android:valueFrom="@string/path_eye_left_sad"
  android:valueTo="@string/path_eye_left_happy"
  android:valueType="pathType"
  android:interpolator="@android:anim/accelerate_interpolator"/>
下面是右眼的屬性動畫定義檔案anim_smile_eye_right.xml:
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
  android:duration="3000"
  android:propertyName="pathData"
  android:valueFrom="@string/path_eye_right_sad"
  android:valueTo="@string/path_eye_right_happy"
  android:valueType="pathType"
  android:interpolator="@android:anim/accelerate_interpolator"/>
下面是嘴巴的屬性動畫定義檔案anim_smile_mouth.xml:
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
  android:duration="3000"
  android:propertyName="pathData"
  android:valueFrom="@string/path_face_mouth_sad"
  android:valueTo="@string/path_face_mouth_happy"
  android:valueType="pathType"
  android:interpolator="@android:anim/accelerate_interpolator"/>


最後是笑臉的向量動畫定義例子animated_vector_smile_eye.xml:
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/vector_face_eye" >

    <target
        android:name="mouth"
        android:animation="@anim/anim_smile_mouth" />

    <target
        android:name="eye_left"
        android:animation="@anim/anim_smile_eye_left" />
    
    <target
        android:name="eye_right"
        android:animation="@anim/anim_smile_eye_right" />
    
</animated-vector>


不要忘了在程式碼中進行向量動畫的播放操作:
	private void startVectorSmile() {
		iv_vector_smile.setImageResource(R.drawable.animated_vector_smile_eye);
		Drawable drawable = iv_vector_smile.getDrawable();
		if (drawable instanceof AnimatedVectorDrawable) {
			((AnimatedVectorDrawable) drawable).start();
		}
	}


修剪類屬性

這類屬性包括path標籤的android:trimPathStart和android:trimPathEnd,可實現向量圖形逐步展開或者逐步消失的動畫效果。
下面是個支付寶支付成功的動畫截圖:


支付成功動畫包含兩個形狀,首先在外面畫個圓圈,然後在圓圈裡面畫個打勾符號。因為圓圈和打勾並不相連,如果按照一般的處理,就會一邊畫圓圈一邊畫打勾,這不是我們所希望的畫完圓圈再畫打勾的效果。所以要想讓圓圈動畫和打勾動畫按順序播放,得分別定義圓圈的向量圖形和打勾的向量圖形,然後等圓圈動畫播放完畢,再開始播放打勾動畫。


下面是圓圈的向量圖形定義檔案vector_pay_circle.xml:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:height="100dp"
    android:viewportHeight="100"
    android:viewportWidth="100"
    android:width="100dp" >

    <path
        android:name="circle"
        android:pathData="
            M 10,50
            A 40 40 0 1 0 10 49"
        android:strokeAlpha="1"
        android:strokeColor="@color/blue_sky"
        android:strokeLineCap="round"
        android:strokeWidth="3" />

</vector>
下面是打勾的向量圖形(含圓圈圖形)定義檔案vector_pay_success.xml:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:height="100dp"
    android:viewportHeight="100"
    android:viewportWidth="100"
    android:width="100dp" >

    <path
        android:name="circle"
        android:pathData="
            M 10,50
            A 40 40 0 1 0 10 49"
        android:strokeAlpha="1"
        android:strokeColor="@color/blue_sky"
        android:strokeLineCap="round"
        android:strokeWidth="3" />
    <path
        android:name="hook"
        android:pathData="
            M 30,50
            L 45 65
            L 75 35"
        android:strokeAlpha="1"
        android:strokeColor="@color/blue_sky"
        android:strokeLineCap="butt"
        android:strokeWidth="3" />

</vector>


接著是支付成功的屬性動畫的xml定義檔案anim_pay.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"
    android:valueTo="1"
    android:valueType="floatType" />


最後是向量動畫的定義檔案,下面這個用來播放圓圈動畫:
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/vector_pay_circle">

    <target
        android:name="circle"
        android:animation="@anim/anim_pay" />

</animated-vector>
下面這個用來播放圓圈動畫後繼的打勾動畫:
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/vector_pay_success">

    <target
        android:name="hook"
        android:animation="@anim/anim_pay" />

</animated-vector>


圓圈動畫播放完畢,接著播放打勾動畫,這要在程式碼中控制,具體的是呼叫AnimatedVectorDrawable物件的registerAnimationCallback方法,一旦監聽到原動畫播放結束,然後開始播放新動畫。


點選下載本文用到的向量圖形與向量動畫的工程程式碼


點此檢視Android開發筆記的完整目錄

相關推薦

Android開發筆記一百向量圖形向量動畫

向量圖形VectorDrawable 與水波圖形RippleDrawable一樣,向量圖形VectorDrawable也是Android5.0之後新增的圖形類。向量圖不同於一般的圖形,它是由一系列幾何曲線構成的影象,這些曲線以數學上定義的座標點連線而成。具體到實現上,則需開

Android開發筆記一百文字輸入佈局TextInputLayout

文字輸入佈局TextInputLayoutTextInputLayout是MaterialDesign庫中對編輯框EditText進行增強的一個控制元件。眾所周知,EditText未輸入字元時,我們可以給它顯示預設的提示文字hint;可是一旦輸入字元,這個hint提示就消失了

Android開發筆記一百H5通過WebView上傳圖片

上一篇文章介紹了WebView與JS之間的資料互動,其實就是把字串傳來傳去,這對文字格式的資訊傳輸來說倒還湊合,倘若要傳輸圖片資訊就不管用了。所以,要想讓h5網頁支援從手機上傳圖片,還得另外想辦法,當然各版本的Android系統也都提供了相應的解決辦法。在Android 4.

Android開發筆記一百利用紅外發射遙控電器

紅外遙控是一種無線控制技術,它具有功耗小、成本低、易實現等諸多優點,因而被各種電子裝置特別是家用電器廣泛採用,像日常生活中的電視遙控器、空調遙控器等等基本都採用紅外遙控技術。不過遙控器並不都是紅外遙控,也可能是射頻遙控。紅外遙控使用近紅外光線(頻率只有幾萬赫茲)作為遙控光源,

Android開發筆記一百工具欄ToolBar

Toolbar 在前面的博文《Android開發筆記(二十)頂部導航欄》中,我們學習了ActionBar的用法,可是ActionBar著實是不怎麼好用,比如文字風格不能定製、圖示不能定製,而且還存在低版本的相容性問題,所以實際開發中大家還是不傾向使用ActionBar。為此

Android開發筆記一百app省電方略

電源管理PowerManager PowerManager是Android的電源管理類,用於管理電源操作如睡眠、喚醒、重啟以及調節螢幕亮度等等。 PowerManager的物件從系統服務POWER_SERVICE中獲取,它的主要方法如下: goToSleep : 睡眠,即鎖

Android開發筆記一百OpenGL的畫筆工具GL10

上一篇文章介紹了OpenGL繪製三維圖形的流程,其實沒有傳說中的那麼玄乎,只要放平常心把它當作一個普通控制元件就好了,接下來繼續介紹OpenGL具體的繪圖操作,這項工作得靠三維圖形的畫筆GL10來完成了。GL10作為三維空間的畫筆,它所描繪的三維物體卻要顯示在二維平面上,顯而

Android開發筆記一百利用GL10描繪點、線、面

上一篇文章介紹了GL10的常用方法,包括如何設定顏色、如何指定座標系、如何調整鏡頭引數、如何挪動觀測方位等等,不過這些方法只是繪圖前的準備工作,真正描繪點、線、面的製圖工作並未涉及,那麼本文就來談談如何利用GL10進行實際的三維繪圖操作。首先在三維座標系中,每個點都有x、y、

Android開發筆記一百約束佈局ConstraintLayout

約束佈局ConstraintLayout是Android Studio 2.2推出的新佈局,並從Android Studio 2.3開始成為預設佈局檔案的根佈局,由此可見Android官方對其寄予厚望,那麼約束佈局究竟具備哪些激動人心的特性呢?傳統的佈局如線性佈局Linear

Android開發筆記一百H5通過WebView錄影上傳

前面的博文《Android開發筆記(一百五十二)H5通過WebView上傳圖片》介紹瞭如何拍照上傳給網頁,不料客戶又要求再加個攝像上傳給網頁。既然如此,那麼再探討一下如何實現這個攝像上傳的功能。與拍照上傳一樣,攝像上傳也要重寫WebChromeClient的openFileC

Android開發筆記一百休眠模式下的定時器控制

定時器AlarmManager常常用於需要週期性處理的場合,比如鬧鐘提醒、任務輪詢等等。並且定時器來源於系統服務,即使App已經不在運行了,也能收到定時器發出的廣播而被喚醒。似此迴光返照的神技,便遭到開發者的濫用,造成使用者手機充斥著各種殺不光程序,就算通過手機安全工具一再地

Java開發筆記字元型整型相互轉化

前面提到字元型別是一種新的變數型別,然而編碼實踐的過程中卻發現,某個具體的字元值居然可以賦值給整型變數!就像下面的例子程式碼那樣,把字元值賦給整型變數,編譯器不但沒報錯,而且還能正常執行! // 字元允許直接賦值給整型變數 private static void charToInt() { i

一百Android O wpa_supplicant初始化學習—— retrieveIfacePtr 流程探討

前言:一直想梳理下WiFi在supplicant的連線流程,但是初始化流程梳理的千瘡八孔,少了前置步驟很難梳理。先看下一個基礎的介面retrieveIfacePtr流程。   1.目標介面 sta_iface.cpp /** * Retrieve the underl

Android開發筆記一百四十三任務排程JobScheduler

任務排程App除了通過螢幕向用戶展示可互動的介面元素之外,還經常需要在後臺做些背地裡做的事情,比如說精密計算、檔案下載、統計分析、資料匯入、狀態監控等等,這些使用者看不到的事一般放在Service中處理。然而有時候我們希望在特定情況下再啟動事務,比如說延遲若干時間之後,或者等

Java開發筆記字符型整型相互轉化

傳播 out 字母 href 不但 java 個數 進制數 com 前面提到字符類型是一種新的變量類型,然而編碼實踐的過程中卻發現,某個具體的字符值居然可以賦值給整型變量!就像下面的例子代碼那樣,把字符值賦給整型變量,編譯器不但沒報錯,而且還能正常運行! // 字符允許

Linux學習筆記iptables filter表案例、 iptables nat表應用

iptables filter表案例、 iptables nat表應用 一、iptables filter表案例需求:將80、20、21端口放行,對22端口指定特定的ip才放行以下為操作方法:vim /usr/local/sbin/iptables.sh //加入如下內容#! /bin/bas

小甲魚《零基礎學習Python》課後筆記

測試題 0.結合你自身的程式設計經驗,總結下異常處理機制的重要性? 可以增強程式的適應環境的能力,提升使用者體驗。 1.請問以下程式碼是否會產生異常,如果會的話,請寫出異常的名稱: >>> my_list = [1, 2, 3,

Android開發筆記顏色的使用

顏色的編碼 Android中顏色值的定義是由透明度alpha和RGB(紅綠藍)三原色來定義的,有八位十六進位制數與六位十六進位制數兩種編碼,例如八位FFE

Android開發筆記螢幕解析度

在app編碼中經常需要獲取手機的螢幕解析度(寬*高),原來我直接上網拷貝程式碼,但在使用過程中卻發現諸多不便。 不便一:下面程式碼中的getWidth和getHeight在adt上提示deprecated已經廢棄了,實在扎眼 WindowManager wm = get

Django學習筆記:datetime.timedelta類介紹

datetime.timedelta物件代表兩個時間之間的時間差,兩個date或datetime物件相減就可以返回一個timedelta物件。  如果有人問你昨天是幾號,這個很容易就回答出來了。但是如果問你200天前是幾號,就不是那麼容易答出來。而在Python中dateti