本篇包括Android屬性動畫的基本使用,理解插值器和估值器,自定義屬性動畫
簡介
屬性動畫是Android3.0及其以上才能使用,但是由于現(xiàn)在開發(fā)的軟件大多最低兼容都是4.0的,所以就不再介紹之前的View動畫了,因為它完全可以被屬性動畫替代,下面進(jìn)入正題。
基本使用
屬性動畫,正如其名,它本質(zhì)就是通過set、get修改某個對象的某個屬性,然后改變UI,來實現(xiàn)動畫。所以屬性動畫的要點就是你要改變的對象必須包含以下兩個要素:
- 具有要修改屬性的set和get方法,例如:setScaleX和getScaleX等;
- 在set方法中包含更新UI的方法,例如:invalidate()等;
如果包含了這兩點,那么該對象的改屬性就能使用屬性動畫了。
下面就來看看,系統(tǒng)給我們提供了哪些自帶的屬性動畫。首先可以知道包含了所有View動畫中有的動畫,包括:平移、旋轉(zhuǎn)、縮放、透明度;在屬性動畫中對應(yīng)著如下的屬性:
- translationX:X軸的平移;
- translationY:Y軸的平移;
- rotation:旋轉(zhuǎn);
- rotationX:圍繞X軸旋轉(zhuǎn);
- rotationY:圍繞Y軸旋轉(zhuǎn);
- scaleX:X方向的縮放;
- scaleY:Y方向的縮放;
- alpha:透明度
屬性動畫除此之外還有其他的一些屬性動畫,例如背景顏色等,這里就不一一列舉了;接下來就舉兩個例子:
(1)向下平移自己高度的距離,代碼如下:
ObjectAnimator.ofFloat(vSquare, "translationY", vSquare.getHeight()).start();
直接來看,第一個參數(shù)Object,表示要改變的對象;第二個參數(shù)String,表示要改變的屬性,當(dāng)然這個就是Y軸的平移,第三個參數(shù)是float ...,接收的是一個變化的數(shù)組,這里只有一個參數(shù);
總之,這句話的含義就是表示從當(dāng)前位置向下平移自己高度的距離;
如果第三個參數(shù)不止傳一個值,而是傳多個,效果如何呢,先上代碼:
ObjectAnimator translationY = ObjectAnimator.ofFloat(vSquare,"translationY", 0, vSquare.getHeight(),
vSquare.getHeight() / 2,vSquare.getHeight() * 2);
translationY.setDuration(3000);
translationY.setInterpolator(new LinearInterpolator());
translationY.start();
這個動畫就是從當(dāng)前位置勻速的向下移動自身高度的距離,再向上移動自身高度的一半的距離,再向下移動兩倍自己高度的距離;其中有個細(xì)節(jié),第一個參數(shù)是0;
沒錯,這其實是這樣的,當(dāng)?shù)谌齻€參數(shù)只有一個時,則會反射調(diào)用該對象的該屬性的get方法,作為動畫的起點,再執(zhí)行;當(dāng)?shù)谌齻€參數(shù)有多個時,就會把第一個參數(shù)作為動畫的起點;
(2)改變背景顏色以及改變Y軸位置并一起循環(huán)播放
首先,我們分析一下,這里邊包含了什么,屬性包括:backgroundColor和translationY,還有就是循環(huán)播放;如果有View動畫的基礎(chǔ)的盆友,估計知道可以用AnimatorSet來統(tǒng)一管理,但是其實也可以直接創(chuàng)建兩個動畫一起執(zhí)行,效果差不多(注意是差不多,不是一樣);其實也挺簡單,直接上代碼:
if (bgAndTransYSet == null) {
ObjectAnimator colorAnim = ObjectAnimator.ofInt(vChangeBg, "backgroundColor", getResources().getColor(R.color.colorAccent),
getResources().getColor(R.color.colorPrimary));
colorAnim.setDuration(2000);
colorAnim.setEvaluator(new ArgbEvaluator());//顏色變化推薦使用這個插值器
colorAnim.setRepeatCount(ValueAnimator.INFINITE);
colorAnim.setRepeatMode(ValueAnimator.REVERSE);
ObjectAnimator translationY = ObjectAnimator.ofFloat(vChangeBg, "translationY", vSquare.getHeight());
translationY.setRepeatCount(ValueAnimator.INFINITE);
translationY.setRepeatMode(ValueAnimator.REVERSE);
bgAndTransYSet = new AnimatorSet();
bgAndTransYSet.addListener(new AnimListener());
bgAndTransYSet.playTogether(colorAnim, translationY);
}
if (!bgAndTransYSet.isStarted()) {
bgAndTransYSet.start();
}
public class AnimListener implements Animator.AnimatorListener{
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
}
里邊可以看到給屬性動畫設(shè)置了估值器,目的是計算當(dāng)前階段應(yīng)該返回什么值與插值器和設(shè)置的時間有關(guān),越到最后就越接近最終的值;
然后還設(shè)置了重復(fù)的模式和次數(shù),重復(fù)次數(shù)這個值為-1,就表示無限,默認(rèn)是0,就是不重復(fù),重復(fù)的模式有兩種,這里的這種表示,從開始到結(jié)束,然后結(jié)束到開始,再開始到結(jié)束,這樣依次循環(huán);還有一種是ValueAnimator.RESTART,表示從開始到結(jié)束,再從開始到結(jié)束,依次循環(huán);
然后就是使用到了AnimatorSet,然后就是一切播放,接收的參數(shù)是一個動畫的數(shù)組,這里又有一個細(xì)節(jié),如果AnimatorSet設(shè)置了duration,那么它包含的子動畫的duration屬性都會和AnimatorSet的duration的值一樣;AnimatorSet還有其他的一些播放方式,例如:
- playSequentially(Animator... items),表示動畫依次播放;
- play(Animator anim).with(Animator anim1),表示兩個動畫一起播放;
- play(Animator anim).before(Animator anim1),表示在anim在anim1之前播放;
- play(Animator anim).after(Animator anim1),表示在anim在anim1之后播放;
注意:這里同一個動畫集不要重復(fù)start
總之播放順序完全可以按照自己的想法來設(shè)置,非常的好用。
最后就是動畫的監(jiān)聽,方法的意思都很明顯,有開始、結(jié)束、取消以及重復(fù)的監(jiān)聽,這樣我們就能在動畫的不同階段做不同的事,除此之外還有一個重要的方法,是屬于ValueAnimator的,監(jiān)聽屬性改變的回調(diào):
public static interface AnimatorUpdateListener {
void onAnimationUpdate(ValueAnimator animation);
}
還有一些其他的動畫就不逐一展示了,都是大同小異的。
理解插值器和估值器
插值器
其作用是根據(jù)時間的流逝的百分比來計算出當(dāng)前屬性值改變的百分比,上文中,用到了LinearInterpolator(線性插值器:表示勻速),除此之外系統(tǒng)還提供了一些其他的插值器,例如:AccelerateDecelerateInterpolator(加速減速插值器:兩頭慢中間快)、DecelerateInterpolator(減速插值器:越來越慢)等等。下面就分析LinearInterpolator的源碼:
package android.view.animation;
import android.content.Context;
import android.util.AttributeSet;
import com.android.internal.view.animation.HasNativeInterpolator;
import com.android.internal.view.animation.NativeInterpolatorFactory;
import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
/**
* An interpolator where the rate of change is constant
*/
@HasNativeInterpolator
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
public LinearInterpolator() {
}
public LinearInterpolator(Context context, AttributeSet attrs) {
}
public float getInterpolation(float input) {
return input;
}
/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactoryHelper.createLinearInterpolator();
}
}
其中可以看到最重要的就是getInterpolation()方法,線性插值器,可以看到它表示時間百分比變化是多少,當(dāng)前的屬性百分比就變化多少,即勻速動畫。再看看AccelerateDecelerateInterpolator的源碼:
package android.view.animation;
import android.content.Context;
import android.util.AttributeSet;
import com.android.internal.view.animation.HasNativeInterpolator;
import com.android.internal.view.animation.NativeInterpolatorFactory;
import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
/**
* An interpolator where the rate of change starts and ends slowly but
* accelerates through the middle.
*/
@HasNativeInterpolator
public class AccelerateDecelerateInterpolator extends BaseInterpolator
implements NativeInterpolatorFactory {
public AccelerateDecelerateInterpolator() {
}
@SuppressWarnings({"UnusedDeclaration"})
public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {
}
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator();
}
}
可以看到getInterpolation()方法,其中用到的余弦函數(shù),其中input的值是[0,1],范圍就是[cos(PI)/2+0.5,cos(2PI)/2+0.5],而cos(PI)cos(2PI)是一個先加速再減速的圖像,這里沒有圖,相信你還沒有把這點數(shù)學(xué)還給老師。cos(PI)cos(2PI)的取值是[-1,1],所以getInterpolation()的返回值也是[0,1]。
估值器
目的在于根據(jù)當(dāng)前屬性改變的百分比來計算改變后的屬性值。上文中用到了ArgbEvaluator(針對color屬性),除此之外系統(tǒng)還提供了:IntEvaluator(針對整型屬性)、FloatEvaluator(針對浮點數(shù)屬性)等等。也來看看IntEvaluator的源碼理解一下:
package android.animation;
/**
* This evaluator can be used to perform type interpolation between <code>int</code> values.
*/
public class IntEvaluator implements TypeEvaluator<Integer> {
/**
* This function returns the result of linearly interpolating the start and end values, with
* <code>fraction</code> representing the proportion between the start and end values. The
* calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,
* where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
* and <code>t</code> is <code>fraction</code>.
*
* @param fraction The fraction from the starting to the ending values
* @param startValue The start value; should be of type <code>int</code> or
* <code>Integer</code>
* @param endValue The end value; should be of type <code>int</code> or <code>Integer</code>
* @return A linear interpolation between the start and end values, given the
* <code>fraction</code> parameter.
*/
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}
這個也是很直接,就是一個方法,獲取改變后的值;
第一個參數(shù)fraction就是插值器里邊的getInterpolator返回的值;表示一個變化的百分比;
第二個參數(shù)startValue,表示變化的起始值;
第三個參數(shù)endValue,表示變化的終值;
其中也就是fraction是個變化的值,取值范圍是[0,1]起始就是一個一次函數(shù)。
到這里其實可能已經(jīng)意識到了,插值器與估值器一起使用就能打造出變化多端的動畫,因為這些屬性值的變化是變化多端的,這時候是否懷念當(dāng)年的三角函數(shù)呢?
自定義屬性動畫
屬性動畫之所以好,是因為它能對每個對象都能改變其屬性,這就讓他有了很強的擴展性,這里就以用動畫改變一個View的寬度為例,我們知道View是沒有提供setWidth和getWidth方法的,所以系統(tǒng)不支持width的屬性動畫,而我們大展身手的機會就來了。理論上我們有三種方式去實現(xiàn)這個功能。
- 改變View的源碼,增加View的這個屬性set和get方法(可惜沒權(quán)限);
- 使用一個類去包裝原始對象,間接提供set和get方法;
- 使用ValueAnimator,監(jiān)聽動畫過程,自己去改變屬性;
顯然第二種方式擴展性更強,改動也最小,直接上代碼:
package net.arvin.androidart.anim;
import android.view.View;
/**
* created by arvin on 17/2/19 21:10
* email:1035407623@qq.com
*/
public abstract class BaseViewWrapper {
protected View mTarget;
public BaseViewWrapper(View mTarget) {
this.mTarget = mTarget;
}
}
package net.arvin.androidart.anim;
import android.view.View;
/**
* created by arvin on 17/2/19 21:11
* email:1035407623@qq.com
*/
public class ViewWidthWrapper extends BaseViewWrapper {
public ViewWidthWrapper(View mTarget) {
super(mTarget);
}
public void setWidth(int width) {
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();
}
public int getWidth() {
return mTarget.getWidth();
}
}
widthWrapper = new ViewWidthWrapper(vWidthChange);
if (widthAnim == null) {
widthAnim = ObjectAnimator.ofInt(widthWrapper, "width", ScreenUtil.dp2px(80));
widthAnim.setRepeatCount(ValueAnimator.INFINITE);
widthAnim.setRepeatMode(ValueAnimator.REVERSE);
}
if (!widthAnim.isStarted()) {
widthAnim.start();
}
代碼很簡單,我也簡單的封裝了一層,方便使用,這個動畫就是循環(huán)改變View的寬度,從當(dāng)前寬度到兩倍自身寬度,再從兩倍寬度變到自身寬度,循環(huán)往返。
好了到這里相信對屬性動畫也有了一定的了解,等以后再進(jìn)一步分析屬性動畫的實現(xiàn)原理,現(xiàn)在就到這里啦!下面奉上源碼。