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官网有这么一个简单的例子,其效果如下,接下来我们就通过这个例子来入门
Android布局动画之animateLayoutChanges与LayoutTransition_第1张图片

最简单的布局动画实现

  事实上,实现上面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布局动画之animateLayoutChanges与LayoutTransition_第2张图片
这就是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

你可能感兴趣的:(android,动画,layout,transition,布局动画)