當(dāng)前Android應(yīng)用開發(fā)涉及的動(dòng)畫主要有三種腿短,分別是:視圖動(dòng)畫炒瘸,逐幀動(dòng)畫膘滨,屬性動(dòng)畫。
逐幀動(dòng)畫
是在 xml 中定義好一系列圖片之后生真,使用AnimationDrawable來順序播放的動(dòng)畫沉噩。
位置:/res/drawable/frame_animation.xml
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/item" android:duration="100"/>
<item android:drawable="@drawable/item" android:duration="100"/>
<item android:drawable="@drawable/item" android:duration="100"/>
<item android:drawable="@drawable/item" android:duration="100"/>
</animation-list>
使用逐幀動(dòng)畫時(shí),避免圖片較多或圖片較大柱蟀,會(huì)引起OOM川蒙。
ImageView imageView = findViewById(R.id.image);
imageView .setBackgroundResource(R.drawable.rocket_thrust);
AnimationDrawable rocketAnimation =
(AnimationDrawable)imageView.getBackground();
rocketAnimation.start();//不可在onCreate中執(zhí)行,要等view首次繪制完畢长已。
視圖動(dòng)畫
針對(duì)View的影像進(jìn)行縮放畜眨、透明度漸變、旋轉(zhuǎn)术瓮、平移或組合使用康聂,從而產(chǎn)生動(dòng)畫的效果。
Java類名 | xml關(guān)鍵字 | 描述 |
---|---|---|
AlphaAnimation | < alpha> | 漸進(jìn)透明度動(dòng)畫效果 |
RotateAnimation | < rotate> | 旋轉(zhuǎn)動(dòng)畫效果 |
ScaleAnimation | < scale> | 漸進(jìn)尺寸伸縮動(dòng)畫效果 |
TranslateAnimation | < translate> | 平移動(dòng)畫效果 |
AnimationSet | < set> | 組合其他動(dòng)畫元素或set元素的容器 |
位置:res/anim/view_anim.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="3000"
android:fillAfter="true"
android:shareInterpolator="true">
<rotate
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="360" />
<alpha
android:duration="200"
android:fillAfter="true"
android:fromAlpha="0"
android:interpolator="@android:anim/linear_interpolator"
android:repeatCount="-1"
android:repeatMode="reverse"
android:toAlpha="1" />
</set>
- duration:動(dòng)畫持續(xù)時(shí)間胞四;
- fillAfter:動(dòng)畫結(jié)束時(shí)恬汁,是否保持最后一幀;
- repeatCount:動(dòng)畫循環(huán)的次數(shù)辜伟,默認(rèn)0次不循環(huán)氓侧,-1為無線循環(huán);
- repeatMode:動(dòng)畫循環(huán)模式导狡,reverse指從動(dòng)畫結(jié)束處循環(huán)约巷,restart指從動(dòng)畫開始處循環(huán)。
- interpolator:插值器旱捧,控制動(dòng)畫執(zhí)行的速度独郎。
- shareInterpolator:是否與set容器中其他動(dòng)畫元素共享插值器踩麦,false為各自使用自己的插值器。
- fromDegrees:旋轉(zhuǎn)動(dòng)畫起始的角度氓癌,單位度谓谦,浮點(diǎn)值。
- pivotX:旋轉(zhuǎn)中心的X坐標(biāo)顽铸,n%表示相對(duì)于自身左邊緣的 自身寬度的n%茁计。
Animation animation= AnimationUtils.loadAnimation(this, R.anim.view_anim);
button.startAnimation(animation);
插值器
Interpolator是Animation類的一個(gè)xml屬性,規(guī)定了從初始值過渡到結(jié)束值得漸變規(guī)律谓松,視圖動(dòng)畫中alpha、scale践剂、rotate鬼譬、translate、set動(dòng)畫元素都會(huì)繼承此屬性逊脯。
如下是系統(tǒng)內(nèi)置的插值器實(shí)現(xiàn):
插值器類名 | Resource ID | 描述 |
---|---|---|
AccelerateDecelerateInterpolator | @android:anim/accelerate_decelerate_interpolator | 系統(tǒng)默認(rèn)插值器优质。在動(dòng)畫開始與結(jié)束時(shí)速度比較慢,在中間的時(shí)候加速军洼。 |
AccelerateInterpolator | @android:anim/accelerate_interpolator | 在動(dòng)畫開始的地方速度比較慢巩螃,然后開始加速。 |
AnticipateInterpolator | @android:anim/anticipate_interpolator | 開始的時(shí)候向后然后向前甩匕争。 |
AnticipateOvershootInterpolator | @android:anim/anticipate_overshoot_interpolator | 開始的時(shí)候向后然后向前甩一定值后返回最后的值避乏。 |
BounceInterpolator | @android:anim/bounce_interpolator | 動(dòng)畫結(jié)束的時(shí)候彈起。 |
CycleInterpolator | @android:anim/cycle_interpolator | 動(dòng)畫循環(huán)播放特定的次數(shù)甘桑,速度沿著正弦曲線拍皮。 |
DecelerateInterpolator | @android:anim/decelerate_interpolator | 在動(dòng)畫開始的地方快然后慢。 |
LinearInterpolator | @android:anim/linear_interpolator | 常量速度跑杭。 |
OvershootInterpolator | @android:anim/overshoot_interpolator | 向前甩一定值后再回到原來位置铆帽。 |
PathInterpolator | @android:anim/path_interpolator | 新增,按照定義坐標(biāo)路徑動(dòng)畫德谅。 |
屬性動(dòng)畫
利用插值器和估值器爹橱,來計(jì)算出各個(gè)時(shí)刻 View 的屬性,然后通過改變 View 的屬性來實(shí)現(xiàn) View 的動(dòng)畫效果窄做。
- 時(shí)間插值器(Interpolator):作用是根據(jù)時(shí)間的流逝的百分比來計(jì)算屬性改變的百分比愧驱。
- 類型估值器(TypeEvaluator):根據(jù)當(dāng)前屬性改變的百分比來計(jì)算改變后的屬性值。
屬性動(dòng)畫中的TimeInterpolator插值器兼容Interpolator接口浸策,故視圖插值器可在屬性動(dòng)畫中使用冯键。
系統(tǒng)內(nèi)置的插值器上文已有介紹,系統(tǒng)內(nèi)置的估值器有: ArgbEvaluator庸汗、FloatArrayEvaluator惫确、FloatEvaluator、IntArrayEvaluator、IntEvaluator改化、PointFEvaluator掩蛤、RectEvaluator。
如顏色估值器 ArgbEvaluator:
public class ArgbEvaluator implements TypeEvaluator {
private static final ArgbEvaluator sInstance = new ArgbEvaluator();
public static ArgbEvaluator getInstance() {
return sInstance;
}
public Object evaluate(float fraction, Object startValue, Object endValue) {
int startInt = (Integer) startValue;
int startA = (startInt >> 24) & 0xff;
int startR = (startInt >> 16) & 0xff;
int startG = (startInt >> 8) & 0xff;
int startB = startInt & 0xff;
int endInt = (Integer) endValue;
int endA = (endInt >> 24) & 0xff;
int endR = (endInt >> 16) & 0xff;
int endG = (endInt >> 8) & 0xff;
int endB = endInt & 0xff;
return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
(int)((startR + (int)(fraction * (endR - startR))) << 16) |
(int)((startG + (int)(fraction * (endG - startG))) << 8) |
(int)((startB + (int)(fraction * (endB - startB))));
}
}
ArgbEvaluator和顏色有關(guān)系的陈肛,evaluate函數(shù)里面分別對(duì)startValue和endValue做了拆分揍鸟。int總共32位,8位8位的去拆分句旱,分別對(duì)應(yīng)A,R,G,B阳藻。 然后對(duì)A,R,G,B每個(gè)都做fraction轉(zhuǎn)換,在組合到一起形成一個(gè)int值谈撒。
ValueAnimator:通過從初始值到結(jié)束值得平滑過渡實(shí)現(xiàn)動(dòng)畫效果腥泥。addUpdateListener可監(jiān)聽動(dòng)畫執(zhí)行過程。
示例1:點(diǎn)擊紅球落下并彈起啃匿。
private final float RADIUS = 70F;
private float circleY = RADIUS;
public void startValueAnimation(){
circleY = RADIUS;
ValueAnimator anim = ValueAnimator.ofFloat(RADIUS, getHeight()/2-RADIUS);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
circ= (float) animation.getAnimatedValue();
invalidate();
}
});
anim.setDuration(5000);
anim.setInterpolator(new BounceInterpolator());
anim.start();
}
onDraw()方法里重繪:
canvas.drawLine(0f,getHeight()/2,getWidth(),getHeight()/2,paint);//繪制中間橫線
canvas.drawCircle(getWidth()/2,circleY,RADIUS,paint);//繪制圓餅
ValueAnimator類針對(duì)起始值和結(jié)束值進(jìn)行動(dòng)畫操作蛔外,可借助動(dòng)畫進(jìn)度監(jiān)聽回調(diào),處理一些頁面刷新動(dòng)作溯乒。
ObjectAnimator:ObjectAnimator繼承ValueAnimator夹厌,可以對(duì)任意對(duì)象的屬性方法進(jìn)行操作,達(dá)到動(dòng)畫效果裆悄。
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f);
animator.setDuration(2000);
animator.start();
第一個(gè)參數(shù)表示動(dòng)畫目標(biāo)對(duì)象矛纹,第二個(gè)參數(shù)表示對(duì)象屬性名,后面的參數(shù)不固定灯帮,表示動(dòng)畫的初始和結(jié)束值崖技。
組合動(dòng)畫:實(shí)際開發(fā)需求中,一般需要用到組合動(dòng)畫而不是單一動(dòng)畫钟哥。屬性動(dòng)畫的組合借助AnimatotSet類實(shí)現(xiàn)迎献。
- play(Animator anim):執(zhí)行現(xiàn)有動(dòng)畫。
- after(Animator anim):將現(xiàn)有動(dòng)畫排在傳入的動(dòng)畫之后執(zhí)行腻贰。
- after(Animator anim):將現(xiàn)有動(dòng)畫延遲指定毫秒后執(zhí)行吁恍。
- before(Animator anim):將現(xiàn)有動(dòng)畫排在傳入的動(dòng)畫之前執(zhí)行。
- with(Animator anim):將現(xiàn)有動(dòng)畫與傳入的動(dòng)畫并行執(zhí)行播演。
示例 2:先執(zhí)行縮放動(dòng)畫冀瓦,再并行執(zhí)行旋轉(zhuǎn)、平移写烤、透明度動(dòng)畫翼闽。
ObjectAnimator scaleX = ObjectAnimator.ofFloat(textview, "scaleX", 1f, 0.5f, 1f);
ObjectAnimator alpha = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0.5f, 1f);
ObjectAnimator rotate = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);
ObjectAnimator translationX = ObjectAnimator.ofFloat(textview, "translationX",
textview.getTranslationX(), textview.getTranslationX()-150f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(rotate).with(translationX).with(alpha).after(scaleX);
animSet.setDuration(3000);
//監(jiān)聽動(dòng)畫狀態(tài)
animSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
//動(dòng)畫結(jié)束
}
});
//監(jiān)聽動(dòng)畫進(jìn)度
animSet.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
invalidate();
}
});
animSet.start();
translationX是View左上角相對(duì)父容器左上角在X軸的偏移量,translationX默認(rèn)值為0。
ViewPropertyAnimator
Android動(dòng)畫團(tuán)隊(duì)專門為View的常用屬性方法封裝的動(dòng)畫操作類,可以鏈?zhǔn)綄?shí)現(xiàn)基本動(dòng)畫效果。
//將textview從原有顏色在5秒的時(shí)間內(nèi)漸進(jìn)到透明芽隆,同時(shí)縮小0.5倍询微。
textview.aniate().alpha(0f).scaleX(0.5f).setDruation(5000);
鏈?zhǔn)秸{(diào)用并行運(yùn)行多種動(dòng)畫崖瞭,但不支持更復(fù)雜的動(dòng)畫組合(如串式運(yùn)行動(dòng)畫),適用于單個(gè)view的默認(rèn)屬性值的動(dòng)畫撑毛。
自定義估值器
屬性動(dòng)畫的自定義估值器需要實(shí)現(xiàn)TypeEvaluator接口书聚,并復(fù)寫evaluate()方法。
- 自定義路徑估值器 PathAnimEvaluator
/**
* 路徑估值器
*/
public class PathAnimEvaluator implements TypeEvaluator<PointModel> {
PointModel pointModel = null;
/**
* 動(dòng)畫執(zhí)行算法
*
* @param fraction 表示動(dòng)畫完成度藻雌,屬性改變的百分比(可以大于1雌续,可以小于0)
* @param startValue 動(dòng)畫初始值
* @param endValue 動(dòng)畫結(jié)束值
* @return 當(dāng)前動(dòng)畫過渡值
*/
@Override
public PointModel evaluate(float fraction, PointModel startValue, PointModel endValue) {
//當(dāng)前進(jìn)度的x坐標(biāo) = 初始值+動(dòng)畫完成百分比*(結(jié)束值-初始值)。
float x = startValue.getX() + fraction * (endValue.getX() - startValue.getX());
float y = startValue.getY() + fraction * (endValue.getY() - startValue.getY());
pointModel.setX(x);
pointModel.setY(y);
return pointModel;//避免頻繁new實(shí)例造成內(nèi)存溢出蹦疑。
}
}
從上述示例可知西雀,對(duì)于任何實(shí)例對(duì)象,只要賦給他屬性方法歉摧,并提供初始值和結(jié)束值,都可以實(shí)現(xiàn)從初始值到結(jié)束值的平滑過渡腔呜。
- 帶動(dòng)畫的控件 AnimationView:從起點(diǎn)到終點(diǎn)叁温,圓餅滑動(dòng)。
public class AnimationView extends View {
private final float RADIUS = 100f;
//屬性名
private PointModel pointModel;
private Paint paint;
public AnimationView(Context context) {
super(context);
init(context, null, 0);
}
public AnimationView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
public AnimationView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
paint = new Paint();
paint.setColor(Color.RED);
}
public PointModel getPointModel() {
return pointModel;
}
public void setPointModel(PointModel pointModel) {
this.pointModel = pointModel;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (pointModel == null) {
pointModel = new PointModel(RADIUS, RADIUS);
startAnimation();
}
canvas.drawCircle(pointModel.getX(), pointModel.getY(), RADIUS, paint);
}
private void startAnimation() {
//PointModel只是一個(gè)帶x,y坐標(biāo)的實(shí)體類
ObjectAnimator translate = ObjectAnimator.ofObject(this,
"pointModel",
new PathAnimEvaluator(),
new PointModel(RADIUS, RADIUS),
new PointModel(RADIUS, getHeight() - RADIUS));
translate.setDuration(10000);
translate.setInterpolator(new BounceInterpolator());
translate.start();
}
}
動(dòng)畫在執(zhí)行過程中會(huì)多次反射調(diào)用setPointModel()方法并賦新值核畴,在這里需要添加觸發(fā)UI刷新的操作膝但,確保動(dòng)畫有效。
如果動(dòng)畫沒有初始值谤草,那么就會(huì)使用get方法提供的初始值跟束,若還沒有則會(huì)使用該類型的系統(tǒng)默認(rèn)值或者報(bào)錯(cuò)。
自定義插值器
屬性動(dòng)畫自定義插值器需實(shí)現(xiàn)TimeInterpolator接口丑孩,并復(fù)寫getInterpolation()方法冀宴。
從上文自定義估值器可知,fraction表示動(dòng)畫完成的百分比温学,這是系統(tǒng)調(diào)用插值器的getInterpolation()方法得出的略贮。
- 自定義速率插值器 PathAnimInterpolator
public class PathAnimInterpolator implements TimeInterpolator {
@Override
public float getInterpolation(float input) {
//實(shí)現(xiàn)先減速后加速的效果
float result;
if (input <= 0.5) {
result = (float) (Math.sin(Math.PI * input)) / 2;
} else {
result = (float) (2 - Math.sin(Math.PI * input)) / 2;
}
return result;
}
}
如上在AnimationView中替換即可。
插值器和估值器的關(guān)系
從動(dòng)畫繪制的流程角度來說:
- 插值器Interpolator會(huì)根據(jù)時(shí)間流逝的百分比計(jì)算出當(dāng)前屬性值改變的百分比仗岖,即為TypeEvaluator中的fraction逃延;
- 然后估值器TypeEvaluator會(huì)根據(jù)屬性值改變的百分比fraction并結(jié)合初始值和結(jié)束值,計(jì)算出動(dòng)畫當(dāng)前進(jìn)度的屬性值轧拄;
- 然后估值器反射調(diào)用屬性set方法更新屬性值揽祥,并刷新UI觸發(fā)onDraw重繪,就實(shí)現(xiàn)了動(dòng)畫的效果檩电。
注意事項(xiàng)
- 在activity銷毀的時(shí)候拄丰,一定確保動(dòng)畫關(guān)閉府树,資源回收,避免內(nèi)存泄露愈案。
- 視圖動(dòng)畫不能改變view的屬性挺尾,只是對(duì)其影像做動(dòng)畫,需要view.clearAnimation()后方可正常操作其屬性站绪。
- 逐幀動(dòng)畫避免圖片過大遭铺、過多,容易造成內(nèi)存泄露恢准。
- 開啟硬件加速魂挂,可以提升動(dòng)畫的流暢性。
- 動(dòng)畫操作里馁筐,盡量用dp涂召,而不是px,處理好屏幕適配問題敏沉。
引用
Android屬性動(dòng)畫完全解析(上)果正,初識(shí)屬性動(dòng)畫的基本用法
自定義控件三部曲之動(dòng)畫篇(七)——ObjectAnimator基本使用
Android屬性動(dòng)畫ObjectAnimator源碼簡單分析