Android動畫

HFW發表於2019-03-27

前言

在Android開發中,UI的變化非常常見,如果不使用動畫來進行過度,那麼使用者體驗就會不怎麼好。我將動畫分為大三類View動畫、幀動畫、屬性動畫三種,下面先從View動畫開始

一、View動畫

View動畫包括以下幾種

  • ScaleAnimation(縮放動畫)
  • TranslateAnimation(平移動畫)
  • AlphaAnimation(透明度動畫)
  • RotateAnimation(旋轉動畫)

並且還提供了一個AnimationSet類來把多個View動畫進行組合顯示,我們可以直接在程式碼中建立這些Animation物件也可以在xml中進行指定

縮放動畫

  • 程式碼中定義
ScaleAnimation sAnimation = new ScaleAnimation(0, 1, 0,
                1, Animation.RELATIVE_TO_SELF, 0.5f, 
                Animation.RELATIVE_TO_SELF, 0.5f);
animation.setDuration(2000);
imageView.startAnimation(sAnimation);
複製程式碼
  • xml中定義
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromXScale="0" android:toXScale="1" android:fromYScale="0"
    android:toYScale="1" android:pivotX="50%" android:pivotY="50%" android:duration="2000">
</scale>
複製程式碼

平移動畫

  • 程式碼中定義
TranslateAnimation tAnimation = new TranslateAnimation(0, 100, 0, 100);
tAnimation.setDuration(2000);
imageView.startAnimation(tAnimation);
複製程式碼
  • xml中定義
<translate xmlns:android="http://schemas.android.com/apk/res/android" android:duration="2000" android:fromXDelta="0" 
    android:toXDelta="200" android:fromYDelta="0" android:toYDelta="200">
</translate>
imageView.startAnimation(AnimationUtils.loadAnimation(this, R.anim.translate));
複製程式碼

透明度動畫

  • 程式碼中定義
AlphaAnimation aAnimation = new AlphaAnimation(0, 1);
aAnimation.setDuration(2000);
imageView.startAnimation(aAnimation);
複製程式碼
  • xml中定義
<alpha xmlns:android="http://schemas.android.com/apk/res/android" android:duration="2000"
    android:fromAlpha="0" android:toAlpha="1">
</alpha>
複製程式碼

旋轉動畫

  • 程式碼中定義
RotateAnimation rAnimation = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f,
                Animation.RELATIVE_TO_SELF, 0.5f);
rAnimation.setDuration(2000);
imageView.startAnimation(rAnimation);
複製程式碼
  • xml中定義
<rotate xmlns:android="http://schemas.android.com/apk/res/android" android:duration="2000" 
    android:fromDegrees="0" android:toDegrees="360">
</rotate>
複製程式碼

組合動畫

  • 程式碼中定義
AnimationSet set = new AnimationSet(true);
set.addAnimation(sAnimation);
set.addAnimation(aAnimation);
set.addAnimation(tAnimation);
set.addAnimation(rAnimation);
// 所有子動畫都會被設定成該超時時間
set.setDuration(5000);
imageView.startAnimation(set);
複製程式碼
  • xml中定義
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha 
        android:duration="2000"
        android:fromAlpha="0" 
        android:toAlpha="1">
    </alpha>
    <rotate 
        android:duration="2000"
        android:fromDegrees="0" 
        android:toDegrees="360">
    </rotate>
    <scale
        android:fromXScale="0" 
        android:toXScale="1" 
        android:fromYScale="0"
        android:toYScale="1"
        android:pivotX="50%" 
        android:pivotY="50%"
        android:duration="2000">
    </scale>
    <translate 
        android:duration="2000" 
        android:fromXDelta="0"
        android:toXDelta="200" 
        android:fromYDelta="0" 
        android:toYDelta="200">
    </translate>
</set>
複製程式碼

以上程式碼有幾個需要注意

  • 在xml中定義的動畫可以通過AnimationUtils.loadAnimation(context, resId))拿到對應的Animation物件
  • 如果想讓一個動畫延時執行可以設定startOffset
  • 如果想讓一個動畫迴圈執行可以設定repeatCount
  • 動畫執行完後預設會返回到原始位置如果不需要返回那麼需要將setFillAfter設定為true,但是雖然執行完後保留在了執行完後的位置但是該位置並不能響應點選事件,原位置能夠響應點選事件
  • 上述動畫都沒有設定插值器,採用預設的插值器AccelerateDecelerateInterpolator,如果想要動畫按照不停的速率執行,那麼可以設定以下幾種插值器
插值器類 對應xml資源id 變化方式
AccelerateDecelerateInterpolator @android:anim/accelerate_decelerate_interpolator 先加速後減速
AccelerateInterpolator @android:anim/accelerate_interpolator 一直加速
AnticipateInterpolator @android:anim/anticipate_interpolator 先往反方向運動一段距離再前進
AnticipateOvershootInterpolator @android:anim/anticipate_overshoot_interpolator 先往反方向運動再前進並且超過目標點,最後回到目標點
BounceInterpolator @android:anim/bounce_interpolator 類似球落地時的反彈效果
CycleInterpolator @android:anim/cycle_interpolatorr 迴圈,先往正向運動到終點,然後向反方向運動知道運動到-終點處再回來
DecelerateInterpolator @android:anim/decelerate_interpolator 一直減速
LinearInterpolator @android:anim/linear_interpolator 勻速運動
OvershootInterpolator @android:anim/overshoot_interpolator 移動超過目標點,然後再回來

LayoutAnimation

如果一個ViewGroup被設定了該屬性那麼在第一次顯示該ViewGroup時會執行所設定的動畫,用法如下。

首先定義一個AnimationSet

<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha
        android:duration="2000"
        android:fromAlpha="0"
        android:toAlpha="1">
    </alpha>
    <translate
        android:duration="2000"
        android:fromYDelta="100%p"
        android:toYDelta="0">
    </translate>
</set>
複製程式碼

然後再res/anim裡面建立一個根節點為layoutAnimation檔案,引用上述的檔案,其中的delay表示對應ViewGroup中的每個View都要延遲動畫duration * delay的時間,現在動畫持續時間是2000ms,那麼第一個item就會在600ms的時候執行動畫,第二個會在1200ms的時候執行

<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
    android:animationOrder="normal"
    android:animation="@anim/set"
    android:delay="0.3">
</layoutAnimation>
複製程式碼

最後給目標ViewGroup設定LayoutAnimation

<android.support.v7.widget.RecyclerView
    android:id="@+id/rv"
    android:background="@color/colorPrimary"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layoutAnimation="@anim/layout_animation"
    android:orientation="vertical">

</android.support.v7.widget.RecyclerView>
複製程式碼

執行效果如下圖所示,當然有點醜,需要的時候可以慢慢調整

Android動畫

二、幀動畫

幀動畫就是將一組圖片按照特定的順序進行播放, 使用方式如下所示

<animation-list xmlns:android="http://schemas.android.com/apk/res/android" 
    android:oneshot="true">
    <item android:drawable="@drawable/s1" android:duration="1000"/>
    <item android:drawable="@drawable/s2" android:duration="1000"/>
    <item android:drawable="@drawable/s3" android:duration="1000"/>
    <item android:drawable="@drawable/s4" android:duration="1000"/>
    <item android:drawable="@drawable/s5" android:duration="1000"/>
    <item android:drawable="@drawable/s6" android:duration="5000"/>
    <item android:drawable="@drawable/s7" android:duration="1000"/>
</animation-list>

imageView.setImageResource(R.drawable.list);
mDrawable = (AnimationDrawable)imageView.getDrawable();
imageView.post(new Runnable() {
    @Override
    public void run() {
        mDrawable.start();
    }
});
複製程式碼

這裡有幾個注意點

  • oneshot表示是否只播放一次,該值預設是false表示迴圈播放
  • 幀動畫在View三大流程執行完畢前,只會顯示第一幀所以要在三大流程執行完成後再開啟

三、屬性動畫

前面講到的View動畫其實並不會真正改變View的屬性比如位置等等,而屬性動畫會真正的改變View的屬性,其主要類包括以下幾個,其中ObjectAnimator繼承於ValueAnimator,先來看看ViewPropertyAnimator

  • ViewPropertyAnimator
  • ValueAnimator
  • ObjectAnimator
  • AnimatorSet

ViewPropertyAnimator

這個類內部其實是通過ValueAnimator實現的,我們可以通過view.animator()得到該類的物件

// translation從當前值慢慢的變為100
imageView.animate().translationX(100);
// translation從當前值慢慢的變為原來值+100
imageView.animate().translationXBy(100);
// 反轉180度
imageView.animate().rotation(180);
複製程式碼

注意我們不需要手動呼叫start方法其就會自動執行,預設持續時間是300ms

ValueAnimator

我們可以通過ofInt、ofFloat、ofArgb等獲取到ValueAnimator例項,下面以通過屬性動畫動態改變圖片的高度為例

在程式碼中定義

ValueAnimator vAnimator = ValueAnimator.ofInt(400, 0);
vAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        ViewGroup.LayoutParams params = imageView.getLayoutParams();
        params.height = (int) animation.getAnimatedValue();
        imageView.setLayoutParams(params);
    }
});
vAnimator.setDuration(3000);
vAnimator.start();
複製程式碼

在xml中定義

<animator xmlns:android="http://schemas.android.com/apk/res/android" android:valueFrom="400dp" 
android:valueType="intType" android:valueTo="0dp" android:duration="3000">
</animator>
ValueAnimator vAnimator =
                (ValueAnimator) AnimatorInflater.loadAnimator(this, R.animator.animator);
複製程式碼

ObjectAnimator

我們可以通過ofInt、ofFloat、ofArgb等獲取到ObjectAnimator例項,一般會操作以下幾個屬性

  • translationX、translationY 平移
  • rotation、rotationX、rotationY 旋轉
  • privotX、privotY 控制旋轉縮放的支點位置
  • alpha 透明度
  • x 、y 、z位置

下面以通過屬性動畫動態改變圖片的透明度為例

在程式碼中定義

ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha", 0, 1);
animator.setDuration(3000);
animator.start();
複製程式碼

在xml中定義

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:valueType="floatType" android:duration="3000" android:propertyName="alpha"
    android:valueFrom="0" android:valueTo="1">
</objectAnimator>
ObjectAnimator animator = (ObjectAnimator)
                AnimatorInflater.loadAnimator(this, R.animator.object_animator);
animator.setTarget(imageView);
animator.start();
複製程式碼

幾個注意點:

  • 當ofXXX屬性名引數後面只有一個引數時那個參數列示的是目標值,系統會呼叫getter獲取初始化值,然後不停的呼叫setter設定,當超過一個引數時第一個就變為初始值最後一個變成目標值,中間的都是轉接點這個時候可以不提供setter方法
  • 當一個類的某個屬性沒有提供setter方法時我們需要對其運用屬性動畫,我們可以建一個包裝類
  • 如果發現使用了屬性動畫改變自定義View的屬性發現介面並沒有發生變化,請檢查下你的setter方法有沒有觸發重繪

AnimatorSet

AnimatorSet類似AnimationSet,作用是把多個屬性動畫組合起來執行

在程式碼中定義

ValueAnimator vAnimator =
                (ValueAnimator) AnimatorInflater.loadAnimator(this, R.animator.animator);
vAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        ViewGroup.LayoutParams params = imageView.getLayoutParams();
        params.height = (int) animation.getAnimatedValue();
        imageView.setLayoutParams(params);
    }
});
vAnimator.setDuration(3000);
vAnimator.start();
ObjectAnimator oAnimator = (ObjectAnimator)
        AnimatorInflater.loadAnimator(this, R.animator.object_animator);
oAnimator.setTarget(imageView);
oAnimator.start();
AnimatorSet set = new AnimatorSet();
// 同時執行
set.playTogether(vAnimator, oAnimator);
// 順序執行
set.playSequentially(vAnimator, oAnimator);
複製程式碼

在xml中定義 其中ordering表示同時執行還是順序執行

<set xmlns:android="http://schemas.android.com/apk/res/android" android:ordering="together">
    <animator android:valueFrom="400px"
        android:valueTo="0px" android:duration="3000" android:valueType="intType">
    </animator>
    <objectAnimator
        android:valueType="floatType" android:duration="3000" android:propertyName="alpha"
        android:valueFrom="0" android:valueTo="1">
    </objectAnimator>
</set>
複製程式碼

PropertyValuesHolder

使用PropertyValuesHolder也能實現多動畫同時執行

PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("alpha", 0, 1);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofInt("rotation", 0, 360);
ValueAnimator animator = ObjectAnimator.ofPropertyValuesHolder(holder1, holder2);
animator.setDuration(3000);
animator.setTarget(imageView);
animator.start();
複製程式碼

Tips: 將ViewGroup的animateLayoutChanges屬性設定為true,在新增View或者移除View的時候會有動畫

相關文章