Android動畫的發展歷程:
3.0之前 | 3.0 | 5.0 |
---|---|---|
View動畫 | 增加屬性動畫,低版本不兼容 | 增加轉場動畫,低版不本兼容 |
動畫在UI開發中算比較重要的一塊,合理正確地使用動畫可以讓你的產品體驗更出色。這篇文章主要是總結Android中的動畫類型和基本使用方式。
Demo源碼: https://github.com/jdqm/AnimationDemo
I.View動畫
View動畫從大方面來看,可以分為兩類,一類是補間(Tween)動畫,即我們只需要提供初始狀態和終止狀態,動畫框架自動計算出中間的狀態;另一類幀動畫,每一幀圖片都需要提供,使用簡單,但相對來說耗費的資源就比較多。
1.TranslationAnimation
構造方法一
/**
* @param fromXDelta 動畫開始時的X坐標
* @param toXDelta 動畫停止時的X坐標
* @param fromYDelta 動畫開始時的y坐標
* @param toYDelta 動畫停止時的y坐標
*
* Note:這里的坐標指定的左上角的坐標
*/
public TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta)
1.1舉例:將一個圖片向右平移100dp,動畫時長為300ms,視圖停止在動畫結束的狀態
Animation ta = new TranslateAnimation(0, dpToPixel(100), 0, 0);
ta.setDuration(300);//動畫時長
ta.setFillAfter(true); //視圖停在動畫結束的狀態
imageView.startAnimation(ta);
構造方法二
/**
* @param fromXType 指定fromXType的解釋方式,可以是 Animation.ABSOLUTE,Animation.RELATIVE_TO_SELF(以自己作為參考點), 或者Animation.RELATIVE_TO_PARENT(以父View作為參考的).
* @param fromXValue 動畫開始的x值, 如果fromXType是ABSOLUTE那就指定一個具體的數字,否則就是一個百分比
*
* Note:其他的參數也是類似的理解方式
*/
public TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue,
int fromYType, float fromYValue, int toYType, float toYValue)
1.2舉例:實現1.1例子一樣的效果,即向右平移100dp
ta = new TranslateAnimation(
Animation.ABSOLUTE, 0,
Animation.ABSOLUTE, dpToPixel(100),
Animation.ABSOLUTE, 0,
Animation.ABSOLUTE, 0);
1.3舉例:使用Animation.RELATIVE_TO_PARENT實現從父布局左邊移動到父布局的水平中間
ta = new TranslateAnimation(
Animation.RELATIVE_TO_PARENT, 0.0f,
Animation.RELATIVE_TO_PARENT, 0.5f,
Animation.ABSOLUTE, 0,
Animation.ABSOLUTE, 0);
2.縮放動畫-ScaleAnimation
Animation sa = new ScaleAnimation(1.0f, 1.5f, 1.0f, 1.5f);
sa.setDuration(300);
sa.setFillAfter(true);
imageView.startAnimation(sa);
3.透明動畫-AlphaAnimation
Animation sa = new AlphaAnimation(0.0f, 1.0f);
sa.setDuration(1000);
sa.setFillAfter(true);
imageView.startAnimation(sa);
4.旋轉動畫-RotationAnimation
Animation sa = new RotateAnimation(0, 180);
sa.setDuration(300);
sa.setFillAfter(true);
imageView.startAnimation(sa);
5.幀動畫-FrameAnimation
此文件放于 drawable下:music_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/music_play01" android:duration="150"/>
<item android:drawable="@drawable/music_play02" android:duration="150"/>
<item android:drawable="@drawable/music_play03" android:duration="150"/>
<item android:drawable="@drawable/music_play04" android:duration="150"/>
</animation-list>
public class FrameAnimationActivity extends AppCompatActivity {
private View frameAnimationView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_twenn_animation);
frameAnimationView = findViewById(R.id.frameAnimationView);
frameAnimationView.setBackgroundResource(R.drawable.music_animation);
}
public void start(View view) {
AnimationDrawable frameAnimation = (AnimationDrawable) frameAnimationView.getBackground();
frameAnimation.start();
}
public void stop(View view) {
AnimationDrawable frameAnimation = (AnimationDrawable) frameAnimationView.getBackground();
frameAnimation.stop();
}
}
View動畫除了在Java代碼中使用外,還可以在xml中進行定義,放在 res/anim
目錄下。
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:fillAfter="true"
android:fillBefore="true"
android:repeatMode="restart"
android:shareInterpolator="true">
<translate
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="100"
android:toYDelta="0" />
<alpha
android:fromAlpha="0"
android:toAlpha="1.0" />
<scale
android:fromXScale="0"
android:fromYScale="0"
android:pivotX="0.5"
android:pivotY="0.5"
android:toXScale="1.0"
android:toYScale="1.0" />
<rotate
android:fromDegrees="0"
android:pivotX="0.5"
android:pivotY="0.5"
android:toDegrees="360" />
</set>
在Java代碼中使用AnimationUtils
來加載這個動畫資源。這種寫法似乎可讀性更好一些,當然這個過程涉及到一些XML的解析,性能上可能會有一些影響,但影響不大。
Animation animation = AnimationUtils.loadAnimation(context, R.anim.animations);
II.屬性動畫
1.TranslatorAnimator
switch (state) {
case 0:
imageView.animate().translationX(dpToPixel(100))
.setDuration(300)
.setInterpolator(new AccelerateDecelerateInterpolator())
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
Toast.makeText(context.getApplicationContext(), "平移動畫結束了", Toast.LENGTH_SHORT).show();
}
});
//如果View默認的Animator沒有你想要改變的屬性,可以用下面這種寫法
//ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "translationX", dpToPixel(100));
//animator.start();
break;
case 1:
imageView.animate().translationX(0);
break;
case 2:
imageView.animate().translationYBy(dpToPixel(100));
break;
case 3:
imageView.animate().translationYBy(-dpToPixel(100));
break;
case 4:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
imageView.animate().translationZ(dpToPixel(15));
}
break;
case 5:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
imageView.animate().translationZ(0);
}
break;
default:
break;
}
2.ScaleAnimator
switch (state) {
case 0:
imageView.animate().scaleX(1.5f)
.setDuration(300)
.setInterpolator(new AccelerateDecelerateInterpolator());
break;
case 1:
imageView.animate().scaleX(1.0f);
break;
case 2:
imageView.animate().scaleY(.5f);
break;
case 3:
imageView.animate().scaleY(1.0f);
break;
default:
break;
}
3.AlphaAnimator
if (state == 0) {
imageView.animate().alpha(0);
state = 1;
} else {
imageView.animate().alpha(1);
state = 0;
}
4.RotationAnimator
switch (state) {
case 0:
imageView.animate().rotation(180);
break;
case 1:
imageView.animate().rotation(0);
break;
case 2:
imageView.animate().rotationX(180);
break;
case 3:
imageView.animate().rotationX(0);
break;
case 4:
imageView.animate().rotationY(180);
break;
case 5:
imageView.animate().rotationY(0);
break;
default:
break;
}
5.PropertyValueHolder
PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("translationX", dpToPixel(100));
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("alpha", 1.0f);
PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("rotation", 360);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(imageView, holder1, holder2, holder3);
animator.setDuration(500)
.start();
6.AnimatorSet
if (state == 0) {
ObjectAnimator animator1 = ObjectAnimator.ofFloat(imageView, "translationX", dpToPixel(100));
ObjectAnimator animator2 = ObjectAnimator.ofFloat(imageView, "rotation", 360);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(imageView, "alpha", 0);
AnimatorSet animatorSet = new AnimatorSet();
//animatorSet.playTogether(animator1, animator2, animator3);
animatorSet.playSequentially(animator1, animator2, animator3);
animatorSet.start();
state = 1;
} else {
ObjectAnimator animator1 = ObjectAnimator.ofFloat(imageView, "translationX", 0);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(imageView, "rotation", 0);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(imageView, "alpha", 1);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(animator1, animator2, animator3);
animatorSet.start();
state = 0;
}
III.轉場動畫
在Android5.0以前,要個Activity的切換添加一些動畫,可以這么干。
startActivity(new Intent(this, TransitionActivity.class));
overridePendingTransition(R.anim.activity_enter_anim, R.anim.activity_exit_anim);
activity_enter_anim.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500">
<translate
android:fromXDelta="100%"
android:toXDelta="0" />
</set>
activity_exit_anim.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500">
<translate
android:fromXDelta="0"
android:toXDelta="-100%" />
</set>
但是這種動畫只是對整個Activity生效,并不能針對Activity布局里的具體View。Android5.0增加的轉場動畫,可以有效處理這個問題。
1.source Activity
1.1 Activity java code
public void explode(View view) {
Intent intent = new Intent(this, TransitionsActivity.class);
intent.putExtra("flag", 0);
if (SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle());
}
}
public void slide(View view) {
Intent intent = new Intent(this, TransitionsActivity.class);
intent.putExtra("flag", 1);
if (SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle());
}
}
public void fade(View view) {
Intent intent = new Intent(this, TransitionsActivity.class);
intent.putExtra("flag", 2);
if (SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle());
}
}
public void share(View view) {
View fab = findViewById(R.id.btnFab);
Intent intent = new Intent(this, TransitionsActivity.class);
intent.putExtra("flag", 3);
if (SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this,
Pair.create(view, "share"),
Pair.create(fab, "fab")).toBundle());
}
}
1.2 activity layout
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.jdqm.animationdemo.transition.TransitionActivity">
<Button
android:id="@+id/btnExplode"
android:layout_width="match_parent"
android:layout_height="100dp"
android:onClick="explode"
android:text="explode" />
<Button
android:id="@+id/btnSlide"
android:layout_width="match_parent"
android:layout_height="100dp"
android:onClick="slide"
android:text="slide"
app:layout_constraintTop_toBottomOf="@id/btnExplode" />
<Button
android:id="@+id/btnFade"
android:layout_width="match_parent"
android:layout_height="100dp"
android:onClick="fade"
android:text="fade"
app:layout_constraintTop_toBottomOf="@id/btnSlide" />
<Button
android:id="@+id/btnShare"
android:layout_width="match_parent"
android:layout_height="100dp"
android:onClick="share"
android:text="share"
android:transitionName="share"
app:layout_constraintTop_toBottomOf="@id/btnFade" />
<Button
android:id="@+id/btnFab"
android:layout_width="56dp"
android:layout_height="56dp"
android:background="@drawable/ripple_round"
android:elevation="5dp"
android:transitionName="fab"
app:layout_constraintTop_toBottomOf="@id/btnShare" />
</android.support.constraint.ConstraintLayout>
1.3 ripple_round.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/holo_green_dark"/>
<corners android:radius="28dp"/>
</shape>
2.target Activity
2.1 Activity Java code
public class TransitionsActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
int flag = getIntent().getExtras().getInt("flag");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
switch (flag) {
case 0:
getWindow().setEnterTransition(new Explode());
break;
case 1:
getWindow().setEnterTransition(new Slide());
break;
case 2:
getWindow().setEnterTransition(new Fade());
getWindow().setExitTransition(new Fade());
break;
case 3:
break;
default:
break;
}
}
setContentView(R.layout.activity_transitions);
}
}
2.2 activity_layout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.jdqm.animationdemo.transition.TransitionsActivity">
<View
android:id="@+id/holderView"
android:layout_width="match_parent"
android:layout_height="300dp"
android:background="@color/colorAccent"
android:transitionName="share" />
<Button
android:id="@+id/btnFab"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_alignParentEnd="true"
android:layout_below="@id/holderView"
android:layout_marginEnd="10dp"
android:layout_marginTop="-26dp"
android:background="@drawable/ripple_round"
android:elevation="5dp"
android:transitionName="fab" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/holderView"
android:layout_marginTop="10dp">
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_below="@+id/button4" />
<Button
android:id="@+id/button4"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_alignParentStart="true"
android:layout_marginTop="10dp" />
</RelativeLayout>
</RelativeLayout>
IV.總結
動畫的實現原理,其實就是通過不斷的改變視圖的位置并進行重新繪制,難的是如何計算出下一幀的位置、大小等信息,而動畫框架則是幫我們完成了這些個雜的計算過程,詳情可了解一下動畫的插值器Interpolator。
對于以上所提到View動畫、屬性動畫以及轉出動畫,在給出的Demo中都有完整的示例。需要注意的是,View動畫只是對View的影像做了變換,但其實際事件響應坐標等還是在原來的位置,而屬性動畫則解決了這個問題。