ConstraintLayout的横空出世,改变了LinearLayoutRelativeLayout一统天下的局面,同时也让我们的布局文件变得清爽,嵌套少了,渲染效率也提高了不少。ConstraintLayout带来的不仅仅是写布局文件上的变化,以往我们在代码中动态的修改布局中控件的大小、位置以及与其他控件的相对关系时,都需要使用到LayoutParams,不同的布局有对应的不同的LayoutParams的子类,使用起来十分繁琐。所以到了ConstraintLayout时代,与之搭配的就是ConstraintSet

创建和初始化ConstraintSet

使用ConstraintSet要先创建对象,并通过clone()获取约束集,clone()有三种用法:

val constraintSet = ConstraintSet() //创建对象

//用法1:从ConstraintLayout实例中获取约束集,最常用的用法
constraintSet.clone(mainLayout) // 假设mainLayout是一个ConstraintLayout实例

//用法2:从布局中获取约束集
constraintSet.clone(context, R.layout.my_layout) // my_layout.xml的根布局必须是ConstraintLayout

//用法3:从其他约束集中获取约束集
constraintSet.clone(otherConstraintSet)

修改尺寸

ConstraintSet提供了一组方法用于设置控件的尺寸:

假设有个button原本的长宽都是wrap_content,要动态改变大小:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val constraintSet = ConstraintSet().apply {
            clone(main_layout)
            constrainWidth(R.id.button, dip2px(200f))
            constrainHeight(R.id.button, dip2px(80f))
        }

        button.setOnClickListener {
            TransitionManager.beginDelayedTransition(main_layout)
            //应用修改
            constraintSet.applyTo(main_layout)
        }
    }
}

约束被修改后,要通过ConstraintSet.applyTo()来应用修改

那么反过来,要重新设置回wrap_content呢:

constrainWidth(R.id.button, ConstraintSet.WRAP_CONTENT)
constrainHeight(R.id.button, ConstraintSet.WRAP_CONTENT)

这里要注意,是ConstraintSet.WRAP_CONTENT,不是以前的LayoutParams.WRAP_CONTENT !!!

同样的还有:ConstraintSet.MATCH_CONSTRAINT,表示根据约束条件自动适应至最大,等于与android:layout_width="0dp"

其他的跟尺寸有关的API还有:

用来设置一些默认、最大最小尺寸,不常用,这里就不展开了。

修改约束

ConstraintSet最重要的用途就是用来修改约束,由于ConstraintLayout中,子控件的位置都是通过与父布局或者其他子控件的约束关系决定的,因此修改约束也等于调整了控件的位置。ConstraintLayout里面提供的所有跟约束有关的内容,ConstraintSet都有对应的API,其中比较重要的是:

connect()

最基本的API,用于建立两个控件直接的约束关系:

public void connect(int startID, int startSide, int endID, int endSide, int margin)
public void connect(int startID, int startSide, int endID, int endSide)

简单的理解:控件a(startID)的右边(startSide),要相对于控件b(endID)的左边(endSide)对齐

其中startSideendSide用以下几种锚点常量表示:

ConstraintSet.TOP
ConstraintSet.BOTTOM
ConstraintSet.LEFT
ConstraintSet.RIGHT
ConstraintSet.START
ConstraintSet.END
ConstraintSet.BASELINE //一般用于TextView

例如,布局中button_1原本位于左上角,要移到button_2的正上方,并与button_2左右对齐:

代码如下:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val constraintSet = ConstraintSet().apply {
            clone(main_layout)
            //button_1原本位于父布局左上角,要分别清理两处现有的约束
            clear(R.id.button_1, ConstraintSet.START)
            clear(R.id.button_1, ConstraintSet.TOP)
            
            //重新建立约束
            //让button_1低边至于button_2顶边,并有4dp的间隔
            connect(R.id.button_1, ConstraintSet.BOTTOM, R.id.button_2, ConstraintSet.TOP, dip2px(4f))
            //让button_1右边与button_2右边对齐,无需间隔
            connect(R.id.button_1, ConstraintSet.START, R.id.button_2, ConstraintSet.START)
            //让button_1左边与button_2左边对齐,无需间隔
            connect(R.id.button_1, ConstraintSet.END, R.id.button_2, ConstraintSet.END)
        }

        button_2.setOnClickListener {
            TransitionManager.beginDelayedTransition(main_layout)
            //应用修改
            constraintSet.applyTo(main_layout)
        }
    }
    
}

center()

基于connect封装,让某一控件(centerID)置于另外两个控件(firstID & secondID)的中间:

public void center(int centerID, int firstID, int firstSide, int firstMargin, int secondID, int secondSide, int secondMargin, float bias)

其中:

例如:

//让button_2水平置于另外button_1和button_3的正中间
constraintSet.center(R.id.button_2, R.id.button_1, ConstraintSet.END, R.id.button_3, ConstraintSet.START 0 0 0)

centerHorizontally() && centerHorizontallyRtl()

基于center()的再次封装,让两个控件水平居中对齐,其中centerHorizontallyRtl用于Rtl布局

public void centerHorizontally(int viewId, int toView)
public void centerHorizontallyRtl(int viewId, int toView)

例如:


//让button_2与button_1水平居中对齐
constraintSet.centerHorizontallyRtl(R.id.button_2, R.id.button_1)

//让button_2水平居中于父布局
constraintSet.centerHorizontallyRtl(R.id.button_2, ConstraintSet.PARENT_ID)

Rtl表示需要兼容某些国家和地区(主要是阿拉伯)从右至左的书写阅读习惯,目前在Android Studio3中,在写xml布局文件的时候,所有用到left和right的地方,都会提示你改成start和end,就是Rtl的体现。

centerVertically()

同样基于center()的再次封装,让两个控件垂直居中对齐

public void centerVertically(int viewId, int toView)

例如:


//让button_2与button_1垂直居中对齐
constraintSet.centerVertically(R.id.button_2, R.id.button_1)

//让button_2垂直居中于父布局
constraintSet.centerVertically(R.id.button_2, ConstraintSet.PARENT_ID)

others

以上是一些基本的约束,除此之外,还有很多其他的很约束有关的API比如:

ConstraintLayout能做的一切,都在这里了。

切换布局

是不是觉得上面写太多代码了麻烦,那我们做两个布局切换好了:

activity_main.xml:

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button_right"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="我在右边"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/button_left"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button_left"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="我在左边"
        app:layout_constraintEnd_toStartOf="@+id/button_right"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>

activity_main_mirror.xml:

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button_right"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="我本来在右边"
        app:layout_constraintEnd_toStartOf="@+id/button_left"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button_left"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="我本来在左边"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/button_right"
        app:layout_constraintTop_toTopOf="parent"
         />
</android.support.constraint.ConstraintLayout>

MainActivity.kt

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val constraintSet1 = ConstraintSet().apply {
            clone(main_layout)
        }

        val constraintSet2 = ConstraintSet().apply {
            clone(this@MainActivity, R.layout.activity_main_mirror)
        }

        button_left.setOnClickListener {
            TransitionManager.beginDelayedTransition(main_layout)
            constraintSet2.applyTo(main_layout)
            button_left.text = "我本来在左边"
            button_right.text = "我本来在右边"
        }

        button_right.setOnClickListener {
            TransitionManager.beginDelayedTransition(main_layout)
            constraintSet1.applyTo(main_layout)
            button_left.text = "我在左边"
            button_right.text = "我在右边"
        }
    }
}

是不是很简单,效果如下:

修改控件属性

ConstraintSet不仅能修改尺寸、位置约束,还能做LayoutParams不能做的事情 – 修改控件的各种属性,是不是很强大:

可能有人会说,控件本身就有设置这些属性的方法,为什么要多此一举,那你就too young too naive了。

ConstraintSet做了那么多事情,只是为了一个目的:动画。让我们感受一下: