ConstraintSet详解

/ 0评 / 1

ConstraintSet

ConstraintSet允许我们动态的修改ConstraintLayout中某些控件的约束,并在约束改变的时候,为我们添加上动画效果

官方文档:https://developer.android.com/reference/androidx/constraintlayout/widget/ConstraintSet

个人理解

ConstraintSet对象中保存了ConstraintLayout里面所有控件的约束条件,通过constraintSet.applyTo()方法将修改以后的ConstraintSet应用到ConstraintLayout,可以实现批量更改约束条件,并通过TransitionManager.beginDelayedTransition()方法,让对这种切换加上动画效果,可以实现一些非常复杂的交互效果。

创建ConstraintSet

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

//用法1:从ConstraintLayout实例中获取约束集,最常用的用法
constraintSet.clone(constraintLayoutView) // constraintLayoutView必须是ConstraintLayout

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

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

常见问题一:java.lang.RuntimeException: All children of ConstraintLayout must have ids to use ConstraintSet

当我们使用方法一和方法二的时候,ConstraintLayout下的所有直接子控件必须要设置id

使用方法一

最简单的一种使用方法则是写两个一模一样的ConstraintLayout布局,然后获取两个ConstraintSet进行互相切换。

原始布局

//activity_demo1.xml
<androidx.constraintlayout.widget.ConstraintLayout>

    <TextView
        android:id="@+id/demo1_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="左上角快乐的小文本"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

期望变化后的布局

//activity_demo1_constraint
<androidx.constraintlayout.widget.ConstraintLayout>
    <TextView
        android:id="@+id/demo1_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="这个文件仅仅用来描述约束,加一个颜色,完全没影响呀"
        android:textColor="@color/teal_200"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/demo1_tvvvv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="所以多一个控件,改个属性,完全没影响呀"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

代码切换ConstraintSet

//初始ConstraintSet
initConstraintSet = ConstraintSet().apply {
    clone(bindingImpl.targetConstraintLayout)
}
//期望ConstraintSet
endConstraintSet = ConstraintSet().apply {
    clone(this@Demo1Activity, R.layout.activity_demo1_constraint)
}

bindingImpl.switchLayout.setOnClickListener {
    //加上动画切换
    TransitionManager.beginDelayedTransition(bindingImpl.targetConstraintLayout)
    endConstraintSet.applyTo(bindingImpl.targetConstraintLayout)

    Handler(Looper.getMainLooper()).postDelayed({
            //2秒以后还原位置
            androidx.transition.TransitionManager.beginDelayedTransition(bindingImpl.targetConstraintLayout)
            initConstraintSet.applyTo(bindingImpl.targetConstraintLayout)
        }, 2000
    )
}

ps:另一个布局文件,仅仅是用来描述约束的不同,只要期望更改的控件以及依赖的控件id都在就可以

使用方法二

比第一种方法使用复杂一点点,那就是使用ConstraintSet的Api来更改约束

比如更改大小、位置

//最开始的约束,从ConstraintLayout中获取
initConstraintSet = ConstraintSet().apply {
    clone(binding.targetConstraintLayout)
}

//在最开始的约束上面新增、修改约束
endConstraintSet = ConstraintSet().apply {
    clone(initConstraintSet)
}

binding.growUp.setOnClickListener {
    //给R.id.demo2_tv添加约束app:layout_constraintWidth_percent="0.8"
    endConstraintSet.constrainPercentWidth(R.id.demo2_tv, 0.8f)
    TransitionManager.beginDelayedTransition(binding.targetConstraintLayout)
    endConstraintSet.applyTo(binding.targetConstraintLayout)
}

binding.transition.setOnClickListener {
    //给R.id.demo2_tv 添加约束app:layout_constraintBottom_toTopOf="@+id/anchor"
    endConstraintSet.connect(
        R.id.demo2_tv,
        ConstraintSet.BOTTOM,
        R.id.anchor,
        ConstraintSet.TOP
    )
    //R.id.demo2_tv 去掉约束app:layout_constraintTop_toTopOf="parent"
    endConstraintSet.clear(R.id.demo2_tv, ConstraintSet.TOP)
    TransitionManager.beginDelayedTransition(binding.targetConstraintLayout)
    endConstraintSet.applyTo(binding.targetConstraintLayout)
}

这种方法需要我们熟悉已有代码的约束,然后在代码中动态添加、修改、删除约束,很多时候会遇到代码中设置的没有效果,可以把这些约束写到布局中预览的看看,比如endConstraintSet.constrainPercentWidth(R.id.demo2_tv, 0.8f)给控件R.id.demo2_tv设置宽度为父布局的80%,如果xml中没有设置如下三个约束,那么就不生效了

android:layout_width="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"

常用API

设置宽高

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

constrainWidth(int viewId, int width)
constrainHeight(int viewId, int height)
constrainPercentWidth(int viewId, float percent)
constrainPercentHeight(int viewId, float percent)

反过来,要重新设置回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还有:

constrainDefaultWidth/constrainDefaultHeight
constrainMaxWidth/constrainMaxHeight
constrainMinWidth/constrainMinHeight

设置约束

1、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)对齐

其中startSide和endSide用以下几种锚点常量表示:

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

当想设置app:layout_constraintTop_toTopOf="parent"的时候,endID可以设置为ConstraintSet.PARENT_ID

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

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

3、centerHorizontally() centerHorizontallyRtl()、centerVertically

基于center()的再次封装,让一个控件横向、垂直居中与某个控件,效果等同于添加了两个约束

//控件R.id.demo2_tv添加约束
//app:layout_constraintTop_toTopOf="@+id/anchor"
//app:layout_constraintBottom_toBottomOf="@+id/anchor"
endConstraintSet.centerVertically(R.id.demo2_tv, R.id.anchor)

其他API

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

//这些都没有动画效果,但是可以通过原始的ConstraintSet还原
setVisibility
setAlpha

//下面这些设置了,无法通过原始的ConstraintSet还原
rotation/rotationX/rotationY
scaleX/scaleY
transformPivotX/transformPivotY
translationX/translationY/translationZ
setElevation

一些类似于属性动画的API

setStringValue(String attributeName, String value)
setFloatValue(String attributeName, float value)
setIntValue(String attributeName, int value)
setColorValue(String attributeName, int value)

可以通过这些API来修改控件属性,attributeName为大写

//调用TextView的setTextColor,将TextView的文本颜色设置为红色
endConstraintSet.setColorValue(R.id.demo2_tv, "TextColor", Color.RED)

同样的,类似于setRotation方法,这些API调用以后,没有动画效果,并且不能通过原始的ConstraintSet还原

还有一些其他API

constrainCircle - 设置圆周约束
createHorizontalChain/createHorizontalChainRtl/createVerticalChain - 设置水平/垂直链
addToHorizontalChain/addToHorizontalChainRtl/addToVerticalChain - 加入水平/垂直链
removeFromHorizontalChain/removeFromHorizontalChainRtl/removeFromoVerticalChain - 从水平/垂直链移除
setHorizontalChainStyle/setVerticalChainStyle - 设置水平/垂直链的类型
setHorizontalBias/setVerticalBias - 设置水平/垂直偏移
setHorizontalWeight/setVerticalWeight - 设置水平/垂直权重
setMargin/setGoneMargin - 设置间隔和约束对象消失后的间隔
setGuidelineBegin/setGuidelineEnd/setGuidelinePercent - 设置参考线的相对位置
setDimensionRatio - 设置宽高比
clear - 清理约束

参考文档
https://enzowyf.github.io/constraintset.html

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注