引言:這篇文章簡單介紹一下Android動畫的基本寫法和一些要注意的地方,幫助大家更加容易使用Android動畫。由于網(wǎng)上有太多更加詳細(xì)、篇幅更大的文章,這里不做太多太詳細(xì)的解釋性的內(nèi)容,因?yàn)閷τ贏ndroid動畫,難點(diǎn)在于:自定義View動畫、自定義屬性動畫集、差值器、估值器,而這幾個知識點(diǎn)既是一時半會說不清楚的(用到物理知識等方面),又是平常實(shí)際開發(fā)中很少用到的(很多創(chuàng)業(yè)app注重實(shí)用性和開發(fā)成本,所以很少會用到大量的花俏動效,這里指“創(chuàng)業(yè)App”)
一、View動畫
-
系統(tǒng)View動畫
gif:
(1) xml形式
* 首先在/res/anim目錄下新建xxx.xml文件:
* 然后,調(diào)用這個動畫,并賦予指定view:
animation = AnimationUtils.loadAnimation(getActivity(),R.anim.view_anim); getImageView().startAnimation(animation);
注意:
android:zAdjustment:允許在動畫播放期間,調(diào)整播放內(nèi)容在Z軸方向的順序,normal(0):正在播放的動畫內(nèi)容保持當(dāng)前的Z軸順序,
top(1):在動畫播放期間,強(qiáng)制把當(dāng)前播放的內(nèi)容放到其他內(nèi)容的上面;
bottom(-1):在動畫播放期間,強(qiáng)制把當(dāng)前播放的內(nèi)容放到其他內(nèi)容之下
(2) Java形式
* 首先,創(chuàng)建一個你想要的Animation(TranslateXX,ScaleXX,RotateXX,AlphaXX),然后開始動畫
```
animation = new RotateAnimation(0, +720, Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
animation.setDuration(3000);
animation.setInterpolator(getActivity(),android.R.anim.accelerate_decelerate_interpolator);
getImageView().startAnimation(animation);
```
* 雖然達(dá)不到xml方式的那種各種動畫效果疊加同時進(jìn)行的效果,我們依然可以使用`AnimationListener`來將幾個動畫連接起來:
```
animation.setAnimationListener(this);
……
int i =4;
@Override
public void onAnimationEnd(Animation animation) {
i-=1;
switch (i){
case 3:
animation = new TranslateAnimation(0,0,0,-160);
animation.setDuration(2000);
animation.setInterpolator(getActivity(),android.R.anim.accelerate_decelerate_interpolator);
/**
* 這里,注意點(diǎn):
* 1.setFillAfter 表示的是:是否讓畫面停留在最后一刻的狀態(tài)
*/
animation.setFillAfter(true);
animation.setAnimationListener(this);
getImageView().startAnimation(animation);
break;
case 2:
//要自己實(shí)際移動View的坐標(biāo)位置
getImageView().offsetTopAndBottom(-160);
// animation.cancel();
getImageView().clearAnimation();
getImageView().invalidate();
animation = new ScaleAnimation(1f,1.5f,1f,1.5f,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
animation.setDuration(2000);
animation.setInterpolator(getActivity(),android.R.anim.accelerate_decelerate_interpolator);
animation.setFillAfter(true);
animation.setAnimationListener(this);
getImageView().startAnimation(animation);
break;
case 1:
// animation.cancel();
getImageView().clearAnimation();
getImageView().invalidate();
animation = new AlphaAnimation(1.0f,0.0f);
animation.setDuration(1000);
animation.setInterpolator(getActivity(),android.R.anim.accelerate_decelerate_interpolator);
animation.setFillAfter(true);
animation.setAnimationListener(this);
getImageView().startAnimation(animation);
break;
default:
getImageView().clearAnimation();
this.dismiss();
break;
}
}
```
-
自定義View動畫
如果不滿足于系統(tǒng)的幾種動畫,那么可以結(jié)合自己的物理知識,利用android.graphics.Camera
和android.graphics.Matrix
類,去實(shí)現(xiàn)自定義的動畫效果。要實(shí)現(xiàn)自定義Animation,那么,就需要繼承Animation
類。下面是一個示例:
* gif:
* 代碼:
```
public class CustomViewAnim extends Animation {
private Camera mCamera;
private final float mFromDegrees;
private final float mToDegrees;
private final float mCenterX;
private final float mCenterY;
private final float mDepthZ;
private final boolean mReverse;
public CustomViewAnim(float mFromDegrees, float mToDegrees
, float mCenterX, float mCenterY, float mDepthZ, boolean mReverse) {
this.mFromDegrees = mFromDegrees;
this.mToDegrees = mToDegrees;
this.mCenterX = mCenterX;
this.mCenterY = mCenterY;
this.mDepthZ = mDepthZ;
this.mReverse = mReverse;
}
/**
* 做一些初始化工作
*/
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
mCamera = new Camera();
}
/**
* 相應(yīng)的矩陣變換
*/
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
final float fromDegrees = mFromDegrees;
float degrees = fromDegrees + ((mToDegrees - fromDegrees)) * interpolatedTime;
final float centerX = mCenterX;
final float centerY = mCenterY;
final Camera camera = mCamera;
final Matrix matrix = t.getMatrix();
camera.save();
if (mReverse){
camera.translate(0f,0f,mDepthZ * interpolatedTime);
}else{
camera.translate(0f,0f,mDepthZ*(1f - interpolatedTime));
}
camera.rotateY(degrees);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-centerX,-centerY);
matrix.postTranslate(centerX,centerY);
}
}
```
-
LayoutAnimation
給ViewGroup中的所有子View加載設(shè)定的動畫,是View動畫的特殊應(yīng)用。
* gif:
* 步驟:
* 1.定義`/res/anim/view_anim.xml`動畫文件:和view動畫的xml形式一樣。
* 2.在Java代碼中給根布局設(shè)定屬性:
```
container = (LinearLayout) findViewById(R.id.list_container);
/**
* 設(shè)置LayoutAnimation
*/
Animation animation = AnimationUtils.loadAnimation(this,
R.anim.view_anim);
LayoutAnimationController controller = new LayoutAnimationController(animation);
controller.setDelay(0.6f);///表示每個子View加載的延時為`view_anim.xml`的duration時間的60%.
controller.setOrder(LayoutAnimationController.ORDER_NORMAL);///表示按加載順序加載每個子view
container.setLayoutAnimation(controller);////設(shè)置好
```
- Activity和Fragment跳轉(zhuǎn)動畫
* Activity跳轉(zhuǎn)動畫
在**startActivity()方法之后** 或者 **finish()/super.onBackPressed()方法之后**執(zhí)行:
`overridePendingTransition(R.anim.enter_anim,R.anim.exit_anim);`
其中,R.anim.enter_anim 和 R.anim.exit_anim 的格式與View動畫的格式一樣。
* Fragment加載動畫(API11即Android3.0之后可用)
* gif:
* **FragmentTransaction.setTransition:**如下示例代碼,使用系統(tǒng)默認(rèn)的Fragment加載動畫。
* **FragmentTransaction.setCustomAnimations:**如下示例代碼,使用自定義的View動畫加載。
* 注意,這種方式對于DialogFragment是無效的,因?yàn)镈ialogFragment沒有對應(yīng)的父容器,本身就是固定的。
* 示例代碼:
定義`fragment_enter_anim.xml`文件(`fragment_exit_anim.xml`文件類似):
```
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true"
android:interpolator="@android:anim/linear_interpolator"
android:zAdjustment="normal"
android:shareInterpolator="true"
android:repeatMode="restart"
android:duration="1000"
>
<translate
android:fromYDelta="-600"
android:toYDelta="0"
android:startOffset="500"
/>
<rotate
android:fromDegrees="-90"
android:toDegrees="0"
android:pivotY="50%"
android:pivotX="50%"
/>
<alpha
android:fromAlpha="0.5"
android:toAlpha="1"
/>
</set>
然后,java代碼中給FragmentTransaction去加載。
private void addFragment() {
Fragment fragment = new TestFragment();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
// transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
// transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
// transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE);
transaction.setCustomAnimations(R.anim.fragment_enter_anim,R.anim.fragment_exit_anim);
transaction.add(R.id.frame_root,fragment);
// transaction.addToBackStack("frag");
transaction.commit();
}
```
* gif:
二、 幀動畫
-
gif:
-
首先,在
/res/drawable
目錄下創(chuàng)建xml文件:
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="true">
<item android:drawable="@drawable/img1" android:duration="500"/>
<item android:drawable="@drawable/img2" android:duration="500"/>
<item android:drawable="@drawable/img3" android:duration="500"/>
</animation-list>
* 然后讓指定view去設(shè)置`background`,并讓動畫`start`
getImageView().setBackgroundResource(R.drawable.frame_anim);
AnimationDrawable ad = (AnimationDrawable) getImageView().getBackground();
ad.start();
## 三、屬性動畫
gif:

(1) xml形式
> 注意:這種形式下,動畫效果容易失效!建議使用Java代碼方式動態(tài)加載動畫。
這里,使用的是/res/animator/xxx.xml文件,而不是/res/anim/xxx.xml,java中的寫法是:`R.animator.xxx`而不是`R.anim.xxx`
首先,定義xml屬性:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together"
>
<objectAnimator
android:propertyName="string"
android:duration="1000"
android:valueFrom="@android:color/white"
android:valueTo="@android:color/holo_green_dark"
android:startOffset="500"
android:repeatCount="1"
android:repeatMode="restart"
android:valueType="intType"/>
<animator
android:duration="1000"
android:valueFrom="@android:color/white"
android:valueTo="@android:color/holo_green_dark"
android:startOffset="10"
android:repeatCount="1"
android:repeatMode="restart"
android:valueType="intType"/>
</set>
然后,在java中指定對象并加載屬性動畫:
getImageView().setImageResource(R.drawable.img1);
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(getActivity(), R.animator.property_anim);
set.setTarget(getImageView());
set.start();
(2) Java形式
getImageView().setImageResource(R.drawable.img1);///設(shè)置圖片內(nèi)容
AnimatorSet set = new AnimatorSet();///創(chuàng)建屬性動畫集
set.playTogether(///“playTogether”表示所有的子動畫同時運(yùn)作
ObjectAnimator.ofFloat(getImageView(),"rotationX",0,360),
ObjectAnimator.ofFloat(getImageView(),"rotationY",0,180),
ObjectAnimator.ofFloat(getImageView(),"rotation",0,-90),
ObjectAnimator.ofFloat(getImageView(),"translationX",0,90),
ObjectAnimator.ofFloat(getImageView(),"translationY",0,90),
ObjectAnimator.ofFloat(getImageView(),"scaleX",1,1.5f),
ObjectAnimator.ofFloat(getImageView(),"scaleY",1,0.5f),
ObjectAnimator.ofFloat(getImageView(),"alpha",1,0.25f,1)
);
set.setDuration(5*1000).start();
## 四、注意事件
1. **關(guān)于Android Animation的setFillBefore、setFillAfter和setFillEnable:**
* 如果是獨(dú)立的Animation,只有setFillAfter有效,設(shè)置為true動畫結(jié)束后保持最后的狀態(tài)。
* 如果是AnimationSet中的Animation,因?yàn)锳nimation的作用周期可能位于整個AnimationSet動畫周期的中間一部分,setFillBefore設(shè)置的是在這個動畫被執(zhí)行前是否啟用這個動畫的第一幀效果填充開始前的動畫,setFillAfter設(shè)置的是在這個動畫結(jié)束后是否保留這個動畫的最后一幀的效果填充后面的動畫,而這兩個設(shè)置必須同時設(shè)置setFillEnable。
* 如果想這個AnimationSet結(jié)束后保留最后的結(jié)果,需要設(shè)置AnimationSet的setFillAfter。
> 補(bǔ)充:當(dāng)setFillEnable為false時,通過查看源碼可知在AnimationSet中自身的動畫周期不受setFillBefore和setFillAfter控制;當(dāng)Animation獨(dú)立存在時,或AnimationSet的setFillAfter為true時,ViewGroup會讀取getFillAfter值,如果為true,不clearAnimation,也就保持了最終的狀態(tài)。
2. **讓view自轉(zhuǎn):**
* 方式一:java代碼動態(tài)設(shè)定旋轉(zhuǎn)動畫的軸心為中心:
`animation = new RotateAnimation(0, +720, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);`
* 方式二:xml中設(shè)置`<rotate>`屬性中`pivotY="50%"`和`pivotX="50%"`來設(shè)定軸心為中點(diǎn):
```
<rotate
android:fromDegrees="-90"
android:toDegrees="0"
android:pivotY="50%"
android:pivotX="50%"
/>
```
3. **OOM問題:**
主要出現(xiàn)在幀動畫當(dāng)中,當(dāng)圖片數(shù)量較多并且圖片較大時就容易出現(xiàn)OOM(App擁有的內(nèi)存已經(jīng)裝不下圖片了)。所以,盡量避免使用幀動畫。
4. **內(nèi)存泄露:**
屬性動畫中有一類無線循環(huán)動畫,這類動畫需要在Activity退出時及時停止,否則將導(dǎo)致Activity無法釋放從而造成內(nèi)存泄露(ML)。通過驗(yàn)證發(fā)現(xiàn)View動畫不存在此問題。
5. **兼容性問題:**
動畫在Android3.0(API 11)以下的系統(tǒng)上有兼容性問題,所以,需要適當(dāng)使用兼容庫如nineoldandroids等進(jìn)行適配(目前大多Android手機(jī)版本在4.0以上,這一點(diǎn)問題不大)
6. **View動畫的問題:**
View動畫是對View的影像做動畫,不是真的改變View的布局屬性和狀態(tài),所以,有時會出現(xiàn):動畫完成后View無法隱藏的情況,即`setVisibility(View.GONE);`失效。解決方法:調(diào)用`view.clearAnimation()`清除View動畫即可。
7. **不要使用px:**
由于不同尺寸的手機(jī),dpi不一樣,所以,用dp設(shè)定動畫更容易適配不同手機(jī)。下面分享個人的一個dp轉(zhuǎn)px的工具類DiaplayUtil:
```
/**
* 屏幕顯示相關(guān)工具類
* (px ,sp, dp 等轉(zhuǎn)換)
*/
public class DisplayUtil {
private static Map<ScreenEnum,Integer> screenMap = new HashMap<>();
/**
* 將px值轉(zhuǎn)換為dip或dp值,保證尺寸大小不變
*
* @param pxValue
* @return
*/
public static int px2dip(float pxValue, Activity activity) {
return (int) (pxValue / getScreenMsg(activity).get(ScreenEnum.Density) + 0.5f);
}
/**
* 將dip或dp值轉(zhuǎn)換為px值,保證尺寸大小不變
* @return
*/
public static int dip2px(float dipValue, Activity activity) {
return (int) (dipValue * getScreenMsg(activity).get(ScreenEnum.Density) + 0.5f);
}
/**
* 將px值轉(zhuǎn)換為sp值,保證文字大小不變
*
* @return
*/
public static int px2sp(float pxValue, Activity activity) {
return (int) (pxValue / getScreenMsg(activity).get(ScreenEnum.ScaledDensity) + 0.5f);
}
/**
* 將sp值轉(zhuǎn)換為px值,保證文字大小不變
* @return
*/
public static int sp2px(float spValue, Activity activity) {
return (int) (spValue * getScreenMsg(activity).get(ScreenEnum.ScaledDensity) + 0.5f);
}
/**
* 獲取屏幕尺寸等信息
*/
public static Map<ScreenEnum,Integer> getScreenMsg(Activity activity){
DisplayMetrics metric = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(metric);
int width = metric.widthPixels;
int height = metric.heightPixels;
float density = metric.density;///屏幕密度(0.75, 1.0 . 1.5)
int densityDpi = metric.densityDpi;///屏幕密度DPI(120/160/240/320/480)
float scaledDensity = metric.scaledDensity;
if (screenMap==null) screenMap = new HashMap<>();
screenMap.clear();
screenMap.put(ScreenEnum.Width,width);
screenMap.put(ScreenEnum.Height,height);
screenMap.put(ScreenEnum.Density,(int)density);
screenMap.put(ScreenEnum.DendityDpi,densityDpi);
screenMap.put(ScreenEnum.ScaledDensity, (int)scaledDensity);
return screenMap;
}
enum ScreenEnum{
Width,Height,Density,DendityDpi,ScaledDensity
}
}
```
8. **動畫元素的交互:**
這里就需要和TranslateAnimation位移動畫一起講解了:由于位移動畫只是動畫過程中移動view的影像,不是實(shí)際的view布局位置的移動,所以,當(dāng)view擁有點(diǎn)擊事件時,位移動畫完成后:view的點(diǎn)擊位置和view的實(shí)際位置會不一致(3.0以前:View動畫和屬性動畫都一樣,新位置無法點(diǎn)擊,舊位置可以點(diǎn)擊;3.0及以后:屬性動畫的單擊事件觸發(fā)位置為移動后的位置,但是View動畫仍在原位置。)
<u>解決方法</u>:改變view的布局屬性,讓view在動畫結(jié)束時實(shí)際移動。
```
animation = new TranslateAnimation(0,0,0,-160);
animation.setDuration(2000);
animation.setInterpolator(getActivity(),android.R.anim.accelerate_decelerate_interpolator);
animation.setFillAfter(true);
animation.setAnimationListener(this);
getImageView().startAnimation(animation);
…………
@Override
public void onAnimationEnd(Animation animation) {
//改變view的布局屬性,讓view在動畫結(jié)束時實(shí)際移動
getImageView().offsetTopAndBottom(-160);
}
```
9. **硬件加速:**
建議在<u>頻繁使用到動畫的地方</u>使用硬件加速。
Android從3.0(API11)開始,在繪制View的時候支持硬件加速,充分利用GPU的特性,使得繪制更加平滑,但是會多消耗一些內(nèi)存。
* Application級別:`<applicationandroid:hardwareAccelerated="true" ...>`
* Activity級別:`<activity android:hardwareAccelerated="false" ...>`
* Window級別:
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
注意: 目前為止,Android還不支持在Window級別關(guān)閉硬件加速.
* View級別:`myView.setLayerType(View.LAYER_TYPE_HARDWARE, null);`
注意:目前為止,Android還不支持在View級別開啟硬件加速。
* 檢測是否啟用了硬件加速:
// 方法一
// 此方法返回true,如果myView掛在一個開啟了硬件加速的Window之下,
// 也就是說,它在繪制的時候不一定使用了硬件加速,getDrawingCache
myView.isHardwareAccelerated();
// 方法二
// 返回true,如果canvas在繪制的時候啟用了硬件加速
// 盡量采用此方法來判斷是否開啟了硬件加速
canvas.isHardwareAccelerated();
* 硬件加速可能出現(xiàn)的限制:
1.某些UI元素沒有顯示:可能是沒有調(diào)用invalidate
2.某些UI元素沒有更新:可能是沒有調(diào)用invalidate
3.繪制不正確:可能使用了不支持硬件加速的操作, 需要關(guān)閉硬件加速或者繞過該操作
4.拋出異常:可能使用了不支持硬件加速的操作, 需要關(guān)閉硬件加速或者繞過該操作
---
## Demo與推薦文章
本文中用到的一些相關(guān)的R.anim.xxx文件可能沒有詳細(xì)說明代碼,可下載Demo
Demo地址:https://github.com/androidjp/anim-demo
Android動畫推薦文章(想了解更詳細(xì)可以看看):
http://www.cnblogs.com/ldq2016/p/5407061.html