4.1 問題
應(yīng)用程序要讓視圖對象運動起來酷愧,實現(xiàn)變化或其他特效度秘。
4.2 解決方案
(API Level12)
ObjectAnimator實例广鳍,例如ViewPropertyAnimator,可以用來操作View對象的屬性说莫,例如視圖的位置或旋轉(zhuǎn)。ViewPropertyAnimation是通過View.animate()獲得的膝蜈,然后根據(jù)動畫的特征進(jìn)行修改。通過這個API進(jìn)行的修改會影響到View對象本身的真實屬性熔掺。
4.3 實現(xiàn)機制
ViewPropertyAnimation是對視圖內(nèi)容制作動畫的最便利方法饱搏。此API的工作方式就像生成器一樣,所有對不同屬性修改的調(diào)用都可以連接起來組成一個動畫置逻。在當(dāng)前線程的Looper的相同迭代中推沸,對ViewPropertyAnimation的所有調(diào)用都會匯集到一個動畫中。以下兩個代碼演示了一個簡單的視圖過渡Activity券坞。
res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/toggleButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Click to Toggle" />
<View
android:id="@+id/theView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#AAA" />
</LinearLayout>
使用了ViewPropertyAnimation的Activity
public class AnimateActivity extends Activity implements View.OnClickListener {
private View mViewToAnimate;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button button = (Button)findViewById(R.id.toggleButton);
button.setOnClickListener(this);
mViewToAnimate = findViewById(R.id.theView);
}
@Override
public void onClick(View v) {
if(mViewToAnimate.getAlpha() > 0f) {
//如果視圖可見鬓催,將其從右側(cè)滑出
mViewToAnimate.animate().alpha(0f).translationX(500f);
} else {
//如果視圖是隱藏的,原地做漸顯動畫
//Property Animations會實際修改視圖恨锚,因此必須首先恢復(fù)視圖的位置
mViewToAnimate.setTranslationX(0f);
mViewToAnimate.animate().alpha(1f);
}
}
}
在這個示例中宇驾,滑動動畫和漸顯動畫是通過alpha和translationX(這個過渡值需要足夠大才能夠讓視圖移除屏幕)屬性一起實現(xiàn)的。我們不需要將這些方法調(diào)用鏈接到一起猴伶,從而組成一個動畫课舍。即使我們在不同的地方調(diào)用菌瘫,它們還是會一起執(zhí)行,因為它們都是在主線程的Looper的相同迭代中設(shè)置的布卡。
注意雨让,這里我們首先恢復(fù)了View的過渡屬性,然后運行了沒有滑動效果的漸顯動畫忿等。這是因為屬性動畫會修改視圖本身栖忠,而不只是暫時地繪制(之前的動畫API的機制)。如果不恢復(fù)這個屬性贸街,依然會有漸顯動畫庵寞,但會在屏幕外右側(cè)1000像素處進(jìn)行。
1.ObjectAnimation
雖然ViewPropertyAnimation可以很方便且快速地實現(xiàn)簡單的屬性動畫薛匪,但對于一些更加復(fù)雜的工作捐川,例如將多個動畫鏈接到一起,這種方式會受到一定的限制逸尖。這時我們可以使用它的父類ObjectAnimator古沥。通過ObjectAnimator,我們可以設(shè)置監(jiān)聽器娇跟,從而在動畫開始和結(jié)束時得到相應(yīng)的通知岩齿;另外在動畫做增量更新時也可以得到通知。
以下兩個代碼顯示了如何使用ObjectAnimator來修改我們的Flipper動畫代碼:
res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/flip_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
</FrameLayout>
使用Objectanimator實現(xiàn)擲硬幣動畫
public class FlipperActivity extends Activity {
private boolean mIsHeads;
private ObjectAnimator mFlipper;
private Bitmap mHeadsImage, mTailsImage;
private ImageView mFlipImage;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mHeadsImage = BitmapFactory.decodeResource(getResources(), R.drawable.heads);
mTailsImage = BitmapFactory.decodeResource(getResources(), R.drawable.tails);
mFlipImage = (ImageView)findViewById(R.id.flip_image);
mFlipImage.setImageBitmap(mHeadsImage);
mIsHeads = true;
mFlipper = ObjectAnimator.ofFloat(mFlipImage, "rotationY", 0f, 360f);
mFlipper.setDuration(500);
mFlipper.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (animation.getAnimatedFraction() >= 0.25f && mIsHeads) {
mFlipImage.setImageBitmap(mTailsImage);
mIsHeads = false;
}
if (animation.getAnimatedFraction() >= 0.75f && !mIsHeads) {
mFlipImage.setImageBitmap(mHeadsImage);
mIsHeads = true;
}
}
});
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN) {
mFlipper.start();
return true;
}
return super.onTouchEvent(event);
}
}
屬性動畫提供了一些之前舊動畫系統(tǒng)沒有的變換功能苞俘,例如x軸和y軸的旋轉(zhuǎn)效果盹沈,從而實現(xiàn)三維變換效果。本例中吃谣,我們不需要計算縮放比例來是實現(xiàn)旋轉(zhuǎn)乞封,只需要告訴視圖沿著y軸旋轉(zhuǎn)即可。正因為如此岗憋,我們不再需要使用兩個動畫來操作硬幣肃晚,整個旋轉(zhuǎn)過程只需要操作視圖的rotationY屬性即可。
另一個強大之處就是AnimationUpdateListener澜驮,它提供了動畫運行過程中的常規(guī)回調(diào)方法陷揪。getAnimationFraction()方法會返回當(dāng)前動畫完成的百分比惋鸥。還可以通過getAnimatedValue()得到當(dāng)前動畫某個屬性的準(zhǔn)確值杂穷。
本例中,我們使用第一個方法在動畫運行到兩個時刻(硬幣可以換面卦绣,即90°和270°或者動畫時長的25%和75%)時耐量,更換正反面的圖片。因為并不能保證從每個角度我們可以得到通知滤港,所有當(dāng)達(dá)到閾值后廊蜒,我們會立即更換圖片趴拧。我們還設(shè)置了一個布爾標(biāo)識來避免在旋轉(zhuǎn)過程中對同一個值進(jìn)行重復(fù)的圖片設(shè)置(這會產(chǎn)生不必要的性能損耗)。
如果應(yīng)用程序需要鏈接多個動畫山叮,ObjectAnimation還可以支持更加傳統(tǒng)的AnimationListener來響應(yīng)動畫的主要事件著榴,例如開始、結(jié)束和重復(fù)屁倔。
提示:
在Android4.4上脑又,Animation還支持pause()和resume()方法以掛起運行中的動畫而不用完全取消它。
2.AnimationSet
如果需要執(zhí)行多個動畫锐借,則可以在AnimationSet中聚集這些動畫问麸。可以同時播放AnimationSet中的所有動畫钞翔,或者依次播放每個動畫严卖。定義動畫集合的Java代碼可能稍微有些冗長,因此我們將在此例中改為使用XML動畫格式布轿。以下代碼定義了一組將應(yīng)用于硬幣翻轉(zhuǎn)的動畫哮笆。
res/animator/flip.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together">
<!-- 建立硬幣旋轉(zhuǎn)的線性重復(fù) -->
<objectAnimator
android:propertyName="rotationX"
android:duration="400"
android:valueFrom="0"
android:valueTo="360"
android:valueType="floatType"
android:repeatMode="restart"
android:repeatCount="3"
android:interpolator="@android:interpolator/linear"/>
<!-- 增加一個提升動畫以顯示硬幣在空中上升 -->
<objectAnimator
android:propertyName="translationY"
android:duration="800"
android:valueTo="-200"
android:valueType="floatType"
android:repeatMode="reverse"
android:repeatCount="1" />
</set>
此處我們在<set>中定義了兩個同時播放的動畫(通過android:ordering = "together")。第一個動畫我們前面以及看過汰扭,用于旋轉(zhuǎn)硬幣圖片一次疟呐。此動畫設(shè)置為重復(fù)3次,提供3個完整的旋轉(zhuǎn)东且。圖片的默認(rèn)插值器是加速/減速時間曲線启具,這看起來會結(jié)束硬幣翻轉(zhuǎn)動畫。為提供全程一致的速度珊泳,我們改為對動畫應(yīng)用系統(tǒng)的線性插值器鲁冯。
第二個動畫使硬幣在旋轉(zhuǎn)期間沿著視圖向上滑動,看起來就像是硬幣被扔到空中的效果色查。因為硬幣還必須落回來薯演,將此動畫設(shè)置為在完成后反向運行一次。
以下代碼顯示了附加到翻轉(zhuǎn)器Activity的新動畫秧了。
帶有XML版本的AnimatorSet的翻轉(zhuǎn)器動畫
public class FlipperActivity extends Activity {
private boolean mIsHeads;
private AnimatorSet mFlipper;
private Bitmap mHeadsImage, mTailsImage;
private ImageView mFlipImage;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mHeadsImage = BitmapFactory.decodeResource(getResources(), R.drawable.heads);
mTailsImage = BitmapFactory.decodeResource(getResources(), R.drawable.tails);
mFlipImage = (ImageView)findViewById(R.id.flip_image);
mFlipImage.setImageResource(R.drawable.heads);
mIsHeads = true;
mFlipper = (AnimatorSet) AnimatorInflater.loadAnimator(this, R.animator.flip);
mFlipper.setTarget(mFlipImage);
ObjectAnimator flipAnimator = (ObjectAnimator) mFlipper.getChildAnimations().get(0);
flipAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (animation.getAnimatedFraction() >= 0.25f && mIsHeads) {
mFlipImage.setImageBitmap(mTailsImage);
mIsHeads = false;
}
if (animation.getAnimatedFraction() >= 0.75f && !mIsHeads) {
mFlipImage.setImageBitmap(mHeadsImage);
mIsHeads = true;
}
}
});
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN) {
mFlipper.start();
return true;
}
return super.onTouchEvent(event);
}
}
在此例中跨扮,我們在XML中使用AnimatorInflater構(gòu)造AnimatorSet對象。產(chǎn)生的動畫必須通過setTarget()附加到適當(dāng)?shù)哪繕?biāo)視圖验毡,這是ObjectAnimator.ofFloat()隱式完成的工作衡创。我們?nèi)匀恍枰狝nimatorUpdateListener確定何時從頭部切換到尾部,但該偵聽器不能直接應(yīng)用于集合對象晶通。因此璃氢,我們必須使用getChildAnimations()查找集合內(nèi)的旋轉(zhuǎn)動畫,以便在適當(dāng)?shù)奈恢酶郊哟藗陕犉鳌?br> 運行此新的示例將產(chǎn)生更加真實的硬幣翻轉(zhuǎn)動畫狮辽。
提供drawable資源:
Demo下載地址:
1.4 動畫視圖