前言
??Android系統(tǒng)支持原生動畫,這為應(yīng)用開發(fā)者開發(fā)絢麗的界面提供了極大的方便,有時(shí)候動畫是很必要的,當(dāng)你想做一個滑動的特效的時(shí)候,如果苦思冥想都搞不定,那么你可以考慮下動畫,說不定動畫輕易就搞定了。下面再簡單回顧下Android中的動畫,本文后面會介紹如何用屬性動畫做個稍微復(fù)雜點(diǎn)的動畫。
Android動畫系列:
動畫分類
分類 | 描述 |
---|---|
View動畫 | 也叫漸變動畫,針對View的動畫,主要支持平移、旋轉(zhuǎn)、縮放、透明度 |
Drawable動畫 | 也叫幀動畫,主要是設(shè)置View的背景,可以以動畫的形式為View設(shè)置多張背景 |
對象屬性動畫 | 可以對對象的屬性進(jìn)行動畫而不僅僅是View,動畫默認(rèn)時(shí)間間隔300ms,默認(rèn)幀率10ms/幀。其可以達(dá)到的效果是:在一個時(shí)間間隔內(nèi)完成對象從一個屬性值到另一個屬性值的改變,因此,屬性動畫幾乎是無所不能的,只要對象有這個屬性,它都能實(shí)現(xiàn)動畫效果。 |
屬性動畫
??比較常用的幾個動畫類是:ValueAnimator、ObjectAnimator和AnimatorSet,其中ObjectAnimator繼承自ValueAnimator,AnimatorSet是動畫集,可以定義一組動畫。使用起來也是及其簡單的,下面舉個小栗子。
栗子:實(shí)現(xiàn)的稍微復(fù)雜點(diǎn)的動畫,先上效果圖
布局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" >
<Button
android:id="@+id/menu"
android:layout_gravity="center"
android:layout_width="50dp"
android:layout_height="50dp"
android:text="0" />
<Button
android:id="@+id/item1"
android:layout_gravity="center"
android:layout_width="50dp"
android:layout_height="50dp"
android:text="1"
android:visibility="gone" />
<Button
android:id="@+id/item2"
android:layout_gravity="center"
android:layout_width="50dp"
android:layout_height="50dp"
android:text="2"
android:visibility="gone" />
<Button
android:id="@+id/item3"
android:layout_gravity="center"
android:layout_width="50dp"
android:layout_height="50dp"
android:text="3"
android:visibility="gone" />
<Button
android:id="@+id/item4"
android:layout_gravity="center"
android:layout_width="50dp"
android:layout_height="50dp"
android:text="4"
android:visibility="gone" />
<Button
android:id="@+id/item5"
android:layout_gravity="center"
android:layout_width="50dp"
android:layout_height="50dp"
android:text="5"
android:visibility="gone" />
<Button
android:id="@+id/item6"
android:layout_gravity="center"
android:layout_width="50dp"
android:layout_height="50dp"
android:text="6"
android:visibility="gone" />
<Button
android:id="@+id/item7"
android:layout_gravity="center"
android:layout_width="50dp"
android:layout_height="50dp"
android:text="7"
android:visibility="gone" />
<Button
android:id="@+id/item8"
android:layout_gravity="center"
android:layout_width="50dp"
android:layout_height="50dp"
android:text="8"
android:visibility="gone" />
<Button
android:id="@+id/item9"
android:layout_gravity="center"
android:layout_width="50dp"
android:layout_height="50dp"
android:text="9"
android:visibility="gone" />
<Button
android:id="@+id/item10"
android:layout_gravity="center"
android:layout_width="50dp"
android:layout_height="50dp"
android:text="10"
android:visibility="gone" />
<Button
android:id="@+id/item11"
android:layout_gravity="center"
android:layout_width="50dp"
android:layout_height="50dp"
android:text="11"
android:visibility="gone" />
<Button
android:id="@+id/item12"
android:layout_gravity="center"
android:layout_width="50dp"
android:layout_height="50dp"
android:text="12"
android:visibility="gone" />
</FrameLayout>
代碼如下:
public class AnimationActivity extends Activity implements View.OnClickListener {
private static final String TAG = "AnimationActivity";
private Button mMenuButton;
private Button mItemButton1;
private Button mItemButton2;
private Button mItemButton3;
private Button mItemButton4;
private Button mItemButton5;
private Button mItemButton6;
private Button mItemButton7;
private Button mItemButton8;
private Button mItemButton9;
private Button mItemButton10;
private Button mItemButton11;
private Button mItemButton12;
private boolean mIsMenuOpen = false;
private int menuNum = 12;
private int range = 360; // 顯示角度范圍: 0 - 360
private float centerX = 0;
private float centerY = 0;
private float radius = 400;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.animation_layout);
initView();
}
private void initView() {
mMenuButton = (Button) findViewById(R.id.menu);
mMenuButton.setOnClickListener(this);
mItemButton1 = (Button) findViewById(R.id.item1);
mItemButton1.setOnClickListener(this);
mItemButton2 = (Button) findViewById(R.id.item2);
mItemButton2.setOnClickListener(this);
mItemButton3 = (Button) findViewById(R.id.item3);
mItemButton3.setOnClickListener(this);
mItemButton4 = (Button) findViewById(R.id.item4);
mItemButton4.setOnClickListener(this);
mItemButton5 = (Button) findViewById(R.id.item5);
mItemButton5.setOnClickListener(this);
mItemButton6 = (Button) findViewById(R.id.item6);
mItemButton6.setOnClickListener(this);
mItemButton7 = (Button) findViewById(R.id.item7);
mItemButton7.setOnClickListener(this);
mItemButton8 = (Button) findViewById(R.id.item8);
mItemButton8.setOnClickListener(this);
mItemButton9 = (Button) findViewById(R.id.item9);
mItemButton9.setOnClickListener(this);
mItemButton10 = (Button) findViewById(R.id.item10);
mItemButton10.setOnClickListener(this);
mItemButton11 = (Button) findViewById(R.id.item11);
mItemButton11.setOnClickListener(this);
mItemButton12 = (Button) findViewById(R.id.item12);
mItemButton12.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v == mMenuButton) {
if (!mIsMenuOpen) {
mIsMenuOpen = true;
doAnimateOpen(mItemButton1, 0, menuNum);
doAnimateOpen(mItemButton2, 1, menuNum);
doAnimateOpen(mItemButton3, 2, menuNum);
doAnimateOpen(mItemButton4, 3, menuNum);
doAnimateOpen(mItemButton5, 4, menuNum);
doAnimateOpen(mItemButton6, 5, menuNum);
doAnimateOpen(mItemButton7, 6, menuNum);
doAnimateOpen(mItemButton8, 7, menuNum);
doAnimateOpen(mItemButton9, 8, menuNum);
doAnimateOpen(mItemButton10, 9, menuNum);
doAnimateOpen(mItemButton11, 10, menuNum);
doAnimateOpen(mItemButton12, 11, menuNum);
} else {
mIsMenuOpen = false;
doAnimateClose(mItemButton1, 0, menuNum);
doAnimateClose(mItemButton2, 1, menuNum);
doAnimateClose(mItemButton3, 2, menuNum);
doAnimateClose(mItemButton4, 3, menuNum);
doAnimateClose(mItemButton5, 4, menuNum);
doAnimateClose(mItemButton6, 5, menuNum);
doAnimateClose(mItemButton7, 6, menuNum);
doAnimateClose(mItemButton8, 7, menuNum);
doAnimateClose(mItemButton9, 8, menuNum);
doAnimateClose(mItemButton10, 9, menuNum);
doAnimateClose(mItemButton11, 10, menuNum);
doAnimateClose(mItemButton12, 11, menuNum);
}
} else {
Toast.makeText(this, "你點(diǎn)擊了" + v.toString(), Toast.LENGTH_SHORT).show();
}
}
/**
* 打開菜單的動畫
* @param view 執(zhí)行動畫的view
* @param index view在動畫序列中的順序
* @param total 動畫序列的個數(shù)
*/
private void doAnimateOpen(View view, int index, int total) {
if (view.getVisibility() != View.VISIBLE) {
view.setVisibility(View.VISIBLE);
}
float angle = index * (range/total);
double degree = Math.PI * (angle/180);
int translationX = (int) (radius * Math.cos(degree));
int translationY = (int) (radius * Math.sin(degree));
Log.d(TAG, String.format("degree=%f, translationX=%d, translationY=%d",
degree, translationX, translationY));
AnimatorSet set = new AnimatorSet();
//包含平移、縮放、透明度、旋轉(zhuǎn)和倒轉(zhuǎn)顯示動畫
set.playTogether(
ObjectAnimator.ofFloat(view, "translationX", 0, translationX),
ObjectAnimator.ofFloat(view, "translationY", 0, translationY),
ObjectAnimator.ofFloat(view, "scaleX", 0f, 1f),
ObjectAnimator.ofFloat(view, "scaleY", 0f, 1f),
ObjectAnimator.ofFloat(view, "alpha", 0f, 1),
ObjectAnimator.ofFloat(view, "rotation", 0, 360)
//need sdk 21
// ObjectAnimator.ofArgb((Button)view, "textColor", 0x000, 0xFFFF00),
// ObjectAnimator.ofArgb(view, "backgroundColor", 0x000, 0xFF0000)
);
// add TimeInterpolator
// set.setInterpolator(new LinearInterpolator());
// set.setInterpolator(new AccelerateDecelerateInterpolator());
// set.setInterpolator(new DecelerateInterpolator());
//動畫周期為500ms
set.setDuration(1 * 2000).start();
}
/**
* 關(guān)閉菜單的動畫
* @param view 執(zhí)行動畫的view
* @param index view在動畫序列中的順序
* @param total 動畫序列的個數(shù)
*/
private void doAnimateClose(final View view, int index, int total) {
if (view.getVisibility() != View.VISIBLE) {
view.setVisibility(View.VISIBLE);
}
float angle = index * (range/total);
double degree = Math.PI * (angle/180);// index / ((total - 1) * (180/angle));
int translationX = (int) (radius * Math.cos(degree));
int translationY = (int) (radius * Math.sin(degree));
Log.d(TAG, String.format("degree=%f, translationX=%d, translationY=%d",
degree, translationX, translationY));
AnimatorSet set = new AnimatorSet();
//包含平移、縮放、透明度、旋轉(zhuǎn)和倒轉(zhuǎn)顯示動畫
set.playTogether(
ObjectAnimator.ofFloat(view, "translationX", translationX, 0),
ObjectAnimator.ofFloat(view, "translationY", translationY, 0),
ObjectAnimator.ofFloat(view, "scaleX", 1f, 0f),
ObjectAnimator.ofFloat(view, "scaleY", 1f, 0f),
ObjectAnimator.ofFloat(view, "alpha", 1f, 0f),
ObjectAnimator.ofFloat(view, "rotation", 360, 0)
//need sdk 21
// ObjectAnimator.ofArgb((Button)view, "textColor", 0xFFFF00, 0x000),
// ObjectAnimator.ofArgb(view, "backgroundColor", 0xFF0000, 0x000)
);
//為動畫加上事件監(jiān)聽,當(dāng)動畫結(jié)束的時(shí)候,我們把當(dāng)前view隱藏
set.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
view.setVisibility(View.GONE);
}
@Override
public void onAnimationCancel(Animator animator) {
}
});
// add TimeInterpolator
// set.setInterpolator(new LinearInterpolator());
// set.setInterpolator(new AccelerateDecelerateInterpolator());
// set.setInterpolator(new DecelerateInterpolator());
set.setDuration(1 * 2000).start();
}
}
??運(yùn)行后,就會出現(xiàn)上圖的效果,你可以用動畫屬性做出更為復(fù)雜的動畫效果。說到屬性動畫,就不得不提到插值器(TimeInterpolator)和估值算法(TypeEvaluator),下面介紹。
TimeInterpolator和TypeEvaluator
??TimeInterpolator中文翻譯為時(shí)間插值器,它的作用是根據(jù)時(shí)間流逝的百分比來計(jì)算出當(dāng)前屬性值改變的百分比,系統(tǒng)預(yù)置的有
類型 | 描述 |
---|---|
LinearInterpolator | 線性插值器:勻速動畫 |
AccelerateDecelerateInterpolator | 加速減速插值器:動畫兩頭慢中間快 |
DecelerateInterpolator | 減速插值器:動畫越來越慢 |
??TypeEvaluator的中文翻譯為類型估值算法,它的作用是根據(jù)當(dāng)前屬性改變的百分比來計(jì)算改變后的屬性值,系統(tǒng)預(yù)置的有IntEvaluator(針對整型屬性)、FloatEvaluator(針對浮點(diǎn)型屬性)和ArgbEvaluator(針對Color屬性)。可能這么說還有點(diǎn)晦澀,沒關(guān)系,下面給出一個實(shí)例就很好理解了。
??看上述動畫,很顯然上述動畫是一個勻速動畫,其采用了線性插值器和整型估值算法,在40ms內(nèi),View的x屬性實(shí)現(xiàn)從0到40的變換,由于動畫的默認(rèn)刷新率為10ms/幀,所以該動畫將分5幀進(jìn)行,我們來考慮第三幀(x=20 t=20ms),當(dāng)時(shí)間t=20ms的時(shí)候,時(shí)間流逝的百分比是0.5 (20/40=0.5),意味這現(xiàn)在時(shí)間過了一半,那x應(yīng)該改變多少呢,這個就由插值器和估值算法來確定。拿線性插值器來說,當(dāng)時(shí)間流逝一半的時(shí)候,x的變換也應(yīng)該是一半,即x的改變是0.5,為什么呢?因?yàn)樗蔷€性插值器,是實(shí)現(xiàn)勻速動畫的,下面看它的源碼:
public class LinearInterpolator implements Interpolator {
public LinearInterpolator() {
}
public LinearInterpolator(Context context, AttributeSet attrs) {
}
public float getInterpolation(float input) {
return input;
}
}
??很顯然,線性插值器的返回值和輸入值一樣,因此插值器返回的值是0.5,這意味著x的改變是0.5,這個時(shí)候插值器的工作就完成了。
??具體x變成了什么值,這個需要估值算法來確定,我們來看看整型估值算法的源碼:
public class IntEvaluator implements TypeEvaluator<Integer> {
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}
??上述算法很簡單,evaluate的三個參數(shù)分別表示:估值小數(shù)、開始值和結(jié)束值,對應(yīng)于我們的例子就分別是:0.5,0,40。根據(jù)上述算法,整型估值返回給我們的結(jié)果是20,這就是(x=20 t=20ms)的由來。
??說明:屬性動畫要求該屬性有set方法和get方法(可選);插值器和估值算法除了系統(tǒng)提供的外,我們還可以自定義,實(shí)現(xiàn)方式也很簡單,因?yàn)椴逯灯骱凸乐邓惴ǘ际且粋€接口,且內(nèi)部都只有一個方法,我們只要派生一個類實(shí)現(xiàn)接口就可以了,然后你就可以做出千奇百怪的動畫效果。具體一點(diǎn)就是:自定義插值器需要實(shí)現(xiàn)Interpolator或者TimeInterpolator,自定義估值算法需要實(shí)現(xiàn)TypeEvaluator。還有就是如果你對其他類型(非int、float、color)做動畫,你必須要自定義類型估值算法。
之前做的動畫加上TimeInterpolator的效果如下:
自定義TimeInterpolator
??在實(shí)際開發(fā)中,系統(tǒng)預(yù)置的時(shí)間差值器肯定是不能滿足我們的需求的,這就需要自定義了。這里自定義一個簡單的時(shí)間插值器,使其具有這樣的曲線特征:
基于中心點(diǎn)正負(fù)振幅逐漸趨于0的時(shí)間插值器,數(shù)學(xué)表達(dá)式:
pow(2, -10 * x) * sin((x - factor / 4) * (2 * PI) / factor) + 1
自定義曲線的特征:
上面已經(jīng)說過,自定義非常簡單,只要實(shí)現(xiàn)接口就OK,實(shí)現(xiàn)如下:
public class MyInterpolator implements Interpolator {
private static final float DEFAULT_FACTOR = 0.3f;
// 因子數(shù)值越小振動頻率越高
private float mFactor;
public MyInterpolator() {
this(DEFAULT_FACTOR);
}
public MyInterpolator(float factor) {
mFactor = factor;
}
@Override
public float getInterpolation(float input) {
// pow(2, -10 * input) * sin((input - factor / 4) * (2 * PI) / factor) + 1
return (float) (Math.pow(2, -10 * input) * Math.sin((input - mFactor / 4.0d) * (2.0d * Math.PI) / mFactor) + 1);
}
}
效果:
OK,屬性動畫就學(xué)習(xí)到這里,下一節(jié)進(jìn)行屬性動畫的深入分析
Demo源碼:https://github.com/yufenfenGH/Android-Animation.git
參考: https://blog.csdn.net/singwhatiwanna/article/details/17639987