首語
- 這是Android動畫系列的目錄,有興趣的可以學習:Android動畫。
- 在某些情況下,圖片需要在屏幕上呈現動畫效果。如果您希望顯示由多張圖片組成的自定義加載動畫,或者希望一個圖標在用戶執行操作後變爲另一個圖標,這種做法就非常實用。Android 提供了兩個選項可以實現。
- 第一個選項是使用 AnimationDrawable。使用該選項,您可以指定多個靜態圖片資源(每次展示一個)來創建動畫。第二個選項是使用 AnimatedVectorDrawable。使用該選項,您可以爲矢量圖添加動畫效果。
幀動畫(AnimationDrawable)
- 幀動畫就是由N張靜態圖片,然後通過控制依次顯示這些圖片,就形成了動畫。
- 實現幀動畫有兩種方式,第一種是xml中實現,第二種是代碼實現。
XML實現
- 幀動畫的XML文件位置在
res/drawable/
目錄中,XML 文件包含一個<animation-list>
元素(用作根節點)和一系列子<item>
節點(每個節點定義一個幀)。
<!--android:oneshot="true"表示是否循環播放,true表示只播放一次。-->
<!--android:duration="200"表示這張圖片動畫播放的時長,單位ms。-->
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="true">
<item android:drawable="@drawable/lockscreen_01" android:duration="200"/>
<item android:drawable="@drawable/lockscreen_02" android:duration="200"/>
<item android:drawable="@drawable/lockscreen_03" android:duration="200"/>
<item android:drawable="@drawable/lockscreen_04" android:duration="200"/>
<item android:drawable="@drawable/lockscreen_05" android:duration="200"/>
<item android:drawable="@drawable/lockscreen_06" android:duration="200"/>
<item android:drawable="@drawable/lockscreen_07" android:duration="200"/>
<item android:drawable="@drawable/lockscreen_08" android:duration="200"/>
<item android:drawable="@drawable/lockscreen_09" android:duration="200"/>
<item android:drawable="@drawable/lockscreen_10" android:duration="200"/>
<item android:drawable="@drawable/lockscreen_11" android:duration="200"/>
<item android:drawable="@drawable/lockscreen_12" android:duration="200"/>
<item android:drawable="@drawable/lockscreen_13" android:duration="200"/>
<item android:drawable="@drawable/lockscreen_14" android:duration="200"/>
<item android:drawable="@drawable/lockscreen_15" android:duration="200"/>
<item android:drawable="@drawable/lockscreen_16" android:duration="200"/>
<item android:drawable="@drawable/lockscreen_17" android:duration="200"/>
</animation-list>
- 在Activity中,該動畫添加到了
ImageView
中。特別要 注意 的是:AnimationDrawable
的start()
方法不能在Activity的onCreate()
方法中調用,因爲AnimationDrawable
有可能在加載的時候還沒有完全加載到Window上,所以最好的使用時機是onWindowFocusChanged()
方法中。
daiv.setBackgroundResource(R.drawable.drawable_animation);
animationDrawable = (AnimationDrawable) daiv.getBackground();
animationDrawable.start();
代碼實現
public void animationDrawable() {
//創建一個AnimationDrawable
AnimationDrawable animationDrawable1 = new AnimationDrawable();
//準備好資源圖片
int[] ids = {R.drawable.lockscreen_01, R.drawable.lockscreen_02, R.drawable.lockscreen_03, R.drawable.lockscreen_04, R.drawable.lockscreen_05};
//通過for循環添加每一幀動畫
for (int i = 0; i < ids.length; i++) {
Drawable frame = getResources().getDrawable(ids[i]);
//設定時長
animationDrawable1.addFrame(frame, 200);
}
animationDrawable1.setOneShot(false);
//將動畫設置到背景上
daiv.setBackground(animationDrawable1);
//開啓幀動畫
animationDrawable1.start();
}
在指定地方播放幀動畫
public class Frame extends android.support.v7.widget.AppCompatImageView {
private AnimationDrawable anim;
public Frame(Context context) {
super(context);
}
public Frame(Context context, AttributeSet attrs) {
super(context, attrs);
}
public Frame(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setAnim(AnimationDrawable anim) {
this.anim = anim;
}
public void setLocation(int top, int left) {
this.setFrame(left, top, left + 200, top + 200);
}
@Override
protected void onDraw(Canvas canvas) {
try {
Field mCurFrame = AnimationDrawable.class.getDeclaredField("mCurFrame");
mCurFrame.setAccessible(true);
int frameInt = mCurFrame.getInt(anim);
if (frameInt == anim.getNumberOfFrames() - 1) {
setVisibility(INVISIBLE);
}
} catch (Exception e) {
e.printStackTrace();
}
super.onDraw(canvas);
}
}
FrameLayout frameLayout = new FrameLayout(this);
setContentView(frameLayout);
final Frame frame = new Frame(this);
frame.setBackgroundResource(R.drawable.drawable_animation);//animation-list
frame.setVisibility(View.INVISIBLE);
final AnimationDrawable anim = (AnimationDrawable) frame.getBackground();
frame.setAnim(anim);
frameLayout.addView(frame);
frameLayout.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
//設置按下才產生動畫效果
if (event.getAction() == MotionEvent.ACTION_DOWN) {
anim.stop();
float x = event.getX();
float y = event.getY();
frame.setLocation((int) y - 40, (int) x - 20);
frame.setVisibility(View.VISIBLE);
anim.start();
}
return false;
}
});
實現效果
AnimatedVectorDrawable
- 矢量圖是一種無需像素化或進行模糊處理即可縮放的圖片。藉助
AnimatedVectorDrawable
類(以及用於實現向後兼容的AnimatedVectorDrawableCompat
),您可以爲矢量圖添加動畫效果,例如旋轉或更改路徑數據以將其變爲其他圖片。不過在學習AnimatedVectorDrawable
之前,我們有必要從VectorDrawable
(矢量圖)開始,因爲熟悉了矢量圖,纔可以添加動畫。
VectorDrawable
VectorDrawable
是一種矢量圖形,在 XML 文件中定義爲一組點、線條和曲線及其相關顏色信息。使用矢量圖的主要優勢在於圖片可縮放。您可以在不降低顯示質量的情況下縮放圖片,也就是說,可以針對不同的屏幕密度調整同一文件的大小,而不會降低圖片質量。這不僅能縮減 APK 文件大小,還能減少開發者維護工作。您還可以對動畫使用矢量圖片,具體方法是針對各種顯示屏分辨率使用多個 XML 文件,而不是多張圖片。- AndroidStudio中也提供了一些VectorDrawable,使用步驟如下:
- File—>new—>Vector Asset;打開界面如圖所示,點擊Clip Art右邊的圖標可以選擇Android自帶的VectorDrawable,同時也可以從外部選擇SVG,PSD格式的圖片生成VectorDrawable。點擊next,finish。圖片的XML代碼如下。
- 這裏我們特別說明一下SVG(Scalable Vector Graphics),意爲可縮放矢量圖,SVG基於可擴展標記語言,使用XML格式定義圖形,與其它圖像格式相比,使用SVG的優勢在於:
- SVG 可被非常多的工具讀取和修改(比如記事本)。
- SVG 與 JPEG 和 GIF 圖像比起來,尺寸更小,且可壓縮性更強。
- SVG 是可伸縮的。
- SVG 圖像可在任何的分辨率下被高質量地打印。
- SVG 可在圖像質量不下降的情況下被放大。
- SVG 圖像中的文本是可選的,同時也是可搜索的(很適合製作地圖)。
- SVG 可以與 Java 技術一起運行。
- SVG 是開放的標準。
- SVG 文件是純粹的 XML。
<vector android:height="24dp" android:tint="#FF0006"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M 0,0 L50,100 L100,0Z"/>
</vector>
-
看到這個是不是直接很懵逼,
android:viewportHeight
定義矢量圖視圖的高度,視圖就是矢量圖path路徑數據所繪製的虛擬畫布。android:viewportWidth
定義矢量圖視圖的寬度。畫布寬高均爲24,意味着繪製了一個24*24的畫布,path路徑必須在這個畫布大小裏去繪製,超出畫布就不顯示了。android:fillColor
定義填充路徑的顏色,如果沒有定義則不填充路徑。最難理解的就是android:pathData
,它的值是一系列字母和數字,爲了告訴解析器如何繪製的,這些字母代表一些指令,具體如下:- M(m):相當於Path.moveTo(),開始新一段的path。
- L(l):相當於Path.lineTo(),移動到指定的點。
- H(h):水平移動。
- V(v):豎直移動。
- C©:三階貝塞爾曲線。相當於Path.cubicTo()。
- S(s):同C,但比C要更平滑。
- Q(q):二階貝塞爾曲線
- T(t):同Q,但比q平滑。
- A(a):弧線。相當於Path.arcTo()。
- Z(z):關閉。相當於Path.close()。
-
每一個指令都有大小寫,大寫表示絕對定位,小寫表示相對定位(相對定位參考點是上一次座標)。 由於繪製路徑的複雜性,因此強烈建議您使用 SVG 編輯器來創建複雜的圖形。
-
android:pathData="M 0,0 L50,100 L100,0Z"
表示座標(0,0)lineto(50,100)lineto(100,0)關閉,繪製出來是一個等腰三角形。
Vector語法
<vector>
<!--<group>用來把多個<path>組合在一起,進行相同的處理。-->
<group>
<path>
<path>
</group>
</vector>
——關於VectorDrawable的介紹就到這裏!
使用 AnimatedVectorDrawable
- 我們通常需要在三個XML文件中定義添加動畫效果後的矢量圖:
- 矢量圖,其中位於res/drawable/
- 添加動畫效果的矢量圖,其中 位於 res/drawable/
- 一個或多個Animator,其中 元素位於 res/animator/
<?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="100"
android:viewportWidth="100">
<group
android:name="group">
<path android:name="tangle"
android:strokeColor="@color/colorAccent"
android:strokeWidth="2"
android:pathData="M 0,0 L50,100 L100,0Z"/>
</group>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/vector_drawable">
<target
android:animation="@anim/anim_vector"
android:name="tangle"/>
<target
android:animation="@anim/anim_group"
android:name="group"/>
</animated-vector>
- 兩個動畫,一個縮放動畫(屬性動畫介紹參考Android動畫目錄),一個針對pathData的動畫。
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="5000"
android:propertyName="scaleX"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:valueFrom="1"
android:valueTo="0.5"
android:valueType="floatType" />
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="2000"
android:propertyName="pathData"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:valueFrom="M 0,0 L50,100 L100,0Z"
android:valueTo="M 0,100 L50,0 L100,100Z"
android:valueType="pathType" />
</set>
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
AnimatedVectorDrawable animatedVectorDrawable = (AnimatedVectorDrawable) vectoriv.getDrawable();
animatedVectorDrawable.start();
}
aapt合併xml
- 這裏有很多文件只是爲了讓一個動畫可以繪製!如果矢量圖動畫在其他地方重複使用,這是實現矢量可繪製動畫的最佳方法。如果它們只用於這個動畫矢量繪圖,那麼有一個更緊湊的方法來實現它們。使用AAPT的內聯資源格式,可以在同一個XML文件中定義所有三個資源。因爲我們正在製作一個矢量圖的動畫效果,所以我們將文件放在res/drawable/下。
<?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">
<aapt:attr name="android:drawable">
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportWidth="100"
android:viewportHeight="100">
<group android:name="group">
<path
android:name="tangle"
android:strokeWidth="2"
android:pathData="M 0,0 L50,100 L100,0Z"
android:strokeColor="@color/colorAccent" />
</group>
</vector>
</aapt:attr>
<target android:name="tangle">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="2000"
android:propertyName="pathData"
<!--無限次infinite-->
android:repeatCount="infinite"
android:repeatMode="reverse"
android:valueFrom="M 0,0 L50,100 L100,0Z"
android:valueTo="M 0,100 L50,0 L100,100Z"
android:valueType="pathType" />
</aapt:attr>
</target>
<target android:name="group">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="5000"
android:propertyName="scaleX"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:valueFrom="1"
android:valueTo="0.5"
android:valueType="floatType" />
</aapt:attr>
</target>
</animated-vector>
- XML標記aapt:attr告訴aapt該標記的子標記應被視爲資源並提取到其自己的資源文件中。使用這種方式更緊湊。實現效果相同。