在日常的Android開(kāi)發(fā)中,經(jīng)常會(huì)使用到動(dòng)畫(huà)俺泣,這里就對(duì)Android開(kāi)發(fā)中的動(dòng)畫(huà)做一下總結(jié)
Android 動(dòng)畫(huà)分類
總的來(lái)說(shuō)距潘,Android動(dòng)畫(huà)可以分為兩類,最初的傳統(tǒng)動(dòng)畫(huà)和Android3.0 之后出現(xiàn)的屬性動(dòng)畫(huà)圆恤;
傳統(tǒng)動(dòng)畫(huà)又包括 幀動(dòng)畫(huà)(Frame Animation)和補(bǔ)間動(dòng)畫(huà)(Tweened Animation)。
傳統(tǒng)動(dòng)畫(huà)
幀動(dòng)畫(huà)
幀動(dòng)畫(huà)是最容易實(shí)現(xiàn)的一種動(dòng)畫(huà)腔稀,這種動(dòng)畫(huà)更多的依賴于完善的UI資源盆昙,他的原理就是將一張張單獨(dú)的圖片連貫的進(jìn)行播放,
從而在視覺(jué)上產(chǎn)生一種動(dòng)畫(huà)的效果焊虏;有點(diǎn)類似于某些軟件制作gif動(dòng)畫(huà)的方式淡喜。
如上圖中的京東加載動(dòng)畫(huà),代碼要做的事情就是把一幅幅的圖片按順序顯示诵闭,造成動(dòng)畫(huà)的視覺(jué)效果炼团。
京東動(dòng)畫(huà)實(shí)現(xiàn)
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@drawable/a_0"
android:duration="100" />
<item
android:drawable="@drawable/a_1"
android:duration="100" />
<item
android:drawable="@drawable/a_2"
android:duration="100" />
</animation-list>
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_frame_animation);
ImageView animationImg1 = (ImageView) findViewById(R.id.animation1);
animationImg1.setImageResource(R.drawable.frame_anim1);
AnimationDrawable animationDrawable1 = (AnimationDrawable) animationImg1.getDrawable();
animationDrawable1.start();
}
可以說(shuō),圖片資源決定了這種方式可以實(shí)現(xiàn)怎樣的動(dòng)畫(huà)
在有些代碼中疏尿,我們還會(huì)看到android:oneshot="false" 瘟芝,這個(gè)oneshot 的含義就是動(dòng)畫(huà)執(zhí)行一次(true)還是循環(huán)執(zhí)行多次。
這里其他幾個(gè)動(dòng)畫(huà)實(shí)現(xiàn)方式都是一樣润歉,無(wú)非就是圖片資源的差異模狭。
補(bǔ)間動(dòng)畫(huà)
補(bǔ)間動(dòng)畫(huà)又可以分為四種形式,分別是 alpha(淡入淡出)踩衩,translate(位移)嚼鹉,scale(縮放大小)驱富,rotate(旋轉(zhuǎn))锚赤。
補(bǔ)間動(dòng)畫(huà)的實(shí)現(xiàn),一般會(huì)采用xml 文件的形式褐鸥;代碼會(huì)更容易書(shū)寫(xiě)和閱讀线脚,同時(shí)也更容易復(fù)用。
XML 實(shí)現(xiàn)
首先叫榕,在res/anim/ 文件夾下定義如下的動(dòng)畫(huà)實(shí)現(xiàn)方式
alpha_anim.xml 動(dòng)畫(huà)實(shí)現(xiàn)
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:fromAlpha="1.0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:toAlpha="0.0" />
scale.xml 動(dòng)畫(huà)實(shí)現(xiàn)
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:fromXScale="0.0"
android:fromYScale="0.0"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="1.0"
android:toYScale="1.0"/>
然后浑侥,在Activity中
Animation animation = AnimationUtils.loadAnimation(mContext, R.anim.alpha_anim);
img = (ImageView) findViewById(R.id.img);
img.startAnimation(animation);
這樣就可以實(shí)現(xiàn)ImageView alpha 透明變化的動(dòng)畫(huà)效果。
也可以使用set 標(biāo)簽將多個(gè)動(dòng)畫(huà)組合(代碼源自Android SDK API)
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@[package:]anim/interpolator_resource"
android:shareInterpolator=["true" | "false"] >
<alpha
android:fromAlpha="float"
android:toAlpha="float" />
<scale
android:fromXScale="float"
android:toXScale="float"
android:fromYScale="float"
android:toYScale="float"
android:pivotX="float"
android:pivotY="float" />
<translate
android:fromXDelta="float"
android:toXDelta="float"
android:fromYDelta="float"
android:toYDelta="float" />
<rotate
android:fromDegrees="float"
android:toDegrees="float"
android:pivotX="float"
android:pivotY="float" />
<set>
...
</set>
</set>
可以看到組合動(dòng)畫(huà)是可以嵌套使用的晰绎。
各個(gè)動(dòng)畫(huà)屬性的含義結(jié)合動(dòng)畫(huà)自身的特點(diǎn)應(yīng)該很好理解寓落,就不一一闡述了;這里主要說(shuō)一下interpolator 和 pivot荞下。
Interpolator 主要作用是可以控制動(dòng)畫(huà)的變化速率 伶选,就是動(dòng)畫(huà)進(jìn)行的快慢節(jié)奏。
Android 系統(tǒng)已經(jīng)為我們提供了一些Interpolator 尖昏,比如 accelerate_decelerate_interpolator仰税,accelerate_interpolator等。更多的interpolator 及其含義可以在Android SDK 中查看抽诉。同時(shí)這個(gè)Interpolator也是可以自定義的陨簇,這個(gè)后面還會(huì)提到。
pivot 決定了當(dāng)前動(dòng)畫(huà)執(zhí)行的參考位置
pivot 這個(gè)屬性主要是在translate 和 scale 動(dòng)畫(huà)中迹淌,這兩種動(dòng)畫(huà)都牽扯到view 的“物理位置“發(fā)生變化塞帐,所以需要一個(gè)參考點(diǎn)。而pivotX和pivotY就共同決定了這個(gè)點(diǎn)巍沙;它的值可以是float或者是百分比數(shù)值葵姥。
我們以pivotX為例,
pivotX取值 | 含義 |
---|---|
10 | 距離動(dòng)畫(huà)所在view自身左邊緣10像素 |
10% | 距離動(dòng)畫(huà)所在view自身左邊緣 的距離是整個(gè)view寬度的10% |
10%p | 距離動(dòng)畫(huà)所在view父控件左邊緣的距離是整個(gè)view寬度的10% |
pivotY 也是相同的原理句携,只不過(guò)變成的縱向的位置榔幸。如果還是不明白可以參考源碼,在Tweened Animation中結(jié)合seekbar的滑動(dòng)觀察rotate的變化理解矮嫉。
Java Code 實(shí)現(xiàn)
有時(shí)候削咆,動(dòng)畫(huà)的屬性值可能需要?jiǎng)討B(tài)的調(diào)整,這個(gè)時(shí)候使用xml 就不合適了蠢笋,需要使用java代碼實(shí)現(xiàn)
private void RotateAnimation() {
animation = new RotateAnimation(-deValue, deValue, Animation.RELATIVE_TO_SELF,
pxValue, Animation.RELATIVE_TO_SELF, pyValue);
animation.setDuration(timeValue);
if (keep.isChecked()) {
animation.setFillAfter(true);
} else {
animation.setFillAfter(false);
}
if (loop.isChecked()) {
animation.setRepeatCount(-1);
} else {
animation.setRepeatCount(0);
}
if (reverse.isChecked()) {
animation.setRepeatMode(Animation.REVERSE);
} else {
animation.setRepeatMode(Animation.RESTART);
}
img.startAnimation(animation);
}
這里animation.setFillAfter決定了動(dòng)畫(huà)在播放結(jié)束時(shí)是否保持最終的狀態(tài)拨齐;animation.setRepeatCount和animation.setRepeatMode 決定了動(dòng)畫(huà)的重復(fù)次數(shù)及重復(fù)方式,具體細(xì)節(jié)可查看源碼理解昨寞。
好了瞻惋,傳統(tǒng)動(dòng)畫(huà)的內(nèi)容就說(shuō)到這里了厦滤。
屬性動(dòng)畫(huà)
屬性動(dòng)畫(huà),顧名思義它是對(duì)于對(duì)象屬性的動(dòng)畫(huà)歼狼。因此掏导,所有補(bǔ)間動(dòng)畫(huà)的內(nèi)容,都可以通過(guò)屬性動(dòng)畫(huà)實(shí)現(xiàn)羽峰。
屬性動(dòng)畫(huà)入門(mén)
首先我們來(lái)看看如何用屬性動(dòng)畫(huà)實(shí)現(xiàn)上面補(bǔ)間動(dòng)畫(huà)的效果
private void RotateAnimation() {
ObjectAnimator anim = ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f);
anim.setDuration(1000);
anim.start();
}
private void AlpahAnimation() {
ObjectAnimator anim = ObjectAnimator.ofFloat(myView, "alpha", 1.0f, 0.8f, 0.6f, 0.4f, 0.2f, 0.0f);
anim.setRepeatCount(-1);
anim.setRepeatMode(ObjectAnimator.REVERSE);
anim.setDuration(2000);
anim.start();
}
這兩個(gè)方法用屬性動(dòng)畫(huà)的方式分別實(shí)現(xiàn)了旋轉(zhuǎn)動(dòng)畫(huà)和淡入淡出動(dòng)畫(huà)趟咆,其中setDuration、setRepeatMode及setRepeatCount和補(bǔ)間動(dòng)畫(huà)中的概念是一樣的梅屉。
可以看到值纱,屬性動(dòng)畫(huà)貌似強(qiáng)大了許多,實(shí)現(xiàn)很方便坯汤,同時(shí)動(dòng)畫(huà)可變化的值也有了更多的選擇虐唠,動(dòng)畫(huà)所能呈現(xiàn)的細(xì)節(jié)也更多。
當(dāng)然屬性動(dòng)畫(huà)也是可以組合實(shí)現(xiàn)的
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(myView, "alpha", 1.0f, 0.5f, 0.8f, 1.0f);
ObjectAnimator scaleXAnim = ObjectAnimator.ofFloat(myView, "scaleX", 0.0f, 1.0f);
ObjectAnimator scaleYAnim = ObjectAnimator.ofFloat(myView, "scaleY", 0.0f, 2.0f);
ObjectAnimator rotateAnim = ObjectAnimator.ofFloat(myView, "rotation", 0, 360);
ObjectAnimator transXAnim = ObjectAnimator.ofFloat(myView, "translationX", 100, 400);
ObjectAnimator transYAnim = ObjectAnimator.ofFloat(myView, "tranlsationY", 100, 750);
AnimatorSet set = new AnimatorSet();
set.playTogether(alphaAnim,scaleXAnim,scaleYAnim,rotateAnim,transXAnim,transYAnim);
//set.playSequentially(alphaAnim, scaleXAnim, scaleYAnim, rotateAnim, transXAnim, transYAnim);
set.setDuration(3000);
set.start();
可以看到這些動(dòng)畫(huà)可以同時(shí)播放玫霎,或者是按序播放凿滤。
屬性動(dòng)畫(huà)核心原理
在上面實(shí)現(xiàn)屬性動(dòng)畫(huà)的時(shí)候,我們反復(fù)的使用到了ObjectAnimator 這個(gè)類庶近,這個(gè)類繼承自ValueAnimator翁脆,使用這個(gè)類可以對(duì)任意對(duì)象的任意屬性進(jìn)行動(dòng)畫(huà)操作。而ValueAnimator是整個(gè)屬性動(dòng)畫(huà)機(jī)制當(dāng)中最核心的一個(gè)類鼻种;這點(diǎn)從下面的圖片也可以看出反番。
屬性動(dòng)畫(huà)核心原理,此圖來(lái)自于Android SDK API 文檔叉钥。
屬性動(dòng)畫(huà)的運(yùn)行機(jī)制是通過(guò)不斷地對(duì)值進(jìn)行操作來(lái)實(shí)現(xiàn)的罢缸,而初始值和結(jié)束值之間的動(dòng)畫(huà)過(guò)渡就是由ValueAnimator這個(gè)類來(lái)負(fù)責(zé)計(jì)算的。它的內(nèi)部使用一種時(shí)間循環(huán)的機(jī)制來(lái)計(jì)算值與值之間的動(dòng)畫(huà)過(guò)渡投队,我們只需要將初始值和結(jié)束值提供給ValueAnimator枫疆,并且告訴它動(dòng)畫(huà)所需運(yùn)行的時(shí)長(zhǎng),那么ValueAnimator就會(huì)自動(dòng)幫我們完成從初始值平滑地過(guò)渡到結(jié)束值這樣的效果敷鸦。除此之外息楔,ValueAnimator還負(fù)責(zé)管理動(dòng)畫(huà)的播放次數(shù)、播放模式扒披、以及對(duì)動(dòng)畫(huà)設(shè)置監(jiān)聽(tīng)器等值依。
從上圖我們可以了解到,通過(guò)duration碟案、startPropertyValue和endPropertyValue 等值愿险,我們就可以定義動(dòng)畫(huà)運(yùn)行時(shí)長(zhǎng),初始值和結(jié)束值价说。然后通過(guò)start方法開(kāi)始動(dòng)畫(huà)辆亏。
那么ValueAnimator 到底是怎樣實(shí)現(xiàn)從初始值平滑過(guò)渡到結(jié)束值的呢风秤?這個(gè)就是由TypeEvaluator 和TimeInterpolator 共同決定的。
具體來(lái)說(shuō)褒链,TypeEvaluator 決定了動(dòng)畫(huà)如何從初始值過(guò)渡到結(jié)束值唁情。
TimeInterpolator 決定了動(dòng)畫(huà)從初始值過(guò)渡到結(jié)束值的節(jié)奏疑苔。
說(shuō)的通俗一點(diǎn)甫匹,你每天早晨出門(mén)去公司上班,TypeEvaluator決定了你是坐公交惦费、坐地鐵還是騎車兵迅;而當(dāng)你決定騎車后,TimeInterpolator決定了你一路上騎行的方式薪贫,你可以勻速的一路騎到公司恍箭,你也可以前半程騎得飛快,后半程騎得慢悠悠瞧省。
如果扯夭,還是不理解,那么就看下面的代碼吧鞍匾。首先看一下下面的這兩個(gè)gif動(dòng)畫(huà)交洗,一個(gè)小球在屏幕上以 y=sin(x) 的數(shù)學(xué)函數(shù)軌跡運(yùn)行,同時(shí)小球的顏色和半徑也發(fā)生著變化橡淑,可以發(fā)現(xiàn)构拳,兩幅圖動(dòng)畫(huà)變化的節(jié)奏也是不一樣的。
如果不考慮屬性動(dòng)畫(huà)梁棠,這樣的一個(gè)動(dòng)畫(huà)純粹的使用Canvas+Handler的方式繪制也是有可能實(shí)現(xiàn)的置森。但是會(huì)復(fù)雜很多,而且加上各種線程符糊,會(huì)帶來(lái)很多意想不到的問(wèn)題凫海。
這里就通過(guò)自定義屬性動(dòng)畫(huà)的方式看看這個(gè)動(dòng)畫(huà)是如何實(shí)現(xiàn)的。
屬性動(dòng)畫(huà)自定義實(shí)現(xiàn)
這個(gè)動(dòng)畫(huà)最關(guān)鍵的三點(diǎn)就是 運(yùn)動(dòng)軌跡男娄、小球半徑及顏色的變化行贪;我們就從這三個(gè)方面展開(kāi)。最后我們?cè)诮Y(jié)合Interpolator說(shuō)一下TimeInterpolator的意義沪伙。
用TypeEvaluator 確定運(yùn)動(dòng)軌跡
前面說(shuō)了瓮顽,TypeEvaluator決定了動(dòng)畫(huà)如何從初始值過(guò)渡到結(jié)束值。這個(gè)TypeEvaluator是個(gè)接口围橡,我們可以實(shí)現(xiàn)這個(gè)接口暖混。
public class PointSinEvaluator implements TypeEvaluator {
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
Point startPoint = (Point) startValue;
Point endPoint = (Point) endValue;
float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());
float y = (float) (Math.sin(x * Math.PI / 180) * 100) + endPoint.getY() / 2;
Point point = new Point(x, y);
return point;
}
}
PointSinEvaluator 繼承了TypeEvaluator類,并實(shí)現(xiàn)了他唯一的方法evaluate翁授;這個(gè)方法有三個(gè)參數(shù)拣播,第一個(gè)參數(shù)fraction 代表當(dāng)前動(dòng)畫(huà)完成的百分比晾咪,這個(gè)值是如何變化的后面還會(huì)提到;第二個(gè)和第三個(gè)參數(shù)代表動(dòng)畫(huà)的初始值和結(jié)束值贮配。這里我們的邏輯很簡(jiǎn)單谍倦,x的值隨著fraction 不斷變化,并最終達(dá)到結(jié)束值泪勒;y的值就是當(dāng)前x值所對(duì)應(yīng)的sin(x) 值昼蛀,然后用x 和 y 產(chǎn)生一個(gè)新的點(diǎn)(Point對(duì)象)返回。
這樣我們就可以使用這個(gè)PointSinEvaluator 生成屬性動(dòng)畫(huà)的實(shí)例了圆存。
Point startP = new Point(RADIUS, RADIUS);//初始值(起點(diǎn))
Point endP = new Point(getWidth() - RADIUS, getHeight() - RADIUS);//結(jié)束值(終點(diǎn))
final ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointSinEvaluator(), startP, endP);
valueAnimator.setRepeatCount(-1);
valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentPoint = (Point) animation.getAnimatedValue();
postInvalidate();
}
});
這樣我們就完成了動(dòng)畫(huà)軌跡的定義叼旋,現(xiàn)在只要調(diào)用valueAnimator.start() 方法,就會(huì)繪制出一個(gè)正弦曲線的軌跡沦辙。
顏色及半徑動(dòng)畫(huà)實(shí)現(xiàn)
之前我們說(shuō)過(guò)夫植,使用ObjectAnimator 可以對(duì)任意對(duì)象的任意屬性進(jìn)行動(dòng)畫(huà)操作,這句話是不太嚴(yán)謹(jǐn)?shù)挠脱叮@個(gè)任意屬性還需要有g(shù)et 和 set 方法详民。
public class PointAnimView extends View {
/**
* 實(shí)現(xiàn)關(guān)于color 的屬性動(dòng)畫(huà)
*/
private int color;
private float radius = RADIUS;
.....
}
這里在我們的自定義view中,定義了兩個(gè)屬性color 和 radius陌兑,并實(shí)現(xiàn)了他們各自的get set 方法沈跨,這樣我們就可以使用屬性動(dòng)畫(huà)的特點(diǎn)實(shí)現(xiàn)小球顏色變化的動(dòng)畫(huà)和半徑變化的動(dòng)畫(huà)。
ObjectAnimator animColor = ObjectAnimator.ofObject(this, "color", new ArgbEvaluator(), Color.GREEN,
Color.YELLOW, Color.BLUE, Color.WHITE, Color.RED);
animColor.setRepeatCount(-1);
animColor.setRepeatMode(ValueAnimator.REVERSE);
ValueAnimator animScale = ValueAnimator.ofFloat(20f, 80f, 60f, 10f, 35f,55f,10f);
animScale.setRepeatCount(-1);
animScale.setRepeatMode(ValueAnimator.REVERSE);
animScale.setDuration(5000);
animScale.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
radius = (float) animation.getAnimatedValue();
}
});
這里诀紊,我們使用ObjectAnimator 實(shí)現(xiàn)對(duì)color 屬性的值按照ArgbEvaluator 這個(gè)類的規(guī)律在給定的顏色值之間變化谒出,這個(gè)ArgbEvaluator 和我們之前定義的PointSinEvaluator一樣,都是決定動(dòng)畫(huà)如何從初始值過(guò)渡到結(jié)束值的邻奠,只不過(guò)這個(gè)類是系統(tǒng)自帶的笤喳,我們直接拿來(lái)用就可以,他可以實(shí)現(xiàn)各種顏色間的自由過(guò)渡碌宴。
對(duì)radius 這個(gè)屬性使用了ValueAnimator杀狡,使用了其ofFloat方法實(shí)現(xiàn)了一系列float值的變化;同時(shí)為其添加了動(dòng)畫(huà)變化的監(jiān)聽(tīng)器贰镣,在屬性值更新的過(guò)程中呜象,我們可以將變化的結(jié)果賦給radius,這樣就實(shí)現(xiàn)了半徑動(dòng)態(tài)的變化碑隆。
這里radius 也可以使用和color相同的方式恭陡,只需要把ArgbEvaluator 替換為FloatEvaluator,同時(shí)修改動(dòng)畫(huà)的變化值即可上煤;使用添加監(jiān)聽(tīng)器的方式休玩,只是為了介紹監(jiān)聽(tīng)器的使用方法而已
好了,到這里我們已經(jīng)定義出了所有需要的動(dòng)畫(huà),前面說(shuō)過(guò)拴疤,屬性動(dòng)畫(huà)也是可以組合使用的永部。因此,在動(dòng)畫(huà)啟動(dòng)的時(shí)候呐矾,同時(shí)播放這三個(gè)動(dòng)畫(huà)苔埋,就可以實(shí)現(xiàn)圖中的效果了。
animSet = new AnimatorSet();
animSet.play(valueAnimator).with(animColor).with(animScale);
animSet.setDuration(5000);
animSet.setInterpolator(interpolatorType);
animSet.start();
PointAnimView 源碼
public class PointAnimView extends View {
public static final float RADIUS = 20f;
private Point currentPoint;
private Paint mPaint;
private Paint linePaint;
private AnimatorSet animSet;
private TimeInterpolator interpolatorType = new LinearInterpolator();
/**
* 實(shí)現(xiàn)關(guān)于color 的屬性動(dòng)畫(huà)
*/
private int color;
private float radius = RADIUS;
public PointAnimView(Context context) {
super(context);
init();
}
public PointAnimView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public PointAnimView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
mPaint.setColor(this.color);
}
public float getRadius() {
return radius;
}
public void setRadius(float radius) {
this.radius = radius;
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.TRANSPARENT);
linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
linePaint.setColor(Color.BLACK);
linePaint.setStrokeWidth(5);
}
@Override
protected void onDraw(Canvas canvas) {
if (currentPoint == null) {
currentPoint = new Point(RADIUS, RADIUS);
drawCircle(canvas);
// StartAnimation();
} else {
drawCircle(canvas);
}
drawLine(canvas);
}
private void drawLine(Canvas canvas) {
canvas.drawLine(10, getHeight() / 2, getWidth(), getHeight() / 2, linePaint);
canvas.drawLine(10, getHeight() / 2 - 150, 10, getHeight() / 2 + 150, linePaint);
canvas.drawPoint(currentPoint.getX(), currentPoint.getY(), linePaint);
}
public void StartAnimation() {
Point startP = new Point(RADIUS, RADIUS);
Point endP = new Point(getWidth() - RADIUS, getHeight() - RADIUS);
final ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointSinEvaluator(), startP, endP);
valueAnimator.setRepeatCount(-1);
valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentPoint = (Point) animation.getAnimatedValue();
postInvalidate();
}
});
//
ObjectAnimator animColor = ObjectAnimator.ofObject(this, "color", new ArgbEvaluator(), Color.GREEN,
Color.YELLOW, Color.BLUE, Color.WHITE, Color.RED);
animColor.setRepeatCount(-1);
animColor.setRepeatMode(ValueAnimator.REVERSE);
ValueAnimator animScale = ValueAnimator.ofFloat(20f, 80f, 60f, 10f, 35f,55f,10f);
animScale.setRepeatCount(-1);
animScale.setRepeatMode(ValueAnimator.REVERSE);
animScale.setDuration(5000);
animScale.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
radius = (float) animation.getAnimatedValue();
}
});
animSet = new AnimatorSet();
animSet.play(valueAnimator).with(animColor).with(animScale);
animSet.setDuration(5000);
animSet.setInterpolator(interpolatorType);
animSet.start();
}
private void drawCircle(Canvas canvas) {
float x = currentPoint.getX();
float y = currentPoint.getY();
canvas.drawCircle(x, y, radius, mPaint);
}
public void setInterpolatorType(int type ) {
switch (type) {
case 1:
interpolatorType = new BounceInterpolator();
break;
case 2:
interpolatorType = new AccelerateDecelerateInterpolator();
break;
case 3:
interpolatorType = new DecelerateInterpolator();
break;
case 4:
interpolatorType = new AnticipateInterpolator();
break;
case 5:
interpolatorType = new LinearInterpolator();
break;
case 6:
interpolatorType=new LinearOutSlowInInterpolator();
break;
case 7:
interpolatorType = new OvershootInterpolator();
default:
interpolatorType = new LinearInterpolator();
break;
}
}
@TargetApi(Build.VERSION_CODES.KITKAT)
public void pauseAnimation() {
if (animSet != null) {
animSet.pause();
}
}
public void stopAnimation() {
if (animSet != null) {
animSet.cancel();
this.clearAnimation();
}
}
}
TimeInterpolator 介紹
Interpolator的概念其實(shí)我們并不陌生蜒犯,在補(bǔ)間動(dòng)畫(huà)中我們就使用到了组橄。他就是用來(lái)控制動(dòng)畫(huà)快慢節(jié)奏的;而在屬性動(dòng)畫(huà)中愧薛,TimeInterpolator 也是類似的作用晨炕;TimeInterpolator 繼承自Interpolator衫画。我們可以繼承TimerInterpolator 以自己的方式控制動(dòng)畫(huà)變化的節(jié)奏毫炉,也可以使用Android 系統(tǒng)提供的Interpolator。
下面都是系統(tǒng)幫我們定義好的一些Interpolator削罩,我們可以通過(guò)setInterpolator 設(shè)置不同的Interpolator瞄勾。
這里我們使用的Interpolator就決定了 前面我們提到的fraction。變化的節(jié)奏決定了動(dòng)畫(huà)所執(zhí)行的百分比弥激。不得不說(shuō)进陡,這么ValueAnimator的設(shè)計(jì)的確是很巧妙。
XML 屬性動(dòng)畫(huà)
這里提一下微服,屬性動(dòng)畫(huà)當(dāng)然也可以使用xml文件的方式實(shí)現(xiàn)趾疚,但是屬性動(dòng)畫(huà)的屬性值一般會(huì)牽扯到對(duì)象具體的屬性,更多是通過(guò)代碼動(dòng)態(tài)獲取以蕴,所以xml文件的實(shí)現(xiàn)會(huì)有些不方便糙麦。
<set android:ordering="sequentially">
<set>
<objectAnimator
android:propertyName="x"
android:duration="500"
android:valueTo="400"
android:valueType="intType"/>
<objectAnimator
android:propertyName="y"
android:duration="500"
android:valueTo="300"
android:valueType="intType"/>
</set>
<objectAnimator
android:propertyName="alpha"
android:duration="500"
android:valueTo="1f"/>
</set>
使用方式:
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
R.anim.property_animator);
set.setTarget(myObject);
set.start();
xml 文件中的標(biāo)簽也和屬性動(dòng)畫(huà)的類相對(duì)應(yīng)。
ValueAnimator --- <animator>
ObjectAnimator --- <objectAnimator>
AnimatorSet --- <set>
這些就是屬性動(dòng)畫(huà)的核心內(nèi)容〈园梗現(xiàn)在使用屬性動(dòng)畫(huà)的特性自定義動(dòng)畫(huà)應(yīng)該不是難事了赡磅。其余便簽的含義,結(jié)合之前的內(nèi)容應(yīng)該不難理解了宝与。
傳統(tǒng)動(dòng)畫(huà) VS 屬性動(dòng)畫(huà)
相較于傳統(tǒng)動(dòng)畫(huà)焚廊,屬性動(dòng)畫(huà)有很多優(yōu)勢(shì)。那是否意味著屬性動(dòng)畫(huà)可以完全替代傳統(tǒng)動(dòng)畫(huà)呢习劫。其實(shí)不然咆瘟,兩種動(dòng)畫(huà)都有各自的優(yōu)勢(shì),屬性動(dòng)畫(huà)如此強(qiáng)大诽里,也不是沒(méi)有缺點(diǎn)袒餐。
從上面兩幅圖比較可以發(fā)現(xiàn),補(bǔ)間動(dòng)畫(huà)中,雖然使用translate將圖片移動(dòng)了匿乃,但是點(diǎn)擊原來(lái)的位置桩皿,依舊可以發(fā)生點(diǎn)擊事件,而屬性動(dòng)畫(huà)卻不是幢炸。因此我們可以確定泄隔,屬性動(dòng)畫(huà)才是真正的實(shí)現(xiàn)了view的移動(dòng),補(bǔ)間動(dòng)畫(huà)對(duì)view的移動(dòng)更像是在不同地方繪制了一個(gè)影子宛徊,實(shí)際的對(duì)象還是處于原來(lái)的地方佛嬉。
當(dāng)我們把動(dòng)畫(huà)的repeatCount設(shè)置為無(wú)限循環(huán)時(shí),如果在Activity退出時(shí)沒(méi)有及時(shí)將動(dòng)畫(huà)停止闸天,屬性動(dòng)畫(huà)會(huì)導(dǎo)致Activity無(wú)法釋放而導(dǎo)致內(nèi)存泄漏暖呕,而補(bǔ)間動(dòng)畫(huà)卻沒(méi)有問(wèn)題。因此苞氮,使用屬性動(dòng)畫(huà)時(shí)切記在Activity執(zhí)行 onStop 方法時(shí)順便將動(dòng)畫(huà)停止湾揽。(對(duì)這個(gè)懷疑的同學(xué)可以自己通過(guò)在動(dòng)畫(huà)的Update 回調(diào)方法打印日志的方式進(jìn)行驗(yàn)證)。
xml 文件實(shí)現(xiàn)的補(bǔ)間動(dòng)畫(huà)笼吟,復(fù)用率極高库物。在Activity切換,窗口彈出時(shí)等情景中有著很好的效果贷帮。
使用幀動(dòng)畫(huà)時(shí)需要注意戚揭,不要使用過(guò)多特別大的圖,容易導(dǎo)致內(nèi)存不足撵枢。
好了民晒,關(guān)于Android 動(dòng)畫(huà)的總結(jié)就到這里。
最后 有興趣的同學(xué)可查看github 源碼歡迎star & fork