本篇包括Android屬性動(dòng)畫的基本使用,理解插值器和估值器走搁,自定義屬性動(dòng)畫
簡(jiǎn)介
屬性動(dòng)畫是Android3.0及其以上才能使用,但是由于現(xiàn)在開發(fā)的軟件大多最低兼容都是4.0的两残,所以就不再介紹之前的View動(dòng)畫了劣纲,因?yàn)樗耆梢员粚傩詣?dòng)畫替代,下面進(jìn)入正題筑悴。
基本使用
屬性動(dòng)畫棋返,正如其名,它本質(zhì)就是通過set雷猪、get修改某個(gè)對(duì)象的某個(gè)屬性睛竣,然后改變UI,來實(shí)現(xiàn)動(dòng)畫求摇。所以屬性動(dòng)畫的要點(diǎn)就是你要改變的對(duì)象必須包含以下兩個(gè)要素:
- 具有要修改屬性的set和get方法射沟,例如:setScaleX和getScaleX等;
- 在set方法中包含更新UI的方法与境,例如:invalidate()等验夯;
如果包含了這兩點(diǎn),那么該對(duì)象的改屬性就能使用屬性動(dòng)畫了摔刁。
下面就來看看挥转,系統(tǒng)給我們提供了哪些自帶的屬性動(dòng)畫。首先可以知道包含了所有View動(dòng)畫中有的動(dòng)畫共屈,包括:平移绑谣、旋轉(zhuǎn)、縮放拗引、透明度借宵;在屬性動(dòng)畫中對(duì)應(yīng)著如下的屬性:
- translationX:X軸的平移;
- translationY:Y軸的平移矾削;
- rotation:旋轉(zhuǎn)壤玫;
- rotationX:圍繞X軸旋轉(zhuǎn);
- rotationY:圍繞Y軸旋轉(zhuǎn)哼凯;
- scaleX:X方向的縮放欲间;
- scaleY:Y方向的縮放;
- alpha:透明度
屬性動(dòng)畫除此之外還有其他的一些屬性動(dòng)畫断部,例如背景顏色等猎贴,這里就不一一列舉了;接下來就舉兩個(gè)例子:
(1)向下平移自己高度的距離,代碼如下:
ObjectAnimator.ofFloat(vSquare, "translationY", vSquare.getHeight()).start();
直接來看嘱能,第一個(gè)參數(shù)Object吝梅,表示要改變的對(duì)象;第二個(gè)參數(shù)String惹骂,表示要改變的屬性苏携,當(dāng)然這個(gè)就是Y軸的平移,第三個(gè)參數(shù)是float ...对粪,接收的是一個(gè)變化的數(shù)組右冻,這里只有一個(gè)參數(shù);
總之著拭,這句話的含義就是表示從當(dāng)前位置向下平移自己高度的距離纱扭;
如果第三個(gè)參數(shù)不止傳一個(gè)值,而是傳多個(gè)儡遮,效果如何呢乳蛾,先上代碼:
ObjectAnimator translationY = ObjectAnimator.ofFloat(vSquare,"translationY", 0, vSquare.getHeight(),
vSquare.getHeight() / 2,vSquare.getHeight() * 2);
translationY.setDuration(3000);
translationY.setInterpolator(new LinearInterpolator());
translationY.start();
這個(gè)動(dòng)畫就是從當(dāng)前位置勻速的向下移動(dòng)自身高度的距離,再向上移動(dòng)自身高度的一半的距離鄙币,再向下移動(dòng)兩倍自己高度的距離肃叶;其中有個(gè)細(xì)節(jié),第一個(gè)參數(shù)是0十嘿;
沒錯(cuò)因惭,這其實(shí)是這樣的,當(dāng)?shù)谌齻€(gè)參數(shù)只有一個(gè)時(shí)绩衷,則會(huì)反射調(diào)用該對(duì)象的該屬性的get方法蹦魔,作為動(dòng)畫的起點(diǎn),再執(zhí)行咳燕;當(dāng)?shù)谌齻€(gè)參數(shù)有多個(gè)時(shí)勿决,就會(huì)把第一個(gè)參數(shù)作為動(dòng)畫的起點(diǎn);
(2)改變背景顏色以及改變Y軸位置并一起循環(huán)播放
首先迟郎,我們分析一下剥险,這里邊包含了什么,屬性包括:backgroundColor和translationY宪肖,還有就是循環(huán)播放;如果有View動(dòng)畫的基礎(chǔ)的盆友健爬,估計(jì)知道可以用AnimatorSet來統(tǒng)一管理控乾,但是其實(shí)也可以直接創(chuàng)建兩個(gè)動(dòng)畫一起執(zhí)行,效果差不多(注意是差不多娜遵,不是一樣)蜕衡;其實(shí)也挺簡(jiǎn)單,直接上代碼:
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());//顏色變化推薦使用這個(gè)插值器
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) {
}
}
里邊可以看到給屬性動(dòng)畫設(shè)置了估值器设拟,目的是計(jì)算當(dāng)前階段應(yīng)該返回什么值與插值器和設(shè)置的時(shí)間有關(guān)慨仿,越到最后就越接近最終的值久脯;
然后還設(shè)置了重復(fù)的模式和次數(shù),重復(fù)次數(shù)這個(gè)值為-1镰吆,就表示無限帘撰,默認(rèn)是0,就是不重復(fù)万皿,重復(fù)的模式有兩種摧找,這里的這種表示,從開始到結(jié)束牢硅,然后結(jié)束到開始蹬耘,再開始到結(jié)束,這樣依次循環(huán)减余;還有一種是ValueAnimator.RESTART综苔,表示從開始到結(jié)束,再?gòu)拈_始到結(jié)束位岔,依次循環(huán)休里;
然后就是使用到了AnimatorSet,然后就是一切播放赃承,接收的參數(shù)是一個(gè)動(dòng)畫的數(shù)組妙黍,這里又有一個(gè)細(xì)節(jié),如果AnimatorSet設(shè)置了duration瞧剖,那么它包含的子動(dòng)畫的duration屬性都會(huì)和AnimatorSet的duration的值一樣拭嫁;AnimatorSet還有其他的一些播放方式,例如:
- playSequentially(Animator... items)抓于,表示動(dòng)畫依次播放;
- play(Animator anim).with(Animator anim1)做粤,表示兩個(gè)動(dòng)畫一起播放;
- play(Animator anim).before(Animator anim1)捉撮,表示在anim在anim1之前播放怕品;
- play(Animator anim).after(Animator anim1),表示在anim在anim1之后播放巾遭;
注意:這里同一個(gè)動(dòng)畫集不要重復(fù)start
總之播放順序完全可以按照自己的想法來設(shè)置肉康,非常的好用。
最后就是動(dòng)畫的監(jiān)聽灼舍,方法的意思都很明顯吼和,有開始、結(jié)束骑素、取消以及重復(fù)的監(jiān)聽炫乓,這樣我們就能在動(dòng)畫的不同階段做不同的事,除此之外還有一個(gè)重要的方法,是屬于ValueAnimator的末捣,監(jiān)聽屬性改變的回調(diào):
public static interface AnimatorUpdateListener {
void onAnimationUpdate(ValueAnimator animation);
}
還有一些其他的動(dòng)畫就不逐一展示了侠姑,都是大同小異的。
理解插值器和估值器
插值器
其作用是根據(jù)時(shí)間的流逝的百分比來計(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()方法圃酵,線性插值器柳畔,可以看到它表示時(shí)間百分比變化是多少,當(dāng)前的屬性百分比就變化多少郭赐,即勻速動(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)是一個(gè)先加速再減速的圖像,這里沒有圖观谦,相信你還沒有把這點(diǎn)數(shù)學(xué)還給老師拉盾。cos(PI)cos(2PI)的取值是[-1,1]豁状,所以getInterpolation()的返回值也是[0捉偏,1]。
估值器
目的在于根據(jù)當(dāng)前屬性改變的百分比來計(jì)算改變后的屬性值泻红。上文中用到了ArgbEvaluator(針對(duì)color屬性)夭禽,除此之外系統(tǒng)還提供了:IntEvaluator(針對(duì)整型屬性)、FloatEvaluator(針對(duì)浮點(diǎn)數(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));
}
}
這個(gè)也是很直接讹躯,就是一個(gè)方法,獲取改變后的值缠劝;
第一個(gè)參數(shù)fraction就是插值器里邊的getInterpolator返回的值潮梯;表示一個(gè)變化的百分比;
第二個(gè)參數(shù)startValue剩彬,表示變化的起始值酷麦;
第三個(gè)參數(shù)endValue,表示變化的終值喉恋;
其中也就是fraction是個(gè)變化的值,取值范圍是[0,1]起始就是一個(gè)一次函數(shù)。
到這里其實(shí)可能已經(jīng)意識(shí)到了轻黑,插值器與估值器一起使用就能打造出變化多端的動(dòng)畫糊肤,因?yàn)檫@些屬性值的變化是變化多端的,這時(shí)候是否懷念當(dāng)年的三角函數(shù)呢氓鄙?
自定義屬性動(dòng)畫
屬性動(dòng)畫之所以好馆揉,是因?yàn)樗軐?duì)每個(gè)對(duì)象都能改變其屬性,這就讓他有了很強(qiáng)的擴(kuò)展性抖拦,這里就以用動(dòng)畫改變一個(gè)View的寬度為例升酣,我們知道View是沒有提供setWidth和getWidth方法的,所以系統(tǒng)不支持width的屬性動(dòng)畫态罪,而我們大展身手的機(jī)會(huì)就來了噩茄。理論上我們有三種方式去實(shí)現(xiàn)這個(gè)功能。
- 改變View的源碼复颈,增加View的這個(gè)屬性set和get方法(可惜沒權(quán)限)绩聘;
- 使用一個(gè)類去包裝原始對(duì)象,間接提供set和get方法耗啦;
- 使用ValueAnimator凿菩,監(jiān)聽動(dòng)畫過程,自己去改變屬性帜讲;
顯然第二種方式擴(kuò)展性更強(qiáng)衅谷,改動(dòng)也最小,直接上代碼:
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();
}
代碼很簡(jiǎn)單似将,我也簡(jiǎn)單的封裝了一層获黔,方便使用,這個(gè)動(dòng)畫就是循環(huán)改變View的寬度玩郊,從當(dāng)前寬度到兩倍自身寬度肢执,再?gòu)膬杀秾挾茸兊阶陨韺挾确浚h(huán)往返既峡。
好了到這里相信對(duì)屬性動(dòng)畫也有了一定的了解,等以后再進(jìn)一步分析屬性動(dòng)畫的實(shí)現(xiàn)原理项鬼,現(xiàn)在就到這里啦侦厚!下面奉上源碼耻陕。