本篇文章主要介紹以下幾個知識點:
- View 動畫
- View 動畫的特殊使用場景
- 屬性動畫
- 使用動畫的注意事項
Android 的動畫可分三種:View 動畫谐檀、幀動畫办素、屬性動畫秦爆。
7.1 View 動畫
View 動畫的作用對象是 View绣硝,支持4種效果:平移動畫、縮放動畫、旋轉(zhuǎn)動畫屋彪、透明度動畫所宰。
幀動畫也屬于 View 動畫,但其表現(xiàn)形式不同畜挥。
7.1.1 View 動畫的種類
View 動畫可通過 XML 來定義(可讀性好)仔粥,也可通過代碼來動態(tài)創(chuàng)建,其4種動畫效果如下:
要使用 View 動畫蟹但,首先要創(chuàng)建動畫的 XML 文件躯泰,其路徑為:res/anim/filename.xml。其固定語法如下:
<?xml version="1.0" encoding="utf-8"?>
<!-- set標(biāo)簽 動畫集合华糖,對應(yīng) AnimationSet 類斟冕,可包含若干個動畫
interpolator 動畫集合采用的插值器,影響動畫的速度(可不指定)
shareInterpolator 是否和集合共享同一個插值器
duration 持續(xù)時間
fillAfter 動畫結(jié)束后 View 是否停留在結(jié)束位置 -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="true"
android:duration="32"
android:fillAfter="true">
<!-- alpha標(biāo)簽 透明度動畫缅阳,對應(yīng) AlphaAnimation 類
fromAlpha 透明度的起始值
toAlpha 透明度的結(jié)束值 -->
<alpha
android:fromAlpha="0.1"
android:toAlpha="1"/>
<!-- scale標(biāo)簽 縮放動畫磕蛇,對應(yīng) ScaleAnimation 類
fromXScale 水平方向縮放的起始值
toXScale 水平方向縮放的結(jié)束值
fromYScale 豎直方向縮放的起始值
toYScale 豎直方向縮放的結(jié)束值
pivotX 縮放的軸點的 x 坐標(biāo)
pivotY 縮放的軸點的 y 坐標(biāo) -->
<scale
android:fromXScale="0.5"
android:toXScale="1.2"
android:fromYScale="32"
android:toYScale="32"
android:pivotX="32"
android:pivotY="32"/>
<!-- translate標(biāo)簽 平移動畫,對應(yīng) TranslateAnimation 類
fromXDelta x 的起始值
toXDelta x 的結(jié)束值
fromYDelta y 的起始值
toYDelta y 的結(jié)束值 -->
<translate
android:fromXDelta="0"
android:toXDelta="32"
android:fromYDelta="0"
android:toYDelta="32"/>
<!-- rotate標(biāo)簽 旋轉(zhuǎn)動畫十办,對應(yīng) RotateAnimation 類
fromDegrees 旋轉(zhuǎn)開始的角度
toDegrees 旋轉(zhuǎn)結(jié)束的角度
pivotX 旋轉(zhuǎn)的軸點的 x 坐標(biāo)
pivotY 旋轉(zhuǎn)的軸點的 y 坐標(biāo) -->
<rotate
android:fromDegrees="0"
android:toDegrees="180"
android:pivotX="32"
android:pivotY="32" />
<set>
<!-- ... -->
</set>
</set>
下面舉個例子秀撇,創(chuàng)建動畫xml文件如下:
<!-- res/anim/chapter_07_view_animation_test.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:zAdjustment="normal"
android:fillAfter="true">
<translate
android:duration="100"
android:fromXDelta="0"
android:fromYDelta="0"
android:interpolator="@android:anim/linear_interpolator"
android:toXDelta="100"
android:toYDelta="100"/>
<rotate
android:duration="400"
android:fromDegrees="0"
android:toDegrees="90" />
</set>
然后在代碼中應(yīng)用上面的動畫如下:
ImageView mImage01 = (ImageView) findViewById(R.id.iv_view_anim_01);
Animation animation = AnimationUtils.loadAnimation(this, R.anim.chapter_07_view_animation_test);
mImage01.startAnimation(animation);
運行效果:
除在 XML 中定義動畫外,還可以通過代碼來應(yīng)用動畫向族,如下:
// 將一張圖片的透明度在1000ms內(nèi)由0變1呵燕。
ImageView mImage02 = (ImageView) findViewById(R.id.iv_view_anim_02);
AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
alphaAnimation.setDuration(1000);
mImage02.startAnimation(alphaAnimation);
運行效果:
另外,通過 Animation 的 setAnimationListener
可給 View 動畫添加過程監(jiān)聽件相,接口如下:
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
7.1.2 自定義 View 動畫
自定義動畫需要繼承 Animation 這個類再扭,重寫它的 initialize
(初始化工作)和 applyTransformation
(進行相應(yīng)的矩陣變換)方法即可。(矩陣變換是數(shù)學(xué)上的概念)
這里提供一個來自 Android 的 ApiDemos 中的一個自定義 View 動畫的例子:
/**
* Function:類3D效果:圍繞y軸旋轉(zhuǎn)并同時沿著z軸平移
* Author:Wonderful on 2017/8/21 11:19
*/
public class Rotate3dAnimation extends Animation{
private final float mFromDegrees;
private final float mToDegrees;
private final float mCenterX;
private final float mCenterY;
private final float mDepthZ;
private final boolean mReverse;
private Camera mCamera;
public Rotate3dAnimation(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();
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation 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(0.0f, 0.0f, mDepthZ * interpolatedTime);
} else {
camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));
}
camera.rotateY(degrees);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
}
}
運行效果:
7.1.3 幀動畫
幀動畫是順序播放一組預(yù)先定義好的圖片夜矗,類似于電影播放泛范,用類 AnimationDrawable 來使用幀動畫。
首先通過 XML 定義一個 AnimationDrawable 如下:
<!-- res/drawable/chapter_07_frame_animation.xml -->
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/summer_01" android:duration="500" />
<item android:drawable="@drawable/summer_02" android:duration="500" />
<item android:drawable="@drawable/summer_03" android:duration="500" />
</animation-list>
然后在代碼中應(yīng)用上面的動畫如下:
ImageView mImage04 = (ImageView) findViewById(R.id.iv_frame_anim);
mImage04.setBackgroundResource(R.drawable.chapter_07_frame_animation);
AnimationDrawable drawable = (AnimationDrawable) mImage04.getBackground();
drawable.start();
運行效果:
幀動畫使用簡單紊撕,但容易引起 OOM罢荡,應(yīng)盡量少使用過多尺寸大的圖片。
7.2 View 動畫的特殊使用場景
View 動畫還可以在一些特殊場景使用对扶,如在 ViewGroup 中控制子元素的出場效果区赵,在 Activity 中實現(xiàn)其切換效果。
7.2.1 LayoutAnimation
LayoutAnimation 也是一個 View 動畫浪南,作用于 ViewGroup慰照,為 ViewGroup 指定一個動畫善镰,控制子元素的出場動畫效果技肩。使用步驟如下:
1. 定義 LayoutAnimation
<!-- res/anim/chapter_07_view_animation_layout.xml -->
<!-- delay 子元素開始動畫的時間延遲
animationOrder 子元素的動畫順序
animation 為子元素指定具體的入場動畫-->
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:delay="0.5"
android:animationOrder="normal"
android:animation="@anim/chapter_07_view_animation_layout_item"/>
2. 為子元素指定具體的入場動畫
<!-- res/anim/anim/chapter_07_view_animation_layout_item.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500"
android:interpolator="@android:anim/accelerate_interpolator"
android:shareInterpolator="true">
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0"/>
<translate
android:fromXDelta="500"
android:toXDelta="0"/>
</set>
3. 為 ViewGroup 指定 android:layoutAnimation
屬性
<!-- 指定 layoutAnimation 屬性,ListView 的 item 就具有出場動畫了
這種方式適用于所有的 ViewGroup -->
<ListView
android:id="@+id/lv_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layoutAnimation="@anim/chapter_07_view_animation_layout"
android:background="#fff4f7f9"
android:cacheColorHint="#00000000"
android:divider="#dddbdb"
android:dividerHeight="1.0px"
android:listSelector="@android:color/transparent"/>
當(dāng)然也可以通過 LayoutAnimationController
在代碼中實現(xiàn):
// 為 listView 指定入場動畫
Animation animation = AnimationUtils.loadAnimation(this, R.anim.chapter_07_view_animation_layout_item);
LayoutAnimationController controller = new LayoutAnimationController(animation);
controller.setDelay(0.5f);
controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
mListView.setLayoutAnimation(controller);
運行效果:
7.2.2 Activity 的切換效果
Activity 有默認(rèn)的切換效果抬伺,也可用 overridePendingTransition(int enterAnim, int exitAnim)
自定義切換效果,這個方法必須在 startActivity(Intent)
或 finish()
后調(diào)用才能生效,其兩參數(shù)含義如下:
enterAnim
Acitivity 被打開時所需的動畫資源 id;exitAnim
Activity 被暫停時所需的動畫資源 id。
下面舉個例子到千,創(chuàng)建動畫xml文件如下:
<!-- res/anim/enter_anim.xml -->
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromYDelta="100%"
android:toYDelta="0"
android:duration="1000" />
<!-- res/anim/exit_anim.xml -->
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromYDelta="0"
android:toYDelta="100%"
android:duration="1000" />
在代碼中使用如下:
// 啟動 AnimViewActivity 時
IntentUtils.to(this, AnimViewActivity.class);
overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);
@Override
public void finish() {
super.finish();
// 退出 AnimViewActivity 時
overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);
}
運行效果:
Fragment 可通過 FragmentTransaction
中的 setCustomAnimations()
方法來添加切換動畫昌渤。
7.3 屬性動畫
屬性動畫是 API 11 新加入的特性,對作用對象進行了擴展憔四,可對任何對象做動畫膀息。在 API 11 之前的系統(tǒng)上使用屬性動畫,可采用開源動畫庫 nineoldandroids了赵。
7.3.1 使用屬性動畫
下面先簡單介紹幾個例子看看如何使用屬性動畫潜支。
(1)
// 改變一個對象的 translationY 屬性,讓其沿著 Y 軸向上平移一段距離
ObjectAnimator.ofFloat(ivObject01, "translationY", -ivObject01.getHeight()).start();
(2)
// 改變一個對象的背景色屬性
ValueAnimator colorAnim = ObjectAnimator.ofInt(tvObject02, "backgroundColor",0xFFFF8080, 0xFF8080FF);
colorAnim.setDuration(3000);
colorAnim.setEvaluator(new ArgbEvaluator());
colorAnim.setRepeatCount(ValueAnimator.INFINITE);
colorAnim.setRepeatMode(ValueAnimator.REVERSE);
colorAnim.start();
(3)
// 動畫集合柿汛,5 秒內(nèi)對 View 的旋轉(zhuǎn)冗酿、平移、縮放络断、透明度改變
AnimatorSet set = new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(ivObject03, "rotationX", 0, 360),
ObjectAnimator.ofFloat(ivObject03, "rotationY", 0, 180),
ObjectAnimator.ofFloat(ivObject03, "rotation", 0, -90),
ObjectAnimator.ofFloat(ivObject03, "translationX", 0, 90),
ObjectAnimator.ofFloat(ivObject03, "translationY", 0, 90),
ObjectAnimator.ofFloat(ivObject03, "scaleX", 1, 1.5f),
ObjectAnimator.ofFloat(ivObject03, "scaleY", 1, 0.5f),
ObjectAnimator.ofFloat(ivObject03, "alpha", 1, 0.25f, 1)
);
set.setDuration(5 * 1000).start();
運行效果:
屬性動畫也可以通過 XML 來定義(res/animator 目錄下)裁替,其語法如下:
<?xml version="1.0" encoding="utf-8"?>
<!-- set標(biāo)簽 動畫集合,對應(yīng) AnimationSet 類貌笨,可包含若干個動畫
ordering 1. together 同時播放(默認(rèn)) 2. sequentially 依次播放 -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together">
<!-- objectAnimator標(biāo)簽 對應(yīng) ObjectAnimator 類
propertyName 作用對象的屬性的名稱(若指定顏色則不需要指定屬性valueType)
duration 動畫的時長
valueFrom 屬性的起始值
valueTo 屬性的結(jié)束值
startOffset 動畫的延遲時間
repeatCount 動畫的重復(fù)次數(shù)(默認(rèn)0弱判,-1 表無限循環(huán))
repeatMode 動畫的重復(fù)模式
valueType propertyName 所指定的屬性的類型
1. intType 整型 2.floatType 浮點型-->
<objectAnimator
android:propertyName="string"
android:duration="1"
android:valueFrom="float|int|color"
android:valueTo="float|int|color"
android:startOffset="1"
android:repeatCount="1"
android:repeatMode="restart"
android:valueType="intType"/>
<!-- animator 標(biāo)簽 對應(yīng) ValueAnimator 類
其屬性含義和 objectAnimator 的一樣 -->
<animator
android:duration="1"
android:valueFrom="float|int|color"
android:valueTo="float|int|color"
android:startOffset="1"
android:repeatCount="1"
android:repeatMode="restart"
android:valueType="intType"/>
<set>
<!-- ... -->
</set>
</set>
下面舉個例子,定義屬性動畫如下:
<!-- res/animator/property_animator.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together">
<objectAnimator
android:propertyName="x"
android:duration="300"
android:valueTo="200"
android:valueType="intType"/>
<objectAnimator
android:propertyName="y"
android:duration="300"
android:valueTo="200"
android:valueType="intType"/>
</set>
然后在代碼中應(yīng)用上面的動畫如下:
AnimatorSet animatorSet = (AnimatorSet) AnimatorInflater.loadAnimator(this, R.animator.property_animator);
animatorSet.setTarget(ivObject04);
animatorSet.start();
在實際開發(fā)中建議采用代碼來實現(xiàn)屬性動畫锥惋。
7.3.2 理解插值器和估值器
TimeInterpolator昌腰,時間插值器,其作用是根據(jù)時間流逝的百分比來計算出當(dāng)前屬性值改變的百分比膀跌,系統(tǒng)預(yù)置的有:
LinearInterpolator
線性插值器:勻速動畫AccelerateDecelerateInterpolator
加速減速插值器:動畫兩頭慢中間快DecelerateInterpolator
減速插值器:動畫越來越慢
TypeEvaluator遭商,類型估值算法,即估值器捅伤,其作用是根據(jù)當(dāng)前屬性改變的百分比來計算改變后的屬性值株婴,系統(tǒng)預(yù)置的有:
- IntEvaluator 針對整形屬性
- FloatEvaluator 浮點型
- ArgbEvaluator Color
例子,一個勻速動畫暑认,采用線性插值器和整形估值算法困介,在 40ms內(nèi),View 的 x 屬性實現(xiàn)從0到40的轉(zhuǎn)變:
自定義插值器需實現(xiàn) Interpolator
或 TimeInterpolator
蘸际,自定義估值算法需實現(xiàn) TypeEvaluator
座哩。
7.3.3 屬性動畫的監(jiān)聽器
屬性動畫提供了監(jiān)聽器用于監(jiān)聽動畫的播放過程,主要有如下兩個接口:
- AnimatorListener
public static interface AnimatorListener {
/**
* 動畫開始
*/
void onAnimationStart(Animator animation);
/**
* 動畫結(jié)束
*/
void onAnimationEnd(Animator animation);
/**
* 動畫取消
*/
void onAnimationCancel(Animator animation);
/**
* 動畫重復(fù)播放
*/
void onAnimationRepeat(Animator animation);
}
針對上述方法粮彤,系統(tǒng)還提供了類 AnimatorListenerAdapter
根穷,用于有選擇性的實現(xiàn)上面的4個方法姜骡。
- AnimatorUpdateListener
public static interface AnimatorUpdateListener {
/**
* 監(jiān)聽整個動畫過程,每播放一幀屿良,此方法調(diào)用一次
*/
void onAnimationUpdate(ValueAnimator animation);
}
7.3.4 對任意屬性做動畫
情景:實現(xiàn)讓Button的寬度從當(dāng)前寬度增加到500px的動畫圈澈。用屬性動畫如下(View 動畫不支持對寬度進行動畫):
@Override
public void onClick(View v){
if(v == mButton){
ObjectAnimator.ofInt(mButton, "width", 500).setDuration(5000).start();
}
}
運行后發(fā)現(xiàn)沒效果(下面解釋)。
對 object 的屬性 O 做動畫尘惧,讓動畫生效需同時滿足兩個條件:
(1)object 必須提供 setO 方法康栈,若沒傳遞初始值,還需提供 getO 方法喷橙。(不滿足啥么,程序直接 Crash)
(2)object 的 setO 對屬性 O 所做的改變必須能通過某種方法反映除了,如會改變 UI 之類的贰逾。(不滿足悬荣,動畫無效果但不會 Crash)
Button 繼承 TextView 有 setWidth
方法,但 TextView 和 Button 的 setWidth
疙剑、getWidth
做的不是同一件事情氯迂,無法通過 setWidth
改變控件寬度。
因此上面情景中滿足了條件1而未滿足條件2言缤,動畫不生效囚戚。
針對上面問題,有如下3中解決方法:
1. 給你的對象加上 get
和 set
方法轧简,如果你有權(quán)限的話驰坊。
2. 用一個類來包裝原始對象,間接為其提供 get
和 set
方法哮独。
具體代碼如下:
@Override
public void onClick(View v){
if(v == mButton){
// 先將 Button 包裝
ViewWrapper wrapper = new ViewWrapper(mButton);
ObjectAnimator.ofInt(wrapper , "width", 500).setDuration(5000).start();
}
}
/**
* 此類用于包裝 View拳芙,間接為 View 提供 get 和 set 方法
*/
private static class ViewWrapper{
private View mTarget;
public ViewWrapper(View mTarget) {
this.mTarget = mTarget;
}
public int getWidth(){
return mTarget.getLayoutParams().width;
}
public void setWidth(int width){
// 修改 target 的寬度
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();
}
}
運行效果:
3. 采用 ValueAnimator,監(jiān)聽動畫過程皮璧,自己實現(xiàn)屬性的改變舟扎。
ValueAnimator 本身不作用于任何對象,即直接使用它無動畫效果悴务。它可對一個值做動畫睹限,監(jiān)聽其動畫過程,修改對象的屬性值讯檐,從而達(dá)到對象的動畫效果羡疗。具體代碼如下:
@Override
public void onClick(View v){
if(v == mButton){
performAnimate(mButton, mButton.getWidth(), 500);
}
}
private void performAnimate(final View target, final int start, final int end){
ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
// 持有一個 IntEvaluator 對象,方便下面估值時使用
private IntEvaluator mEvaluator = new IntEvaluator();
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 獲得當(dāng)前動畫的進度值别洪,整形叨恨,1-100之間
int currentValue = (int) animation.getAnimatedValue();
// 獲得當(dāng)前進度占整個動畫過程的比例,浮點型挖垛,0-1之間
float fraction = animation.getAnimatedFraction();
// 直接調(diào)用整型估值器痒钝,通過比例計算出寬度秉颗,然后再設(shè)給 Button
target.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end);
target.requestLayout();
}
});
valueAnimator.setDuration(5000).start();
}
運行效果和上面一樣。
7.3.5 屬性動畫的工作原理
屬性動畫要求動畫作用的對象提供該屬性的 set
方法送矩,屬性動畫根據(jù)傳遞該屬性的初始值和最終值蚕甥,以動畫的效果多次去調(diào)用 set
方法。
每次傳遞給 set
方法的值都不一樣栋荸,確切來時是隨著時間的推移菇怀,所傳遞的值越來越接近最終值。
若動畫時沒傳遞初始值蒸其,則還要提供 get
方法,因為系統(tǒng)要去獲取屬性的初始值库快。
7.4 使用動畫的注意事項
1. OOM 問題
在幀動畫中使用較多的大圖片時容易出現(xiàn) OOM摸袁,盡量避免使用幀動畫。
2. 內(nèi)存泄漏
在 Activity 退出時及時停止屬性動畫中的無限循環(huán)動畫义屏。
3. 兼容性問題
動畫在 3.0 以下系統(tǒng)有兼容性問題靠汁,要做好適配工作。
4. View 動畫的問題
View 動畫并不是真正改變 View 的狀態(tài)闽铐,有時完成動畫后無法隱藏 View蝶怔,即 setVisibility(View.GONE)
無效,此時只要調(diào)用 view.clearAnimation()
清除即可兄墅。
5. 不要使用 px
執(zhí)行動畫過程中盡量使用 dp踢星,避免在不同設(shè)備上出現(xiàn)不同的效果。
6. 動畫元素的交互
將 view 移動后隙咸,在 3.0 以下系統(tǒng)上沐悦,不管是 View 動畫還是屬性動畫,新位置均無法觸發(fā)點擊事件五督,但老位置仍然可以觸發(fā)點擊事件藏否。
7. 硬件加速
使用動畫過程中,建議開啟硬件加速充包,提高流暢性副签。
本篇文章就介紹到這。