Android动画之LayoutTransition布局动画

1 LayoutTransition 概述

通过对视图动画和属性动画的学习,我们现在可以对一个view进行动画操作,但是如何在添加view,删除view,显示view,隐藏view时给相应view和受影响的其他view添加动画,不太容易做。如果不添加动画,单纯的使用setVisible会显得很突兀。如果只是对受到影响的view添加动画,可以通过设置view的高度使之显示和隐藏,还可以利用ScrollView通过滚动隐藏和显示动画,但其他受影响的view则比较难处理,布局动画LayoutTransition 就可以很好地完成这个功能。


来自Developer 网站关于LayoutTransition概述:
https://developer.android.google.cn/reference/android/animation/LayoutTransition

LayoutTransition字面翻译是布局的过渡也就是布局动画,这个类可以实现ViewGroup的布局改变时自动执行动画,LayoutTransition 从api11开始提供。给ViewGroup设置动画很简单,只需要生成一个LayoutTransition实例,然后调用ViewGroup的setLayoutTransition(LayoutTransition)函数就可以了。当设置了布局动画的ViewGroup添加或者删除内部view时就会触发动画。如果要设置定制的动画,需要调用setAnimator()方法。

布局动画由两种状态的改变导致执行四种不同的动画,两种状态的改变分别是view被添加到ViewGroup(或者变得可见VISIBILITY),view被移除ViewGroup(或者不可见),所以设置View可见或者不可见也将触发布局动画添加和删除动画的逻辑( GONE and VISIBLE)。

四种不同的动画分别是(api11中添加):

  • APPEARING:view被添加(可见)到ViewGroup会触发的动画。
  • DISAPPEARING :view被移除(不可见)ViewGroup会触发的动画。
  • CHANGE_APPEARING :view被添加(可见)到ViewGroup,会影响其他View,此时其它View会触发的动画。
  • CHANGE_DISAPPEARING:view被移除(不可见)ViewGroup,会影响其他View,此时其它View会触发的动画。
    API16 添加了CHANGING 类型,所以是五种类型动画。

四种类型的动画都有默认的动画效果,当只为ViewGroup设置了animateLayoutChanges=true后,触发ViewGroup中view的添加和删除就会触发默认动画。默认情况下DISAPPEARING和CHANGE_APPEARING类型动画会立即执行,其他类型动画则会有个延迟。会导致如下效果:当一个View被添加到布局中,其他受影响的View会首先移动,接着当view添加到布局时运行appearing animation。当一个view被从布局中移除时,首先运行移除动画,接着运行其他受影响的view的动画。可以利用setDuration和setStartDelay修改延迟时间。

注意:
如果在DISAPPEARING动画完成之前运行了APPEARING动画,则DISAPPEARING动画将停止,并且会恢复DISAPPEARING动画的效果。如果您在APPEARING动画完成之前启动DISAPPEARING动画,则会对APPEARING动画发生类似作用。

下面这段话揭示布局动画存在的一些问题:
为布局动画指定的动画,默认的或者用户自定义的,都仅仅是一些模板,简单说就是你指定了动画的值,但是布局动画最终不一定按照你指定的值运行。设置的这些动画有自己的属性值(duration,properties,start delay),但真正运行时动画的开始结束值是进程运行时从view的真实情况自动获取,自动设置的。。。还有很多就不翻译了,大家可以去看看。

ViewGroup在xml文件中设置了animateLayoutChanges=true会有默认的布局动画,但如果在一个多层嵌套的布局中,由于布局处于多个层级的关系这个布局动画很可能不会工作。有时CHANGE_APPEARING 和 CHANGE_DISAPPEARING动画在一个滚动的布局中,可能导致布局动画无法匹配view的实际位置导致动画看着不连贯(或者无法到准确位置),这时可以通过禁用CHANGE_APPEARING 和 CHANGE_DISAPPEARING动画解决。

LayoutTransition构造函数
LayoutTransition()

2 LayoutTransition 默认动画效果

ViewGroup在xml文件中设置了animateLayoutChanges=true会有默认的布局动画效果。

动画执行先后
从上面的分析可以知道,默认情况下DISAPPEARING和CHANGE_APPEARING类型动画会立即执行,其他类型动画则会有个延迟。也就是说如果删除view,被删除的view将先执行动画消失,经过一些延迟受影响的view会进行动画补上位置,如果添加view,受影响的view将会先给添加的view腾位置执行CHANGE_APPEARING动画,经过一些时间的延迟才会执行APPEARING动画。

代码示例:
布局文件:

<LinearLayout 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:id="@+id/ll_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:animateLayoutChanges="true"
    tools:context=".Main8Activity">

    <Button
        android:id="@+id/btnadd"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:text="添加view"/>
    <Button
        android:id="@+id/btndel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:text="删除view"/>
</LinearLayout>
public class Main8Activity extends AppCompatActivity {

    private LinearLayout mContainer;
    private Button mBtnAdd;
    private Button mBtnDel;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main8);
        mContainer = findViewById(R.id.ll_container);
        mBtnAdd = findViewById(R.id.btnadd);
        mBtnAdd.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Button button = new Button(Main8Activity.this);
                button.setPadding(20,20,20,20);
                button.setText("tempBtn");
                mContainer.addView(button,2);
            }
        });

        mBtnDel = findViewById(R.id.btndel);
        mBtnDel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int count = mContainer.getChildCount();
                if (count >= 3){
                    mContainer.removeViewAt(2 );
                }

            }
        });
    }
}

3 设置自定义布局动画

四种不同的动画分别是(api11中添加):

  • APPEARING:view被添加(可见)到ViewGroup会触发的动画。
  • DISAPPEARING :view被移除(不可见)ViewGroup会触发的动画。
  • CHANGE_APPEARING :view被添加(可见)到ViewGroup,会影响其他View,此时其它View会触发的动画。
  • CHANGE_DISAPPEARING:view被移除(不可见)ViewGroup,会影响其他View,此时其它View会触发的动画。
  • API16 添加了CHANGING 类型,View在ViewGroup中位置改变时的过渡动画,不涉及删除或者添加操作。

如何给ViewGroup添加布局动画:

  • 生成LayoutTransition实例LayoutTransition layoutTransition = new LayoutTransition();
  • 为LayoutTransition 各个类型动画设置用户自定义动画,setAnimator()函数
  • 通过ViewGroup的setLayoutTransition(layoutTransition )可以添加布局动画。

setAnimator(int transitionType, Animator animator)
参数说明:
transitionType:动画类型,分为五种,分别是CHANGE_APPEARING,CHANGE_DISAPPEARING,CHANGING,APPEARING,DISAPPEARING。
animator:相应的动画。
setAnimator方法为每种布局动画设置具体的动画,可以设置ObjectAnimator或者包含属性动画的AnimatorSet。
setStagger 可以设置每个子view动画的时间间隔。

APPEARING和DISAPPEARING 动画

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:id="@+id/ll_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:animateLayoutChanges="true"
    tools:context=".Main8Activity">

    <Button
        android:id="@+id/btnadd"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:text="添加view"/>
    <Button
        android:id="@+id/btndel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:text="删除view"/>
</LinearLayout>
public class Main8Activity extends AppCompatActivity {
    private LinearLayout mContainer;
    private Button mBtnAdd;
    private Button mBtnDel;
    private LayoutTransition layoutTransition;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main8);
        mContainer = findViewById(R.id.ll_container);

        layoutTransition = new LayoutTransition();
        addCustomTransition();
        mContainer.setLayoutTransition(layoutTransition);

        mBtnAdd = findViewById(R.id.btnadd);
        mBtnAdd.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Button button = new Button(Main8Activity.this);
                button.setPadding(20,20,20,20);
                button.setText("tempBtn");
                LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT);
                mContainer.addView(button,2,params);
            }
        });

        mBtnDel = findViewById(R.id.btndel);
        mBtnDel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int count = mContainer.getChildCount();
                if (count >= 3){
                    mContainer.removeViewAt(2 );
                }

            }
        });
    }

    private void addCustomTransition() {

        /**
         * 移除View时view的DISAPPEARING动画
         */
        ObjectAnimator addAnimator = ObjectAnimator.ofFloat(null, "translationX", 0, 50,0).
                setDuration(1500);
        layoutTransition.setAnimator(LayoutTransition.DISAPPEARING, addAnimator);

        /**
         * 添加view是view的APPEARING动画
         */
        ObjectAnimator removeAnimator = ObjectAnimator.ofFloat(null, "scaleX", 0.5f, 1).
                setDuration(1500);
        layoutTransition.setAnimator(LayoutTransition.APPEARING, removeAnimator);
    }
}

添加的动画效果为每次添加view时X轴从0.5缩放到1.0,删除view是view向右移动50px后回到原点,然后消失。
效果图:

CHANGE_APPEARING 和 CHANGE_DISAPPEARING

CHANGE_APPEARING :view被添加(可见)到ViewGroup,会影响其他View,此时其它View会触发的动画。
CHANGE_DISAPPEARING:view被移除(不可见)ViewGroup,会影响其他View,此时其它View会触发的动画。

设置布局动画时需要注意一下几点总结

  • CHANGE_APPEARING和CHANGE_DISAPPEARING布局动画设置必须使用PropertyValuesHolder所构造的动画才会有效果,否则不起作用。
  • PropertyValuesHolder生成动画时,”left”、”top”属性必须有,如果没有则没有动画效果。如果不需要改变这两个值,可以写成写为:
    PropertyValuesHolder left = PropertyValuesHolder.ofInt("left",0,0);
    PropertyValuesHolder top = PropertyValuesHolder.ofInt("top",0,0);
  • PropertyValuesHolder中所使用的ofInt,ofFloat中的参数值,第一个值和最后一个值必须相同,不然此属性所对应的的动画将被放弃,在此属性值上将不会有效果;同时不能所有的属性值都相同,否则也将无效(不能写成100,100,100)。
    PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top", 0, 100,0);
  • 设置动画duration等没有效果
//为了防止动画没有效果,把left,right,top,bottom的设置都加上

PropertyValuesHolder changeLeft =
        PropertyValuesHolder.ofInt("left", 0, 0);
PropertyValuesHolder changeTop =
        PropertyValuesHolder.ofInt("top", 0, 0);
PropertyValuesHolder changeRight =
        PropertyValuesHolder.ofInt("right", 0, 0);
PropertyValuesHolder changeBottom =
        PropertyValuesHolder.ofInt("bottom", 0, 0);

/**
 * 添加view时,其他受影响view动画效果
 */
PropertyValuesHolder aniChanApp = PropertyValuesHolder.ofFloat("rotation", 0, 50, 0);
ObjectAnimator changeApp = ObjectAnimator.ofPropertyValuesHolder(this,  changeLeft,changeTop,aniChanApp);

layoutTransition.setAnimator(LayoutTransition.CHANGE_APPEARING, changeApp);

/**
 * 删除view时其他受影响view动画效果
 */
PropertyValuesHolder aniChangeDis = PropertyValuesHolder.ofFloat("rotation", 0, 50, 0);
ObjectAnimator changeDis = ObjectAnimator.ofPropertyValuesHolder(this,changeLeft,changeTop,aniChangeDis);

layoutTransition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, changeDis);

image

相同代码如果仅仅不设置left和top动画,则不会有效果

image

Animation动画概述和执行原理
Android动画之补间动画TweenAnimation
Android动画之逐帧动画FrameAnimation
Android动画之插值器简介和系统默认插值器
Android动画之插值器Interpolator自定义
Android动画之视图动画的缺点和属性动画的引入
Android动画之ValueAnimator用法和自定义估值器
Android动画之ObjectAnimator实现补间动画和ObjectAnimator自定义属性
Android动画之ObjectAnimator中ofXX函数全解析-自定义Property,TypeConverter,TypeEvaluator
Android动画之AnimatorSet联合动画用法
Android动画之LayoutTransition布局动画
Android动画之共享元素动画
Android动画之ViewPropertyAnimator(专用于view的属性动画)
Android动画之Activity切换动画overridePendingTransition实现和Theme Xml方式实现
Android动画之ActivityOptionsCompat概述
Android动画之场景变换Transition动画的使用
Android动画之Transition和TransitionManager使用
Android动画之圆形揭露动画Circular Reveal
Android 动画之 LayoutAnimation 动画
Android动画之视图动画的缺点和属性动画的引入

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容