本文使用的ConstraintLayout版本为
implementation "androidx.constraintlayout:constraintlayout:2.0.0-beta6"
按照角度来约束一个View
app:layout_constraintCircle
: 参照物的idapp: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>
app:layout_constraintWidth_default
只有在View的宽度定义为0dp的时候才生效,其余情况下设置这个属性是不起任何作用的。它有三个值:wrap、spread和percent。
android:layout_width="wrap_content"
android:layout_width="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>
目标为gone的时候的margin
可以约束宽和高的比例,width或height必须有一个为0dp
constraintDimensionRatio="2:1"
如果width和height都是0dp,那么需要指定H或
W`
高度计算出来,宽度充满约束
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>
需要对应方向的宽或者高为0dp
在屏幕适配中很有用,但是在约束布局中用的并不多,因为在约束布局中,实现百分比的方式不只有这一种。
constraintWidth_percent="0.3"
还可以用Guide_percent
,需要和Guideline配合使用。
constraintGuide_percent="0.3"
可多个控件一起移动。
和 Group 类似,同样通过引⽤的⽅式来避免布局嵌套,可以为⼀组控件统⼀设置旋转/缩放/ 位移。
androicx.constraintLayout.heper.widget.Layer
通过设置⼀组控件的某个⽅向的屏障,来 避免布局嵌套 。
<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>
显示效果如下
barrier永远在text1、text2最长的长度之后,实际显示中,barrier不会显示出来。
通过 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版本有关系
渐变的动画,新建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" />
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
效果如图所示 :
同时,还可以设置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)
}
减少布局嵌套的作用 :
除了提升性能以外,还可以更加方便地做过渡动画,可以让view之间,更加方便地引用,约束。
文中示例代码下载
更多ConstraintLayout特性详见
ConstraintLayout 约束布局 2.0
ConstraintLayout,看完一篇真的就够了么?
文中图标源自 阿里巴巴图库