android動畫的使用(下-高級用法)

前言

在上篇介紹了android動畫的一些基本用法,如果仔細看過的話,要對付一般的動畫效果,想必是沒有太大問題的,但是對於某些特殊需求,單純用最基本的用法是沒法實現的,很明顯,這些都是需要我們自己去定義的東西。

今天我來講講android動畫的高級用法。下面我將從以下幾方面來講這個高級用法:

LayoutAnimation

先來講講這個LayoutAnimation(佈局動畫),算是對上篇View動畫的補充。

佈局動畫xml用法

我們先看看如何用佈局來實現。

首先看看主佈局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="ShowListView"
            android:text="顯示列表"/>

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="HideListView"
            android:text="隱藏列表"/>
    </LinearLayout>

    <ListView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:visibility="gone"
        android:layoutAnimation="@anim/layout_animation"
        android:id="@+id/testListView">
    </ListView>

</LinearLayout>

定義了兩個按鈕和一個列表視圖,按鈕來控制列表的顯示和隱藏。

然後是佈局動畫的定義以及動畫的定義

<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
    android:delay="0.5"
    android:animation="@anim/test_animation"
    android:animationOrder="normal">

</layoutAnimation>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="2000">

    <alpha android:fromAlpha="0"
           android:toAlpha="1">
    </alpha>

    <rotate android:fromDegrees="0"
            android:toDegrees="360">
    </rotate>

</set>

代碼非常簡潔,就一個旋轉和漸變。

最後是代碼的調用:

public class MainActivity extends AppCompatActivity {

    private ListView mListView;
    private ArrayAdapter<String> mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mListView = (ListView) findViewById(R.id.testListView);

        String[] list = {"1", "2", "3", "4", "5", "6", "7"};
        mAdapter = new ArrayAdapter<String>(this, android.R.layout
                .simple_list_item_1, list);
        mListView.setAdapter(mAdapter);
    }

    public void ShowListView(View v) {
        mListView.setVisibility(View.VISIBLE);
    }

    public void HideListView(View v) {
        mListView.setVisibility(View.INVISIBLE);
    }
}

我們來看看效果:
這裏寫圖片描述

從上面的gif圖可以看出,第一次顯示的時候是執行動畫的,但是再次顯示的時候就沒有動畫效果了,也就是說,佈局動畫只有在你設置佈局動畫的控件第一次顯示的時候有效果,在之後就沒有效果了,如果要動畫效果,就只能按照上篇介紹的自己設置動畫效果了。

佈局動畫代碼用法

代碼用法其實就是使用LayoutAnimationController,我們直接看代碼吧:

Animation animation = AnimationUtils.loadAnimation(this,R.anim.test_animation);
        LayoutAnimationController controller = new LayoutAnimationController(animation);
        controller.setDelay(0.5f);
        controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
        mListView.setLayoutAnimation(controller);

將佈局中的android:layoutAnimation=”@anim/layout_animation”取消,並在代碼中添加上述代碼,就可以通過代碼來實現佈局動畫了,效果和上面一樣,就不發圖了。

自定義佈局動畫的播放順序

之前我們使用了LayoutAnimation,並且使用了setOrder來控制播放的順序,系統默認提供的順序只有三種:

public class LayoutAnimationController {
    /**
     * Distributes the animation delays in the order in which view were added
     * to their view group.
     */
    public static final int ORDER_NORMAL  = 0;

    /**
     * Distributes the animation delays in the reverse order in which view were
     * added to their view group.
     */
    public static final int ORDER_REVERSE = 1;

    /**
     * Randomly distributes the animation delays.
     */
    public static final int ORDER_RANDOM  = 2;
    ...
 }

分別是順序、反序以及隨機,有時候這三種方式不一定能滿足我們的需求,此時,我們就需要自己實現動畫的播放順序,那麼,如何實現呢,查看LayoutAnimationController的源碼可以發現有這麼兩個方法

  1. getDelayForView(View view):返回當前view動畫播放的延遲時間
  2. getTransformedIndex(AnimationParameters params):返回動畫的播放順序

我們來看下這兩個方法。先看第二個方法,該方法返回動畫播放的順序,當順序時,該順序爲0、1、2、3… n;當反序時,該順序爲n、n - 1… 0;當隨機時,爲0~n中的隨機數;下面來看看第一個方法,第一個方法纔是真正計算動畫的延遲時間的,它會調用第二個方法,並根據其返回的播放順序來計算延遲時間,可看如下源碼:

final float delay = mDelay * mAnimation.getDuration();
final long viewDelay = (long) (getTransformedIndex(params) * delay);

這個動畫的播放順序影響着延遲時間,而延遲時間就是決定動畫執行先後的根源,因此,我們只需要重寫getTransformedIndex這個方法即可改變動畫執行的順序,當然你也可以重寫getDelayForView方法,自己來定義動畫的延遲時間。下面我們來看看如何實現。

首先自定義一個LayoutAnimationController類

public class CustomLayoutAnimationController extends LayoutAnimationController {

    public final static int CUSTOM_ORDER = 7;
    private CurrentIndexListener mListener;


    public CustomLayoutAnimationController(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomLayoutAnimationController(Animation animation) {
        super(animation);
    }

    public CustomLayoutAnimationController(Animation animation, float delay) {
        super(animation, delay);
    }

    public void setCurrentIndexListener(CurrentIndexListener listener){
        this.mListener = listener;
    }

    @Override
    protected int getTransformedIndex(AnimationParameters params) {
        if (mListener != null && getOrder() == CUSTOM_ORDER) {
            return mListener.currentIndex(this, params.count, params.index);
        } else {
            return super.getTransformedIndex(params);
        }
    }

    public interface CurrentIndexListener {
        int currentIndex(CustomLayoutAnimationController controller, int count, int index);
    }
}

自定義一個CUSTOM_ORDER參數,重寫getTransformedIndex方法並流出接口來自定義播放順序,下面我們來看看如何自定義播放順序,直接上完整代碼了

public class TestCustomLayoutAnimationController extends AppCompatActivity implements
        CustomLayoutAnimationController.CurrentIndexListener {

    private GridLayout gridLayout;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_layout_animation_controller);

        gridLayout = (GridLayout) findViewById(R.id.gridLayout);


        ScaleAnimation animation = new ScaleAnimation(1, 0, 1, 0, Animation.RELATIVE_TO_SELF,
                0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        animation.setDuration(500);
        animation.setFillAfter(true);

        CustomLayoutAnimationController customLayoutAnimationController = new
                CustomLayoutAnimationController(animation);
        customLayoutAnimationController.setOrder(CustomLayoutAnimationController.CUSTOM_ORDER);
        customLayoutAnimationController.setDelay(0.2f);
        customLayoutAnimationController.setCurrentIndexListener(this);
        gridLayout.setLayoutAnimation(customLayoutAnimationController);
    }

    @Override
    public int currentIndex(CustomLayoutAnimationController controller, int count, int index) {
        int result = 1;
        switch (index % 4) {
            case 0:
                result = 3;
                break;
            case 1:
                result = 4;
                break;
            case 2:
                result = 5;
                break;
            case 3:
                result = 6;
                break;
        }
        return  result;
    }
}

新建自定義類的實例對象,然後實現CurrentIndexListener接口,並且使用自定義參數CUSTOM_ORDER作爲播放順序參數。這裏我設置的播放順序是從左往右每兩個view進行動畫變化,只做參考用,大家可修改爲自己想要的。

下面來看看佈局:

<?xml version="1.0" encoding="utf-8"?>
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:columnCount="4"
            android:rowCount="2"
            android:id="@+id/gridLayout"
            android:orientation="horizontal">

    <ImageView
        android:layout_width="96dp"
        android:layout_height="96dp"
        android:background="@android:color/holo_blue_light"/>

    <ImageView
        android:layout_width="96dp"
        android:layout_height="96dp"
        android:background="@android:color/holo_orange_light"/>

    <ImageView
        android:layout_width="96dp"
        android:layout_height="96dp"
        android:background="@android:color/holo_green_light"/>

    <ImageView
        android:layout_width="96dp"
        android:layout_height="96dp"
        android:background="@android:color/holo_red_light"/>

    <ImageView
        android:layout_width="96dp"
        android:layout_height="96dp"
        android:background="@android:color/holo_red_light"/>

    <ImageView
        android:layout_width="96dp"
        android:layout_height="96dp"
        android:background="@android:color/holo_green_light"/>

    <ImageView
        android:layout_width="96dp"
        android:layout_height="96dp"
        android:background="@android:color/holo_orange_light"/>

    <ImageView
        android:layout_width="96dp"
        android:layout_height="96dp"
        android:background="@android:color/holo_blue_light"/>

</GridLayout>

佈局沒啥好說的,直接看效果吧:
這裏寫圖片描述
下面我來介紹一種更簡單的方法GridLayoutAnimationController。

GridLayoutAnimationController

GridLayoutAnimationController是LayoutAnimationController的子類,它主要用於grid佈局的動畫,但是當我使用GridLayout來使用這個動畫時,總是報LayoutAnimationController.AnimationParameters can not be cast to GridLayoutAnimationController.AnimationParameters,可能GridLayoutAnimationController只能作用於GridView吧,下面我就以GridView來談談這個動畫。
我們先看看GridLayoutAnimation:

<gridLayoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
    android:columnDelay="0.5"
    android:rowDelay="0"
    android:direction="left_to_right"
    android:directionPriority="none"
    android:animation="@anim/test_animation">

</gridLayoutAnimation>
  • android:columnDelay:行動畫延遲,取值0~1的浮點數,也可以取百分數
  • android:rowDelay:行動畫延遲,取值同上
  • android:direction:動畫執行方向,可選左至右、上至下、下至上、右至左,可以組合使用
  • android:directionPriority:方向優先級,有row、column和none三個值,分別代表列優先、行優先以及行列同時進行。關於優先級我就以row來講解,列優先時,動畫會從第一列的第一個元素開始然後經過列延遲,執行第一列的第二個元素,以此類推,並且此時會忽略行動畫延遲;同樣的,行優先時忽略列延遲;none時,行列延遲都需要考慮。

下面我們看看具體的實現吧,先看佈局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content">

    <GridView
        android:id="@+id/gridView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:columnWidth="96dp"
        android:horizontalSpacing="2dp"
        android:numColumns="4"
        android:stretchMode="none"
        android:verticalSpacing="2dp"></GridView>
</LinearLayout>

然後是代碼中的實現:

        mGridView = (GridView) findViewById(R.id.gridView);
        List<Integer> colorList = new ArrayList<Integer>();
        colorList.add(android.R.color.holo_blue_light);
        colorList.add(android.R.color.holo_orange_light);
        colorList.add(android.R.color.holo_green_light);
        colorList.add(android.R.color.holo_red_light);
        colorList.add(android.R.color.holo_red_light);
        colorList.add(android.R.color.holo_green_light);
        colorList.add(android.R.color.holo_orange_light);
        colorList.add(android.R.color.holo_blue_light);
        GridAdapter mAdapter = new GridAdapter(this,colorList);
        mGridView.setAdapter(mAdapter);

        ScaleAnimation animation = new ScaleAnimation(1, 0, 1, 0, Animation.RELATIVE_TO_SELF,
                0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        animation.setStartOffset(600);
        animation.setDuration(500);
        animation.setFillAfter(true);

        GridLayoutAnimationController controller = new GridLayoutAnimationController(animation,0.2f,0f);
        controller.setDirection(GridLayoutAnimationController.DIRECTION_LEFT_TO_RIGHT);
        controller.setDirectionPriority(GridLayoutAnimationController.PRIORITY_NONE);
        mGridView.setLayoutAnimation(controller);

這裏我用的是代碼來實現GridLayoutAnimationController,和xml中實現沒區別。

關於GridView適配器的實現我也把代碼貼出來吧:

public class GridAdapter extends BaseAdapter {

    private List<Integer> colorList;
    private AppCompatActivity mContext;
    private LayoutInflater mInflater;

    public GridAdapter(AppCompatActivity context, List<Integer> list){
        this.colorList = list;
        this.mContext = context;
        mInflater = context.getLayoutInflater();
    }

    @Override
    public int getCount() {
        return colorList.size();
    }

    @Override
    public Object getItem(int position) {
        return colorList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder;
        if(convertView == null){
            convertView = mInflater.inflate(R.layout.activity_gridview,null);
            viewHolder = new ViewHolder();
            viewHolder.imageView = (ImageView) convertView.findViewById(R.id.imageView);
            convertView.setTag(viewHolder);
        }else {
            viewHolder = (ViewHolder) convertView.getTag();
        }
        viewHolder.imageView.setBackgroundResource(colorList.get(position));
        return convertView;
    }

    class ViewHolder{
        ImageView imageView;
    }

}

最後看看效果:
這裏寫圖片描述
效果是不是和上面完全一樣?除了每個控件之間間隔2dp。使用GridLayoutAnimationController來實現這種grid佈局的動畫效果非常的方便。

LayoutTransition

LayoutTransition與LayoutAnimation的區別之處在於LayoutTransition使用的是屬性動畫,而LayoutAnimation使用的是View動畫。在介紹LayoutTransition之前我們先來介紹andoird默認的佈局動畫。

animateLayoutChanges

凡是派生自ViewGroup的控件都有該屬性,當返回true時,將打開該ViewGroup下的默認過渡動畫,即該ViewGroup中的子控件顯示時都擁有默認的過渡動畫;將返回false時不具備過渡效果。下面我們來看看該屬性的效果。

先來看看佈局代碼,非常簡單,只需要添加該條屬性即可:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:id="@+id/activity_main"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:animateLayoutChanges="true"
    android:layout_height="match_parent">


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="AddButton"
            android:text="添加按鈕"/>

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="RemoveButton"
            android:text="刪除按鈕"/>

    </LinearLayout>

</LinearLayout>

下面看看代碼中的實現:

public class MainActivity extends AppCompatActivity {

    private LinearLayout mLayout;
    private int num = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mLayout = (LinearLayout) findViewById(R.id.activity_main);

    }

    public void AddButton(View v) {
        num++;
        Button button = new Button(this);
        button.setText("Button" + num);
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams
                .WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        button.setLayoutParams(params);
        mLayout.addView(button, num);
    }

    public void RemoveButton(View v) {
        if (num > 0) {
            Button button = (Button) mLayout.getChildAt(num);
            mLayout.removeView(button);
            num--;
        }
    }
}

以上代碼非常容易理解,就是在LinearLayout中添加按鈕和刪除按鈕。

最後我們來看看效果:
這裏寫圖片描述
會有一個逐漸顯現的過渡效果。

LayoutTransition的實現

android默認的佈局動畫效果無法進行自定義,因此要實現自定義佈局動畫,我們可以使用LayoutTransition來實現,下面先來看看LayoutTransition的四個標誌:

public class LayoutTransition {

    /**
     * A flag indicating the animation that runs on those items that are changing
     * due to a new item appearing in the container.
     */
    public static final int CHANGE_APPEARING = 0;

    /**
     * A flag indicating the animation that runs on those items that are changing
     * due to an item disappearing from the container.
     */
    public static final int CHANGE_DISAPPEARING = 1;

    /**
     * A flag indicating the animation that runs on those items that are appearing
     * in the container.
     */
    public static final int APPEARING = 2;

    /**
     * A flag indicating the animation that runs on those items that are disappearing
     * from the container.
     */
    public static final int DISAPPEARING = 3;

    /**
     * A flag indicating the animation that runs on those items that are changing
     * due to a layout change not caused by items being added to or removed
     * from the container. This transition type is not enabled by default; it can be
     * enabled via {@link #enableTransitionType(int)}.
     */
    public static final int CHANGING = 4;
    ...
}
  • CHANGE_APPEARING:當ViewGroup中一個新的控件顯示時其他控件所執行的動畫標誌;
  • CHANGE_DISAPPEARING:當ViewGroup中一個控件消失時其他控件所執行的動畫標誌;
  • APPEARING:當ViewGroup中一個新的控件顯示時所執行的動畫標誌;
  • DISAPPEARING:當ViewGroup中一個控件消失時所執行的動畫標誌;
  • CHANGING:子View在容器中位置改變時的過渡動畫,不涉及刪除或者添加操作,這個暫時不做介紹了,默認是不開啓的。

通過這4個標誌,我們不僅可以對新加入和移除的控件執行動畫,也可以在此時對該ViewGroup內其他控件執行動畫效果,下面來看看具體實現。

先看看主佈局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:id="@+id/linearLayout"
              android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="AddButton"
            android:text="添加按鈕"/>

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="DeleteButton"
            android:text="刪除按鈕"/>
    </LinearLayout>
</LinearLayout>

接着來看看代碼中的實現:

public class TestLayoutTransition extends AppCompatActivity {

    private LinearLayout mLinearLayout;
    private int num = 0;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.test_layout_transition);

        mLinearLayout = (LinearLayout) findViewById(R.id.linearLayout);

        LayoutTransition transition = new LayoutTransition();

        //appearing動畫
        ObjectAnimator animator1 = ObjectAnimator.ofFloat(null, "translationX", 0f, 100f);
        //disappearing動畫
        ObjectAnimator animator2 = ObjectAnimator.ofFloat(null, "rotation", 0f, 90f, 0f);
        /* 這裏得額外注意!使用change_appearing和change_disappearing時必須使用PropertyValuesHolder來構建,
        並且必須使用以下4箇中的兩個,如果相關屬性不想變動的話,那麼就都設置一樣的值,比如(0,0),另外,如果想要
        動畫生效,必須保證開始和結尾的值相同,比如(0,1,0)就是有效的,(100,100)是無效的*/
        PropertyValuesHolder pvhLeft = PropertyValuesHolder.ofInt("left", 0, 0);
        PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top", 0, 0);
        PropertyValuesHolder pvhRight = PropertyValuesHolder.ofInt("right", 0, 0);
        PropertyValuesHolder pvhBottom = PropertyValuesHolder.ofInt("bottom", 0, 0);

        //change_appearing動畫
        PropertyValuesHolder animatorA = PropertyValuesHolder.ofFloat("scaleX", 1f, 2f, 1f);
        ObjectAnimator animator3 = ObjectAnimator.ofPropertyValuesHolder(mLinearLayout, pvhLeft,
                pvhTop, pvhRight, pvhBottom, animatorA);

        //change_disappearing動畫
        PropertyValuesHolder animatorD = PropertyValuesHolder.ofFloat("scaleX", 1f, 0.5f, 1f);
        ObjectAnimator animator4 = ObjectAnimator.ofPropertyValuesHolder(mLinearLayout, pvhLeft,
                pvhTop, pvhRight, pvhBottom, animatorD);

        //設置各個階段的動畫
        transition.setAnimator(LayoutTransition.APPEARING, animator1);
        transition.setAnimator(LayoutTransition.DISAPPEARING, animator2);
        transition.setAnimator(LayoutTransition.CHANGE_APPEARING, animator3);
        transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, animator4);

        //分別設置各類型動畫的執行時間,不設置也沒關係,系統默認是300ms
        transition.setDuration(LayoutTransition.APPEARING, 500);
        transition.setDuration(LayoutTransition.DISAPPEARING, 500);
        transition.setDuration(LayoutTransition.CHANGE_APPEARING, 1000);
        transition.setDuration(LayoutTransition.CHANGE_DISAPPEARING, 1000);

        //設置change_appearing和change_disappearing時各個子item執行動畫的延遲時間
        transition.setStagger(LayoutTransition.CHANGE_APPEARING, 100);
        transition.setStagger(LayoutTransition.CHANGE_DISAPPEARING, 100);

        mLinearLayout.setLayoutTransition(transition);
    }

    public void AddButton(View v) {
        num++;
        Button button = new Button(this);
        button.setText("Button" + num);
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams
                .WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        button.setLayoutParams(params);
        mLinearLayout.addView(button, 1);
    }

    public void DeleteButton(View v) {
        if (num > 0) {
            mLinearLayout.removeViewAt(1);
            num--;
        }
    }
}

上面代碼註解的已經非常詳細了,主要還是change_appearing和change_disappearing時比較特殊,一定要記住,如果直接使用ObjectAnimator是無效的,大家可以自己去嘗試下加深印象。

最後看看效果:
這裏寫圖片描述
使用了setStagger就是上面的效果,不然所有item是同時執行動畫的。

屬性動畫之get和set

關於屬性動畫的基本用法,上篇我們已經介紹過了,不過,在使用屬性動畫的時候有一點是需要注意的,我先貼出一段ObjectAnimator源碼中的文字介紹:

Appropriate set/get functions are then determined internally and the animation will call these functions as necessary to animate the property.

什麼意思呢,就是說使用屬性動畫時,要使用動畫的屬性必須含有set和get方法,這和屬性動畫的原理是一致的,因爲屬性動畫其實就是通過set方法來根據相應時間進行屬性值的改變並以此達到動畫的效果。所以,如果一個屬性不支持get和set方法,那麼就沒法使用屬性動畫,那麼,如何讓任何屬性都支持屬性動畫呢,請看下面的例子。

先上佈局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="執行動畫"
        android:onClick="DoAnimator"/>

    <ListView
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:id="@+id/animatorListView"></ListView>

</LinearLayout>

佈局沒啥好說的,一個按鈕和一個listView。

下面上代碼中的實現:

public class TestSetAndGet extends AppCompatActivity {

    private ListView mListView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_set_and_get);

        mListView = (ListView) findViewById(R.id.animatorListView);

        String[] numList = {"1", "2", "3", "4", "5"};
        ArrayAdapter<String> mAdapter = new ArrayAdapter<String>(this, android.R.layout
                .simple_list_item_1, numList);
        mListView.setAdapter(mAdapter);
    }

    public void DoAnimator(View v) {

        /* 直接使用此方法不行
        ObjectAnimator animator = ObjectAnimator.ofInt(mListView,"width",200,300,200);
        animator.setDuration(2000);
        animator.start();
        */

        /* 第一種方法(推薦)*/
        ViewWrapper viewWrapper = new ViewWrapper(mListView);
        ObjectAnimator animator = ObjectAnimator.ofInt(viewWrapper,"width",200,300,200);
        animator.setDuration(2000);
        animator.start();

        /* 第二種方法 */
//        ValueAnimator valueAnimator = ValueAnimator.ofInt(200,300,200);
//        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
//
//            private IntEvaluator evaluator = new IntEvaluator();
//
//            @Override
//            public void onAnimationUpdate(ValueAnimator animator) {
//
//                /* 第一種寫法
//                float fraction = animator.getAnimatedFraction();
//                System.out.println("fraction:" + fraction);
//                if(fraction < 0.5f){
//                    mListView.getLayoutParams().width = evaluator.evaluate(fraction,200,400);
//                    System.out.println("<0.5:" + evaluator.evaluate(fraction,200,400));
//                }else {
//                    mListView.getLayoutParams().width = evaluator.evaluate(fraction,400,200);
//                    System.out.println(">0.5:" + evaluator.evaluate(fraction,400,200));
//                }
//                mListView.requestLayout();
//                */
//
//                /* 第二種寫法 */
//                int currentValue = (int) animator.getAnimatedValue();
//                System.out.println("currentValue:" + currentValue);
//                mListView.getLayoutParams().width = currentValue;
//                mListView.requestLayout();
//            }
//        });
//        valueAnimator.setDuration(2000);
//        valueAnimator.start();

    }

    private static class ViewWrapper{

        private View targetView;

        public ViewWrapper(View view){
            this.targetView = view;
        }

        public int getWidth(){
            return  targetView.getLayoutParams().width;
        }

        public void setWidth(int widthValue){
            targetView.getLayoutParams().width = widthValue;
            targetView.requestLayout();
        }
    }
}

上面的代碼註釋寫的很清楚了,主要介紹了兩種實現的方式:

  • 通過包裝View來間接提供set和get方法,比較推薦;
  • 通過監聽ValueAnimator的動畫過程,實現屬性的改變,其中,又有兩種實現方式:
    • 直接通過animator返回的當前值來設置
    • 通過animator返回的fraction(可以理解爲時間片,取值0.0~1.0)自己來計算,其中要注意的是代碼中將width從200~300~200,其中實際上變化的總量有200,所以計算時,需要從200開始,400結束,這樣計算的值纔不會出錯,即(400 - 200) * 0.5 = 100,如果是300結束,那麼在0.5時變化量爲(300 - 200) * 0.5 = 50,就出錯了,這點要注意。

最後看下效果圖,兩種實現方式都是相同的:
這裏寫圖片描述

自定義估值器

估值器我們在上面已經用過了,用的是IntEvaluator,除了這個整形估值器外,還有ArgbEvaluator, FloatArrayEvaluator, FloatEvaluator, IntArrayEvaluator,PointFEvaluator, RectEvaluator。它的主要作用就是根據當前屬性改變的時間百分比來計算改變後的屬性值。一般來說系統給出的這些估值器已經夠用了,除非是自己定義的屬性類,需要自定義估值器。

比如我需要實現一個imageView的平移、旋轉、縮放和透明度漸變,除了可以用AnimatorSet和PropertyValueHolder來實現組合動畫外,還可以通過自定義估值器來進行實現,下面看看具體實現。

先上佈局,只有一個按鈕和imageView:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="StartAnimation"
        android:layout_centerInParent="true"
        android:text="開啓動畫"/>

    <ImageView
        android:id="@+id/dialogIv"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:background="@drawable/dialog"/>

</RelativeLayout>

下面看看自定義屬性類的實現:

public class CustomProperty {

    private int rotationAngel;//旋轉的角度
    private float alpha;//透明度
    private float scale;//縮放的大小
    private float x;
    private float y;

    public CustomProperty(int rotationAngel, float alpha, float scale, float x, float y) {
        this.rotationAngel = rotationAngel;
        this.alpha = alpha;
        this.scale = scale;
        this.x = x;
        this.y = y;
    }

    public int getRotationAngel() {
        return rotationAngel;
    }

    public void setRotationAngel(int rotationAngel) {
        this.rotationAngel = rotationAngel;
    }

    public float getAlpha() {
        return alpha;
    }

    public void setAlpha(float alpha) {
        this.alpha = alpha;
    }

    public float getScale() {
        return scale;
    }

    public void setScale(float scale) {
        this.scale = scale;
    }

    public float getX() {
        return x;
    }

    public void setX(float x) {
        this.x = x;
    }

    public float getY() {
        return y;
    }

    public void setY(float y) {
        this.y = y;
    }
}

很普通的class實現,包含x、y、scale、rotation以及alpha。

接着是自定義估值器的實現,我們可以看一些系統內置估值器的實現,主要就是evaluate這個方法,因此,我們只需要重寫這個evaluate方法即可:

public class CustomEvaluator implements TypeEvaluator<CustomProperty> {

    @Override
    public CustomProperty evaluate(float fraction, CustomProperty startValue, CustomProperty
            endValue) {
        CustomProperty customProperty = new CustomProperty(0,0,0,0,0);
        customProperty.setX(400 * fraction);
        customProperty.setY(400 * fraction * fraction);
        customProperty.setAlpha(0.5f + fraction);
        customProperty.setRotationAngel((int) (1080 * fraction));
        customProperty.setScale(1 + fraction);
        return customProperty;
    }
}

很簡單,自己通過計算返回一個customProperty,這裏可以根據需求自行更改,反正返回一個自己想要的值。

然後我們看看代碼中如何實現:

public class TestEvaluator extends AppCompatActivity {

    private ImageView dialogIv;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_evaluator);

        dialogIv = (ImageView) findViewById(R.id.dialogIv);
    }

    public void StartAnimation(View v){
        ValueAnimator valueAnimator = new ValueAnimator();
        valueAnimator.setDuration(3000);
        valueAnimator.setObjectValues(new CustomProperty(0,0,0,0,0));
        //這裏需要注意的是不能和下一句代碼位置相反
        // 不然就報錯,我這裏就設置了endValue,如果設置多個,就是startValue和endValue都設置,也是可以的,不影響
        valueAnimator.setEvaluator(new CustomEvaluator());
        valueAnimator.start();
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                CustomProperty customProperty = (CustomProperty) animation.getAnimatedValue();
                dialogIv.setRotation(customProperty.getRotationAngel());
                dialogIv.setScaleX(customProperty.getScale());
                dialogIv.setScaleY(customProperty.getScale());
                dialogIv.setAlpha(customProperty.getAlpha());
                dialogIv.setX(customProperty.getX());
                dialogIv.setY(customProperty.getY());
                dialogIv.requestLayout();
            }
        });
    }
}

在onAnimationUpdate這個回調方法中通過getAnimatedValue()獲取估值器返回的自定義屬性類的值,再根據這些值給控件設置動畫效果,其中的注意點已經寫出來了,最後別忘了調用requestLayout()。

最後我們看下效果:
這裏寫圖片描述
成一個拋物線移動、繞z軸旋轉3圈、放大2倍並且透明度從0.5~1漸變。

自定義插值器

關於插值器,想必大家都不陌生,動畫默認的插值器的是線性插值器,系統內置的插值器有很多,看下圖:
這裏寫圖片描述
這裏我就不一一講解了,感興趣的可以自己去嘗試下,比較常用的有AccelerateDecelerateInterpolator(加速減速),AccelerateInterpolator(加速),BounceInterpolator(小球回彈效果),DecelerateInterpolator(減速)等等。

如果現成的插值器都不能滿足我們的需求,那麼我們就需要自定義插值器。自定義插值器也非常方便,因爲它和估值器一樣,都只需要關注一個方法即可,在估值器中我們關注的是evaluate這個方法,在插值器中我們只需要關注getInterpolation這個方法即可,當然從其他插值器的源碼中我們也可以看出都是對這個方法的操作來實現不同的效果。

下面我們來看看這個getInterpolation方法:

public float getInterpolation(float input) {
    ...
}

只有一個參數input,改值的範圍是0~1,是系統根據動畫時間計算出來的值。值得一提的是,還記得估值器中evaluate的第一個參數fraction嗎,它也是一個0~1的值,而且也是系統根據動畫時間計算出來的值,但其實它和input是有不同的,input是系統根據動畫時間以及刷新率一幀幀算出來的,而fraction就是getInterpolation(float input)這個方法返回的值,所以當使用默認插值器,也就是線性插值器時,由於線性插值器輸入值與輸出值相同,此時input和fraction的值纔是相同的,其餘情況,兩者是不同的。

下面我們來實現先減速後加速的插值器效果,從效果上分析,可以由三階貝塞爾曲線構造出,可以登錄此網站來修改效果:三階貝塞爾曲線參數,這裏我用的是(.12,.88,.96,.2),再根據三階貝塞爾的公式:
這裏寫圖片描述
代入x座標能求出x的值,代入y座標能求而出y的值。

下面我們看具體實現,佈局什麼的和上面的相同,直接看自定義插值器的實現:

public class CustomInterpolator implements Interpolator {

    public CustomInterpolator() {
    }

    @Override
    public float getInterpolation(float input) {
        //三階貝塞爾,4個控制點爲(0,0)、(0.12,0.88)、(0.96,0.2)以及(1,1)
        //如果都用x座標帶進去,求的是x的值
        //如果都用y座標帶進去,求得是y的值
        float r1 = (float) (0 * Math.pow(1 - input, 3));
        float r2 = (float) (3 * 0.88 * Math.pow(1 - input, 2) * input);
        float r3 = (float) (3 * 0.2 * (1 - input) * Math.pow(input, 2));
        float r4 = (float) (1 * Math.pow(input, 3));
        System.out.println(">>>>>>>" + (r1 + r2 + r3 + r4));
        return r1 + r2 + r3 + r4;
    }
}

然後只需要在代碼中使用:

valueAnimator.setInterpolator(new CustomInterpolator());

其他代碼同上,我就不貼出來了,最後我們看效果吧:
這裏寫圖片描述
很明顯,先減速然後再加速。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章