Android佈局動畫之animateLayoutChanges與LayoutTransition

轉載請註明出處(萬分感謝!):
http://blog.csdn.net/javazejian/article/details/52571779
出自【zejian的博客】

關聯文章:

走進絢爛多彩的屬性動畫-Property Animation(上篇)
走進絢爛多彩的屬性動畫-Property Animation之Interpolator和TypeEvaluator(下篇)
屬性動畫-Property Animation之ViewPropertyAnimator 你應該知道的一切
Android佈局動畫之animateLayoutChanges與LayoutTransition

  關於佈局動畫是針對ViewGroup而言的,意指ViewGroup在增加子View或者刪除子View時其子View的過渡動畫,在android官網有這麼一個簡單的例子,其效果如下,接下來我們就通過這個例子來入門

最簡單的佈局動畫實現

  事實上,實現上面ViewGroup的佈局動畫非常簡單,我們只要給子View所在的ViewGroup的xml中添加下面的屬性即可:

android:animateLayoutChanges="true"

  通過設置以上代碼,當ViewGroup在添加View時,子View會呈現出過渡的動畫效果,這個動畫效果是android默認的顯示效果,而且無法使用自定義的動畫來替換這個效果,不過還是有其他方式可以實現自定義的過渡動畫滴,這個我們後面會分析,下面看看android官方給的demo的代碼實現:
activity_layout_changes.xml佈局文件

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- A vertical LinearLayout in a ScrollView. This emulates a ListView (and is lighter weight
         than a ListView when there aren't many rows). -->
    <ScrollView android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!-- Note that this LinearLayout has the "animateLayoutChanges" property set to true.
             This tells the framework to automatically animate child views (in this case, rows)
             as they are added to and removed from the LinearLayout. -->
        <LinearLayout android:id="@+id/container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:showDividers="middle"
            android:divider="?android:dividerHorizontal"
            android:animateLayoutChanges="true"
            android:paddingLeft="16dp"
            android:paddingRight="16dp" />

    </ScrollView>

    <!-- The "empty" view to show when there are no items in the "list" view defined above. -->
    <TextView android:id="@android:id/empty"
        style="?android:textAppearanceSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:padding="32dp"
        android:text="@string/message_empty_layout_changes"
        android:textColor="?android:textColorSecondary" />

</FrameLayout>

  佈局代碼比較簡單,使用了一個ScrollView包裹了LinearLayout佈局並在LinearLayout中添加了屬性android:animateLayoutChanges="true",啓動了android自帶的佈局動畫,之所以使用ScrollView,是爲了在添加子View多時可以進行滑動,而TextView只不過在沒有子view時一個提示語。接着看看子View的佈局文件list_item_example.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="?android:listPreferredItemHeightSmall"
    android:orientation="horizontal"
    android:showDividers="middle"
    android:divider="?android:dividerVertical"
    android:dividerPadding="8dp"
    android:gravity="center">

    <!-- Dummy text view that will display the name of a random country. -->
    <TextView android:id="@android:id/text1"
        style="?android:textAppearanceMedium"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content"
        android:paddingLeft="?android:listPreferredItemPaddingLeft" />

    <!-- A button that, when pressed, will delete this list item row from its container. -->
    <ImageButton android:id="@+id/delete_button"
        android:layout_width="48dp"
        android:layout_height="match_parent"
        android:src="@drawable/ic_list_remove"
        android:background="?android:selectableItemBackground"
        android:contentDescription="@string/action_remove_item" />

</LinearLayout>

  主要控件有用於顯示每個子View內容的TextView和用於響應刪除子View事件的ImageButton,最後就是Activity的實現:
LayoutChangesActivity.java

package com.example.android.animationsdemo;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

/**
 * This sample demonstrates how to use system-provided, automatic layout transitions. Layout
 * transitions are animations that occur when views are added to, removed from, or changed within
 * a {@link ViewGroup}.
 *
 * <p>In this sample, the user can add rows to and remove rows from a vertical
 * {@link android.widget.LinearLayout}.</p>
 */
public class LayoutChangesActivity extends Activity {

    private ViewGroup mContainerView;

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

        mContainerView = (ViewGroup) findViewById(R.id.container);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        //響應添加事件的menu
        getMenuInflater().inflate(R.menu.activity_layout_changes, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                NavUtils.navigateUpTo(this, new Intent(this, MainActivity.class));
                return true;

            case R.id.action_add_item:
                //添加子View
                findViewById(android.R.id.empty).setVisibility(View.GONE);
                addItem();
                return true;
        }

        return super.onOptionsItemSelected(item);
    }

    private void addItem() {
        //實例化一個子View
        final ViewGroup newView = (ViewGroup) LayoutInflater.from(this).inflate(
                R.layout.list_item_example, mContainerView, false);

        // 隨機設置子View的內容
        ((TextView) newView.findViewById(android.R.id.text1)).setText(
                COUNTRIES[(int) (Math.random() * COUNTRIES.length)]);

        //設置刪除按鈕的監聽
        newView.findViewById(R.id.delete_button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mContainerView.removeView(newView);

                // If there are no rows remaining, show the empty view.
                if (mContainerView.getChildCount() == 0) {
                    findViewById(android.R.id.empty).setVisibility(View.VISIBLE);
                }
            }
        });
        //添加子View
        mContainerView.addView(newView, 0);
    }

    /**
     * A static list of country names.
     */
    private static final String[] COUNTRIES = new String[]{
            "Belgium", "France", "Italy", "Germany", "Spain",
            "Austria", "Russia", "Poland", "Croatia", "Greece",
            "Ukraine",
    };
}

  代碼比較簡單,mContainerView作爲子View的承載容器,我們可以不斷添加子View,也可以移除子View,由於我們在佈局文件中啓動了android自帶的佈局動畫,所以在添加子View或移除子View都會有過度動畫,現在運行程序,效果如下:

這就是android中最簡單的佈局動畫

佈局動畫實之layoutAnimation

  除了上面的佈局動畫外,有時我們可能需要第一次加載ListView或者GridView的時候能有個動畫的過度效果,以便達到更好的體驗,如下ListView加載子View的效果:

  事實上實現這種效果也比較簡單,我們只需要在ListView的佈局文件中添加android:layoutAnimation=”@anim/layout”屬性即可。接下來給出實現步驟:
實現動畫效果
left_into.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="500"
        android:fromXDelta="100%"
        android:fromYDelta="0"
        android:toXDelta="0"
        android:toYDelta="0" />
    <alpha
        android:duration="500"
        android:fromAlpha="0"
        android:toAlpha="1" />
</set>

這個比較簡單,就一個平移和透明度的效果,接着創建layout_animation.xml

<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
    android:animation="@anim/left_into"
    android:animationOrder="normal"
    android:delay="0.5"
    />

這裏簡單介紹一下layoutAnimation標籤屬性:

屬性名 含義
android:delay delay的單位爲秒,表示子View進入的延長時間
android:animationOrder 子類的進入方式 ,其取值有,normal 0 默認 ,reverse 1 倒序 ,random 2 隨機
android:animation 子view要執行的具體動畫的文件,自定義即可

然後設置給listView控件即可,activity_listview.xml:

<?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">

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        <!-- 設置在這裏 ⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️ -->
        android:layoutAnimation="@anim/layout_animation"
        >
    </ListView>
</LinearLayout>

ListViewActivity.java代碼如下:

package com.example.android.animationsdemo;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by zejian
 * Time 16/9/17.
 * Description:
 */
public class ListViewActivity extends Activity {

    private ListView listView;
    private List<String> list;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_listview);
        listView= (ListView) findViewById(R.id.listView);
        initData();
        listView.setAdapter(new Myadpter());
    }

    public void initData(){
        list = new ArrayList<>();
        for(int i=1;i<30;i++){
            list.add("佈局動畫listView測試_"+i);
        }
    }

    private  class  Myadpter extends BaseAdapter{

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

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

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

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            TextView tv= new TextView(ListViewActivity.this);
            tv.setTextSize(20);
            tv.setHeight(100);
            tv.setText(list.get(position));
            return tv;
        }
    }
}

  執行以上代碼,效果就是前面我們給出listView進入的動態效果,這裏特別注意的是,動畫只在listView子View第一次進入時有效,如果後面動態給listView添加子View時,此動畫效果無效,當然除了通過xml設置外,我們還可以通過代碼動態設置,代碼範例如下,這裏就不演示了。

//通過加載XML動畫設置文件來創建一個Animation對象,子View進入的動畫
Animation animation=AnimationUtils.loadAnimation(this, R.anim.left_into);
//得到一個LayoutAnimationController對象;
LayoutAnimationController lac=new LayoutAnimationController(animation);
//設置控件顯示的順序;
lac.setOrder(LayoutAnimationController.ORDER_REVERSE);
//設置控件顯示間隔時間;
lac.setDelay(0.5f);
//爲ListView設置LayoutAnimationController屬性;
listView.setLayoutAnimation(lac);

  通過AnimationUtils.loadAnimation加載子View的動畫來並返回一個Animation對象,然後將Animation對象設置到LayoutAnimationController中並返回LayoutAnimationController對象,配置LayoutAnimationController對象的一些屬性,最後設置到ListView中,其中LayoutAnimationController對應layoutAnimation標籤,這裏需要注意的是layoutAnimation動畫不僅僅限於ListView,GridView中,也可用於一切ViewGroup中。

佈局動畫實之LayoutTransition

  前面我們說過ViewGroup在設置android:animateLayoutChanges="true"後在添加或者刪除子view時可以啓用系統帶着的動畫效果,但這種效果無法通過自定義動畫去替換。不過還好android官方爲我們提供了LayoutTransition類,通過LayoutTransition就可以很容易爲ViewGroup在添加或者刪除子view設置自定義動畫的過渡效果了。
  LayoutTransition類用於當前佈局容器中需要View添加,刪除,隱藏,顯示時設置佈局容器子View的過渡動畫。也就是說利用LayoutTransition,可以分別爲需添加或刪除的View對象在移動到新的位置的過程添加過渡的動畫效果。我們可以通過setLayoutTransition()方法爲佈局容器ViewGroup設置LayoutTransition對象,代碼如下:

//初始化容器動畫
LayoutTransition mTransitioner = new LayoutTransition();
container.setLayoutTransition(mTransitioner);

一般地,Layout中的子View對象有四種動畫變化的形式,如下:

屬性值 含義
LayoutTransition.APPEARING 子View添加到容器中時的過渡動畫效果。
LayoutTransition.CHANGE_APPEARING 子View添加到容器中時,其他子View位置改變的過渡動畫
LayoutTransition.DISAPPEARING 子View從容器中移除時的過渡動畫效果。
LayoutTransition.CHANGE_DISAPPEARING 子View從容器中移除時,其它子view位置改變的過渡動畫
LayoutTransition.CHANGING 子View在容器中位置改變時的過渡動畫,不涉及刪除或者添加操作

下面給出一個例子,先看佈局文件activity_layout_animation.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    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="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="addView"
            android:text="添加控件" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="removeView"
            android:text="移除控件" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" />
</LinearLayout>

比較簡單不囉嗦,接着看看LayoutAnimationActivity.java


 package com.example.android.animationsdemo;

import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;

/**
 * Created by zejian
 * Time 16/9/17.
 * Description:
 */
public class LayoutAnimationActivity extends Activity {


    private int i = 0;
    private LinearLayout container;
    private LayoutTransition mTransitioner;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_layout_animation);

        container = (LinearLayout) findViewById(R.id.container);
        //構建LayoutTransition
        mTransitioner = new LayoutTransition();
        //設置給ViewGroup容器
        container.setLayoutTransition(mTransitioner);
        setTransition();
    }


    private void setTransition() {
        /**
         * 添加View時過渡動畫效果
         */
        ObjectAnimator addAnimator = ObjectAnimator.ofFloat(null, "rotationY", 0, 90,0).
                setDuration(mTransitioner.getDuration(LayoutTransition.APPEARING));
        mTransitioner.setAnimator(LayoutTransition.APPEARING, addAnimator);

        /**
         * 移除View時過渡動畫效果
         */
        ObjectAnimator removeAnimator = ObjectAnimator.ofFloat(null, "rotationX", 0, -90, 0).
                setDuration(mTransitioner.getDuration(LayoutTransition.DISAPPEARING));
        mTransitioner.setAnimator(LayoutTransition.DISAPPEARING, removeAnimator);

        /**
         * view 動畫改變時,佈局中的每個子view動畫的時間間隔
         */
        mTransitioner.setStagger(LayoutTransition.CHANGE_APPEARING, 30);
        mTransitioner.setStagger(LayoutTransition.CHANGE_DISAPPEARING, 30);


        /**
         *LayoutTransition.CHANGE_APPEARING和LayoutTransition.CHANGE_DISAPPEARING的過渡動畫效果
         * 必須使用PropertyValuesHolder所構造的動畫纔會有效果,不然無效!使用ObjectAnimator是行不通的,
         * 發現這點時真特麼噁心,但沒想到更噁心的在後面,在測試效果時發現在構造動畫時,”left”、”top”、”bottom”、”right”屬性的
         * 變動是必須設置的,至少設置兩個,不然動畫無效,問題是我們即使這些屬性不想變動!!!也得設置!!!
         * 我就問您惡不噁心!,因爲這裏不想變動,所以設置爲(0,0)
         *
         */
        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);


        /**
         * view被添加時,其他子View的過渡動畫效果
         */
        PropertyValuesHolder animator = PropertyValuesHolder.ofFloat("scaleX", 1, 1.5f, 1);
        final ObjectAnimator changeIn = ObjectAnimator.ofPropertyValuesHolder(
                this, pvhLeft,  pvhBottom, animator).
                setDuration(mTransitioner.getDuration(LayoutTransition.CHANGE_APPEARING));
        //設置過渡動畫
        mTransitioner.setAnimator(LayoutTransition.CHANGE_APPEARING, changeIn);


        /**
         * view移除時,其他子View的過渡動畫
         */
        PropertyValuesHolder pvhRotation =
                PropertyValuesHolder.ofFloat("scaleX", 1, 1.5f, 1);
        final ObjectAnimator changeOut = ObjectAnimator.ofPropertyValuesHolder(
                this, pvhLeft, pvhBottom, pvhRotation).
                setDuration(mTransitioner.getDuration(LayoutTransition.CHANGE_DISAPPEARING));

        mTransitioner.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, changeOut);
    }


    public void addView(View view) {
        i++;
        Button button = new Button(this);
        button.setText("佈局動畫_" + i);
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
        container.addView(button, Math.min(1, container.getChildCount()), params);
    }

    public void removeView(View view) {
        if (i > 0)
            container.removeViewAt(0);
    }
}

  簡單分析一下,LayoutTransition.APPEARING和LayoutTransition.DISAPPEARING的情況下直接使用屬性動畫來設置過渡動畫效果即可,而對於LayoutTransition.CHANGE_APPEARING和LayoutTransition.CHANGE_DISAPPEARING必須使用PropertyValuesHolder所構造的動畫纔會有效果,不然無效,真特麼噁心, ,但沒想到更噁心的在後面,在測試效果時發現在構造動畫時,”left”、”top”、”bottom”、”right”屬性的變動是必須設置的,至少設置兩個,不然動畫無效,最坑爹的是我們即使這些屬性不想變動!!!也得設置!!!我就問您惡不噁心!,那麼我們不想改變這四個屬性時該如何設置呢?這時只要傳遞的可變參數都一樣就行如下面的(0,0)也可以是(100,100)即可(坑爹啊!測試半天才發現,一直在考慮代碼有沒有問題,最後發現時特麼的也是醉了…….):

PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top",0,0);  
PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("left",100,100);  
PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("bottom",0,0);  
PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("right",0,0);  

  還有點需要注意的是在使用的ofInt,ofFloat中的可變參數值時,第一個值和最後一個值必須相同,不然此屬性將不會有動畫效果,比如下面首位相同是有效的

PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top",100,0,100);
PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top",0,100,0);

但是如果是下面的設置就是無效的:

PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("left",0,100);

  關於PropertyValuesHolder,後面我會分開單獨一篇來分析,這裏我們只需知道PropertyValuesHolder構造的動畫可以設置給ObjectAnimator.ofPropertyValuesHolder()便可以產生響應的動畫效果即可,該方法原型如下:

public static ObjectAnimator ofPropertyValuesHolder(Object target, PropertyValuesHolder... values)

  最後我們通過setAniamtor的方法設置LayoutTransition的5種狀態下的過渡動畫,最後運行一下程序,效果如下:

最後這裏小結一下LayoutTransition的一些常用函數:

函數名稱 說明
setAnimator(int transitionType, Animator animator) 設置不同狀態下的動畫過渡,transitionType取值爲, APPEARING、DISAPPEARING、CHANGE_APPEARING、CHANGE_DISAPPEARING 、CHANGING
setDuration(long duration) 設置所有動畫完成所需要的時長
setDuration(int transitionType, long duration) 設置特定type類型動畫時長,transitionType取值爲, APPEARING、DISAPPEARING、CHANGE_APPEARING、CHANGE_DISAPPEARING 、CHANGING
setStagger(int transitionType, long duration) 設置特定type類型動畫的每個子item動畫的時間間隔 ,transitionType取值爲: APPEARING、DISAPPEARING、CHANGE_APPEARING、CHANGE_DISAPPEARING、CHANGING
setInterpolator(int transitionType, TimeInterpolator interpolator) 設置特定type類型動畫的插值器, transitionType取值爲: APPEARING、DISAPPEARING、CHANGE_APPEARING、CHANGE_DISAPPEARING、CHANGING
setStartDelay(int transitionType, long delay) 設置特定type類型動畫的動畫延時 transitionType取值爲, APPEARING、DISAPPEARING、CHANGE_APPEARING、CHANGE_DISAPPEARING 、CHANGING
addTransitionListener(TransitionListener listener) 設置監聽器TransitionListener

關於監聽器接口TransitionListener原型如下:

/**
 * This interface is used for listening to starting and ending events for transitions.
 */
public interface TransitionListener {

    /**
     * 監聽LayoutTransition當前對應的transitionType類型動畫開始
     * @param transition LayoutTransition對象實例
     * @param container LayoutTransition綁定的容器-container
     * @param view 當前在做動畫的View對象
     * @param transitionType LayoutTransition類型,取值有:APPEARING、DISAPPEARING、
     *                       CHANGE_APPEARING、CHANGE_DISAPPEARING、CHANGING
     */
    public void startTransition(LayoutTransition transition, ViewGroup container,
                                View view, int transitionType);

    /**
     * 監聽LayoutTransition當前對應的transitionType類型動畫結束
     * @param transition LayoutTransition對象實例
     * @param container LayoutTransition綁定的容器-container
     * @param view 當前在做動畫的View對象
     * @param transitionType LayoutTransition類型,取值有:APPEARING、DISAPPEARING、
     *                       CHANGE_APPEARING、CHANGE_DISAPPEARING、CHANGING
     */
    public void endTransition(LayoutTransition transition, ViewGroup container,
                              View view, int transitionType);
}

  註釋比較清晰,就不過多說明,我們如果想在某種transitionType類型動畫開或者結束時設置某些操作,便可實現該接口,測試效果也比較簡單,這裏就不舉例了。ok~,關於佈局動畫就先了解這麼多吧。

關聯文章:

走進絢爛多彩的屬性動畫-Property Animation(上篇)
走進絢爛多彩的屬性動畫-Property Animation之Interpolator和TypeEvaluator(下篇)
屬性動畫-Property Animation之ViewPropertyAnimator 你應該知道的一切
Android佈局動畫之animateLayoutChanges與LayoutTransition

發佈了65 篇原創文章 · 獲贊 3504 · 訪問量 230萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章