Android开发经验谈

利用ConstraintLayout快速实现布局改变的动画

2019-01-08  本文已影响16人  大盗贼霍真普洛兹姥爷

利用ConstraintLayout快速实现布局改变的动画

问题

有如下一个动画,当点击按钮之后需要让微信和朋友圈的图标平移淡出,再次点击之后微信和朋友圈的图标平移消失。怎么实现呢?要计算出图标应该出现的位置,然后写一堆动画让其移动到指定位置么?还是有更方便的方法?


a.gif

ConstraintSet简介

ConstraintSet可以让你方便地通过代码来设置ConstraintLayout的约束。可以利用ConstraintSet创建并保存约束,将这些约束传入一个已经存在的ConstraintLayout。可以利用下面三种方法来创建一个ConstraintSet。

c = new ConstraintSet(); c.connect(....);
c.clone(context, R.layout.layout1);
c.clone(clayout);

当使用clone的方法来获取ConstaintSet 的时候,需要注意布局文件中所有的view必须有一个id,这个可以理解,因为其要保存所有布局文件中view的约束,如果有些view没有设置id的话,就无法获取这个view的约束了。

public void clone(ConstraintLayout constraintLayout) {
...
    int id = view.getId();
    if (id == -1) {
    throw new RuntimeException("All children of     ConstraintLayout must have ids to use ConstraintSet");
...
}

ConstraintSet除了被用来设置约束,其还有一个非常酷的用处,可以利用它来方便地实现布局变化的动画。

TransitionManager

实际上在这个页面中ConstraintSet,除了介绍了ConstraintSet之外,还给出了一段代码,这段代码正是利用ConstraintSet来执行的动画。可以看到,实际上处理动画的类是TransitionManager

如果你的API版本在19及以上,那么可以直接使用TransitionManager,否则需要导入support包中的transition。

implementation 'com.android.support:transition:xx'

调用TransitionManager的beginDelayedTransition方法,即可执行动画。TransitionManager中的默认动画效果为AutoTransition。可以看到,默认的动画效果为顺序执行淡出、布局变化、淡入三种动画。如果有需要的话,可以自定义Transition,通过beginDelayedTransition方法传入来使用。

public class AutoTransition extends TransitionSet {
    ...
    private void init() {
        setOrdering(ORDERING_SEQUENTIAL);
        addTransition(new Fade(Fade.OUT)).
                addTransition(new ChangeBounds()).
                addTransition(new Fade(Fade.IN));
    }
}

注意一点,TransitionSet是继承自Transition的,因此可以通过addTransition来添加一个自定义TransitionSet的实例的。可以方便实现一些复杂的动画效果。

比如默认的动画效果为顺序执行淡出、布局变化、淡入三种动画。如果我要改成先执行淡出,然后同时执行布局变化和淡入怎么办。先创建一个同时执行布局变化和淡入的SecondStepTransition。将淡出和SecondStepTransition添加到CustomTransition中顺序执行。

public class CustomTransition extends TransitionSet {
    public CustomTransition() {
        setOrdering(ORDERING_SEQUENTIAL);
        addTransition(new Fade(Fade.OUT)).
                addTransition(new SecondStepTransition());
                
    }
}
public class SecondStepTransition extends TransitionSet {
    public SecondStepTransition() {
        setOrdering(ORDERING_TOGETHER);
        addTransition(new ChangeBounds()).
                addTransition(new Fade(Fade.IN));
    }
}

实现开头的动画

前面铺垫完了,可以开始利用前面的知识来实现开头的动画了。

观察前面的动画的第一个场景,可以看到微信和朋友圈图标有一个淡入并且从右侧平移的效果,普通分享按钮有一个淡出的效果,评论按钮有一个像左侧平移的效果。

可以认为平移效果实际上就是布局发生了变化,而淡出淡入效果则是可见状态发生了变化。那么可以先写出一个布局文件,然后对该布局文件中的各个元素修改其布局及可见状态并用动画来处理。如果自己来做这件事,会非常复杂。所幸有ConstraintSet和TransitionManager。

ConstraintSet用来保存前后两种状态不同的布局参数,TransitionManager则用来利用不同的布局参数来执行动画。

首先,先写一个布局文件frame_1,在这个文件中,微信和朋友圈图标是隐藏的。这个布局文件会一开始就被加载,首先展示出来。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/constraint_container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingTop="10dp"
    android:paddingBottom="10dp">

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="123456"
        app:layout_constraintLeft_toLeftOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="111" />

    <ImageView
        android:id="@+id/more"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:layout_marginRight="15dp"
        android:src="@mipmap/video_list_more"
        app:layout_constraintRight_toRightOf="parent"/>

    <ImageView
        android:id="@+id/share"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:layout_marginRight="20dp"
        android:src="@mipmap/share_normal"
        app:layout_constraintRight_toLeftOf="@id/more"/>

    <ImageView
        android:id="@+id/comment"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:layout_marginRight="20dp"
        android:src="@mipmap/comment_normal"
        app:layout_constraintRight_toLeftOf="@id/share"/>

    <ImageView
        android:id="@+id/share_wechat"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:src="@mipmap/share_wechat"
        app:layout_constraintRight_toLeftOf="@id/more"
        android:visibility="invisible"/>

    <ImageView
        android:id="@+id/share_moments"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:src="@mipmap/share_moments"
        app:layout_constraintRight_toLeftOf="@id/share_wechat"
        android:visibility="invisible"/>
</android.support.constraint.ConstraintLayout>

然后再写一个布局文件frame_2,这个布局文件中微信和朋友圈图标是可见的,是动画切换后的效果。这个布局文件不会被加载使用,而是用来提供布局参数的。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/constraint_container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingTop="10dp"
    android:paddingBottom="10dp">

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="123456"
        app:layout_constraintLeft_toLeftOf="parent" />

    <ImageView
        android:id="@+id/more"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:layout_marginRight="15dp"
        android:src="@mipmap/video_list_more"
        app:layout_constraintRight_toRightOf="parent"/>

    <ImageView
        android:id="@+id/share_wechat"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:layout_marginRight="20dp"
        android:src="@mipmap/share_wechat"
        app:layout_constraintRight_toLeftOf="@id/more"/>

    <ImageView
        android:id="@+id/share_moments"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:layout_marginRight="20dp"
        android:src="@mipmap/share_moments"
        app:layout_constraintRight_toLeftOf="@id/share_wechat"/>

    <ImageView
        android:id="@+id/comment"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:layout_marginRight="20dp"
        android:src="@mipmap/comment_normal"
        app:layout_constraintRight_toLeftOf="@id/share_moments"/>

    <ImageView
        android:id="@+id/share"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:layout_marginRight="20dp"
        android:src="@mipmap/share_normal"
        android:visibility="gone"/>
</android.support.constraint.ConstraintLayout>

对比两个布局文件,可以看到,主要的区别在于,微信和朋友圈的图标的约束条件、可见状态和marginRight发生了变化。

两个布局文件创建完成之后,再创建一个布局文件activity_main作为frame_1的parent

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="40dp"
        android:layout_marginBottom="100dp"
        android:text="click me"
        android:layout_gravity="center_horizontal|bottom"/>
</FrameLayout>

实际上,可以将frame_1中的代码直接写到activity_main中,但是为了方便对比阅读,还是将frame_1单独拆出来写了。

下面java代码则实现了布局切换的效果。


public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private ConstraintLayout constraintContainer; //frame_1中的最外层ConstraintLayout
    private Button button; //布局切换按钮,点击执行切换动画
    private ConstraintSet constraintSet1 = new ConstraintSet(); //保存frame_1中的布局参数
    private ConstraintSet constraintSet2 = new ConstraintSet(); //保存frame_2中的布局参数

    private TransitionSet customTransition; //transition动画
    private boolean isFrame1; //当前使用的是否是frame_1中的布局参数

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

        FrameLayout container = findViewById(R.id.container);
        button = findViewById(R.id.button);
        button.setOnClickListener(this);

        LayoutInflater.from(this).inflate(R.layout.frame_1, container);
        constraintContainer = findViewById(R.id.constraint_container);
        customTransition = new CustomTransition();
        //读取frame_1中的布局参数
        constraintSet1.clone(constraintContainer);
        //读取frame_2中的布局参数
        constraintSet2.clone(this, R.layout.frame_2);
        isFrame1 = true;
    }

    @Override
    public void onClick(View v) {
        //设置执行动画的ViewGroup以及transition
    TransitionManager.beginDelayedTransition(constraintContainer, customTransition);
        if (isFrame1) {
            //将frame_2中的参数应用到当前的constaintlayout中
            constraintSet2.applyTo(constraintContainer);
            isFrame1 = false;
        } else {
            //将frame_1中的参数应用到当前的constaintlayout中
            constraintSet1.applyTo(constraintContainer);
            isFrame1 = true;
        }
    }
}

由于动画效果是同时执行的,而AutoTransition是顺序执行,因此需要创建一个新的TransitionSet。

public class CustomTransition extends TransitionSet {
    public CustomTransition() {
        setOrdering(ORDERING_TOGETHER);
        this.addTransition(new Fade(Fade.OUT))
                .addTransition(new ChangeBounds())
                .addTransition(new Fade(Fade.IN));
    }
}

切记,ConstraintLayout的动画效果不是通过切换加载两个布局文件来实现的,而是将两个布局文件的布局参数保存下来,然后设置给ConstraintLayout来实现的。

以后如果遇到类似的发生布局改变的动画,不妨考虑下这个方法,更加优雅和方便。

上一篇 下一篇

猜你喜欢

热点阅读