ConstraintLayout的一些黑科技

本文使用的ConstraintLayout版本为

implementation "androidx.constraintlayout:constraintlayout:2.0.0-beta6"

角度

按照角度来约束一个View

  • app:layout_constraintCircle : 参照物的id
  • app:layout_constraintCircleAngle : 与参照物的角度
  • app:layout_constraintCircleRadius : 与参照物的距离

<androidx.constraintlayout.widget.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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_01"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_marginTop="200dp"
        android:background="@color/colorAccent"
        android:gravity="center"
        android:text="文本1"
        android:textColor="@android:color/white"
        android:textSize="18sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_02"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_marginTop="200dp"
        android:background="@color/colorPrimaryDark"
        android:gravity="center"
        android:text="文本2"
        android:textColor="@android:color/white"
        android:textSize="18sp"
        app:layout_constraintCircle="@id/tv_01"
        app:layout_constraintCircleAngle="45"
        app:layout_constraintCircleRadius="120dp" />

androidx.constraintlayout.widget.ConstraintLayout>

效果如下所示

ConstraintLayout的一些黑科技_第1张图片

constraintWidth_default

app:layout_constraintWidth_default只有在View的宽度定义为0dp的时候才生效,其余情况下设置这个属性是不起任何作用的。它有三个值:wrap、spread和percent。

  • wrap:相当于android:layout_width="wrap_content"
  • spread:相当于android:layout_width="match_parent"
  • percent:设置View的宽度为parent的比例值,比例值默认是100%,即宽度是match_parent。这个比例值通过属性app:layout_constraintWidth_percent设置。

我们来尝试一下


<androidx.constraintlayout.widget.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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_01"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_marginTop="200dp"
        android:background="@color/colorAccent"
        android:gravity="center"
        android:text="文本1"
        android:textColor="@android:color/white"
        android:textSize="18sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        app:layout_constraintWidth_default="percent"
        app:layout_constraintWidth_percent="0.6"
        android:text="这是一段文本这是一段文本这是一段文本这是一段文本这是一段文本这是一段文本这是一段文本"
        app:layout_constraintEnd_toEndOf="@+id/tv_01"
        app:layout_constraintStart_toStartOf="@+id/tv_01"
        app:layout_constraintTop_toBottomOf="@+id/tv_01" />

androidx.constraintlayout.widget.ConstraintLayout>

效果如下图所示

ConstraintLayout的一些黑科技_第2张图片

goneMargin

目标为gone的时候的margin

DimensionRatio约束

可以约束宽和高的比例,width或height必须有一个为0dp

constraintDimensionRatio="2:1"

如果width和height都是0dp,那么需要指定HW`

高度计算出来,宽度充满约束

constraintDimensionRatio="H,2:1"

宽度计算出来,高度充满约束

constraintDimensionRatio="W,2:1"

我们来尝试一下


<androidx.constraintlayout.widget.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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_01"
        android:layout_width="0dp"
        android:layout_height="120dp"
        android:layout_marginTop="200dp"
        android:background="@color/colorAccent"
        android:gravity="center"
        android:text="文本1"
        app:layout_constraintDimensionRatio="1.5"
        android:textColor="@android:color/white"
        android:textSize="18sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

androidx.constraintlayout.widget.ConstraintLayout>

效果如下图所示

ConstraintLayout的一些黑科技_第3张图片

百分比

需要对应方向的宽或者高为0dp
在屏幕适配中很有用,但是在约束布局中用的并不多,因为在约束布局中,实现百分比的方式不只有这一种。

constraintWidth_percent="0.3"

还可以用Guide_percent,需要和Guideline配合使用。

constraintGuide_percent="0.3"

Layer

可多个控件一起移动。
和 Group 类似,同样通过引⽤的⽅式来避免布局嵌套,可以为⼀组控件统⼀设置旋转/缩放/ 位移。

androicx.constraintLayout.heper.widget.Layer

Barrier

通过设置⼀组控件的某个⽅向的屏障,来 避免布局嵌套 。


<androidx.constraintlayout.widget.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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".BarrierActivity">

    <TextView
        android:id="@+id/text1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="dfafdfdfsdf"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/text2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="35dp"
        android:text="ghghjghjghghhhggh"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@id/text1" />

    <androidx.constraintlayout.widget.Barrier
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:barrierDirection="end"
        app:constraint_referenced_ids="text1,text2" />

androidx.constraintlayout.widget.ConstraintLayout>

显示效果如下
ConstraintLayout的一些黑科技_第4张图片
barrier永远在text1、text2最长的长度之后,实际显示中,barrier不会显示出来。

Placeholder

通过 setContentId 来将指定控件放到占位符中

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    findViewById<ImageView>(R.id.imageView).setOnClickListener(this)
    findViewById<ImageView>(R.id.imageView2).setOnClickListener(this)
    findViewById<ImageView>(R.id.imageView3).setOnClickListener(this)
}

override fun onClick(v: View) {
    val findViewById = findViewById<Placeholder>(R.id.placeholder)
    findViewById.setContentId(v!!.id)
}

这样,就可以将指定控件显示到PlaceHolder中,如果要有动画效果,我们还可以这样设置

override fun onClick(v: View) {
    val placeholder = findViewById<Placeholder>(R.id.placeholder)
    placeholder.setContentId(v!!.id)
    val constraintLayout = findViewById<ConstraintLayout>(R.id.constraintLayout)
    TransitionManager.beginDelayedTransition(constraintLayout, ChangeBounds().apply {
        interpolator = OvershootInterpolator()
        duration = 1000
    })
}

效果如下

这个暂时在constraintlayout:1.1.3能生效,constraintlayout:2.0.0-beta6没有反应,可能和beta版本有关系

自定义ConstraintHelper

渐变的动画,新建CircularRevealHelper类

class CircularRevealHelper(context: Context, attrs: AttributeSet) :
    ConstraintHelper(context, attrs) {

    //当回调,表示我们所有控件的布局完成了
    override fun updatePostLayout(container: ConstraintLayout) {
        super.updatePostLayout(container)

        //可以在这里对View播放动画
        referencedIds.forEach {
            val view = container.getViewById(it)
            val radius = hypot(view.width.toDouble(),view.height.toDouble()).toInt()

            ViewAnimationUtils.createCircularReveal(view,0,0,0F,radius.toFloat())
                .setDuration(2000L)
                .start()
        }
    }
}

在xml中进行声明,constraint_referenced_ids关联需要的ImageView

<com.heiko.constraintlayouttest.CircularRevealHelper
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:constraint_referenced_ids="imageView,imageView2,imageView3" />

效果如下图所示
ConstraintLayout的一些黑科技_第5张图片

代码中动态改变约束

class Linear(context: Context, attrs: AttributeSet) : VirtualLayout(context, attrs) {
    private val constraintSet: ConstraintSet by lazy {
        ConstraintSet().apply {
            isForceId = false
        }
    }

    //在布局之前
    override fun updatePreLayout(container: ConstraintLayout?) {
        super.updatePreLayout(container)

        constraintSet.clone(container)

        val viewIds = referencedIds

        for (i in 1 until mCount) {
            val current = viewIds[i]
            val before = viewIds[i - 1]

            constraintSet.connect(current, ConstraintSet.START, before, ConstraintSet.START)
            constraintSet.connect(current, ConstraintSet.TOP, before, ConstraintSet.BOTTOM)

            constraintSet.applyTo(container)
        }
    }
}

但这个也不用自己去写,已经有相应的组件了。

<androidx.constraintlayout.helper.widget.Flow
        android:id="@+id/flow"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:orientation="vertical"
        app:flow_wrapMode="chain"
        app:flow_verticalGap="16dp"
        app:flow_horizontalGap="16dp"
        app:constraint_referenced_ids="image,image2,image3,image4"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

Flow 就是类似于LinearLayout

效果如图所示 :
ConstraintLayout的一些黑科技_第6张图片
同时,还可以设置flow_wrapMode,会有不同的效果。

动态替换整个布局

对于一些经布局排布方式有差异,要动态替换的情况,非常有用。
首先定义初始化状态下的activity_constraint_1.xml


<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:onClick="onClick">

    <ImageView
        android:id="@+id/image_01"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_01"
        app:layout_constraintBottom_toTopOf="@+id/image_02"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/image_02"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_02"
        app:layout_constraintBottom_toTopOf="@+id/image_03"
        app:layout_constraintEnd_toEndOf="@+id/image_01"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="@+id/image_01"
        app:layout_constraintTop_toBottomOf="@+id/image_01" />

    <ImageView
        android:id="@+id/image_03"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_03"
        app:layout_constraintBottom_toTopOf="@+id/image_04"
        app:layout_constraintEnd_toEndOf="@+id/image_02"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="@+id/image_02"
        app:layout_constraintTop_toBottomOf="@+id/image_02" />

    <ImageView
        android:id="@+id/image_04"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_04"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="@+id/image_03"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="@+id/image_03"
        app:layout_constraintTop_toBottomOf="@+id/image_03" />

androidx.constraintlayout.widget.ConstraintLayout>

接着定义替换后的activity_constraint_2.xml


<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:onClick="onClick">

    <ImageView
        android:id="@+id/image_01"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_01"
        app:layout_constraintBottom_toTopOf="@+id/image_02"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/image_02"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_02"
        app:layout_constraintBottom_toTopOf="@+id/image_03"
        app:layout_constraintEnd_toEndOf="@+id/image_01"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="@+id/image_01"
        app:layout_constraintTop_toBottomOf="@+id/image_01" />

    <ImageView
        android:id="@+id/image_03"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_03"
        app:layout_constraintBottom_toTopOf="@+id/image_04"
        app:layout_constraintEnd_toEndOf="@+id/image_02"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="@+id/image_02"
        app:layout_constraintTop_toBottomOf="@+id/image_02" />

    <ImageView
        android:id="@+id/image_04"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_04"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="@+id/image_03"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="@+id/image_03"
        app:layout_constraintTop_toBottomOf="@+id/image_03" />

androidx.constraintlayout.widget.ConstraintLayout>

然后,在代码中,实现其替换效果

fun onCLick(view:View){
    val constraintSet = ConstraintSet().apply { 
        isForceId = false //控件没有Id的时候也不会报错
		clone(this@ConstraintSetActivity, R.id.activity_constraint_2)
    }
	//使用过渡动画
    TransitionManager.beginDelayedTransition(constraintLayout, ChangeBounds().apply {
            interpolator = AccelerateDecelerateInterpolator()
            duration = 1000
        })
    constraintSet.applyTo(constraintLayout)
}

效果如下所示:
ConstraintLayout的一些黑科技_第7张图片

减少布局嵌套的作用 :
除了提升性能以外,还可以更加方便地做过渡动画,可以让view之间,更加方便地引用,约束。

示例代码下载

文中示例代码下载

其他

更多ConstraintLayout特性详见
ConstraintLayout 约束布局 2.0
ConstraintLayout,看完一篇真的就够了么?
文中图标源自 阿里巴巴图库

你可能感兴趣的:(Android)