1. Chain Style
Chain Style可以将水平或垂直方向的多个VIEW串起来,进行整体的控制.
<?xml version="1.0" encoding="utf-8"?>
<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"
android:fillViewport="true"
>
<View
android:id="@+id/v1"
android:layout_width="100dp"
android:layout_height="100dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:background="@color/purple_200"
android:layout_marginStart="40dp"
android:layout_marginTop="40dp"
/>
<TextView
android:id="@+id/tv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/holo_green_light"
app:layout_constraintStart_toEndOf="@id/v1"
android:layout_marginStart="12dp"
android:text="111111"
app:layout_constraintTop_toTopOf="@id/v1"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintBottom_toTopOf="@id/tv2"
/>
<TextView
android:id="@+id/tv2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/holo_blue_bright"
app:layout_constraintStart_toEndOf="@id/v1"
app:layout_constraintTop_toBottomOf="@id/tv1"
app:layout_constraintBottom_toBottomOf="@id/v1"
android:layout_marginStart="12dp"
android:layout_marginTop="4dp"
android:text="22222222222222"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
- tv1 和 tv2 垂直排列,当做一个整体,相对于左边的v1垂直居中.
- 即使tv1或tv2有1个被隐藏/gone, 剩下的Viwe依然相对于v1垂直居中.
- 可以使用bias来调节整体的位置,即使View被隐藏也生效.
2. layout_constraintWidth_percent
percent可以控制View的宽度/高度占外部ConstraintLayout的百分比.
- 0dp
- default="percent"
- percent="0.X"
3. Barrier
Barrier类似GuideLine,但是更加灵活,其引用一组控件id,以尺寸最大的控件的边缘作为自身的位置.
4. Layer
Layer用于给一组View设置公共的背景,或者控制1组View同时执行1个动画.
- alpha动画针对的是Layer自身,不是其控制的View
- rotate, translate, scale 针对的是其控制的View
<?xml version="1.0" encoding="utf-8"?>
<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=".constraintlayout.ConstraintLayoutActivity">
<androidx.constraintlayout.helper.widget.Layer
android:id="@+id/layer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:constraint_referenced_ids="v1,v2,v3"
android:background="@color/purple_500"
/>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/bt1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:text="展示Layer动画"
android:onClick="exeLayerAnim"
/>
<TextView
android:id="@+id/v1"
android:layout_width="100dp"
android:layout_height="100dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_margin="20dp"
android:text="111"
/>
<TextView
android:id="@+id/v2"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_margin="20dp"
app:layout_constraintTop_toBottomOf="@id/v1"
app:layout_constraintStart_toStartOf="parent"
android:text="222"
/>
<TextView
android:id="@+id/v3"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_margin="20dp"
app:layout_constraintTop_toBottomOf="@id/v1"
app:layout_constraintStart_toEndOf="@id/v2"
android:text="333"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
Layer为多个View设置共同的背景:
Layer让一组View同时执行1个动画:
fun exeLayerAnim(view: View) {
val layer: Layer = findViewById(R.id.layer)
//alpha是针对Layer自身的,不是针对其控制的View集合
val anim = ValueAnimator.ofFloat(0F, 1.0F)
anim.duration = 3000
anim.addUpdateListener {
layer.alpha = it.animatedValue as Float
}
anim.start()
//rotate动画是针对其控制的View集合
//旋转过程中,其背景不会跟随旋转
/*val anim = ValueAnimator.ofFloat(0F, 360.0F)
anim.duration = 3000
anim.addUpdateListener {
layer.rotation = it.animatedValue as Float
}
anim.start()*/
//translation动画是针对其控制的View集合
//移动过程中,其背景不会跟随移动
/*val anim = ValueAnimator.ofFloat(0F, 360.0F, 0F)
anim.duration = 3000
anim.addUpdateListener {
layer.translationX = it.animatedValue as Float
}
anim.start()*/
//scale动画是针对其控制的View集合
//缩放过程中,其背景不会跟随缩放
/*val anim = ValueAnimator.ofFloat(0F, 2.0F, 1.0F)
anim.duration = 3000
anim.addUpdateListener {
layer.scaleX = it.animatedValue as Float
layer.scaleY = it.animatedValue as Float
}
anim.start()*/
}
5. ImageFilterView
ImageFilterView,可以方便的实现圆角图片/圆角背景色,在任意容器中均可使用,不限于ConstraintLayout.
- 可以使用两个属性来设置图片资源的圆角,分别是roundPercent和round
- roundPercent接受的值类型是0-1的小数,根据数值的大小会使图片在方形和圆形之间按比例过度,round=可以设置具体圆角的大小
6. 自定义 ConstraintHelper
ConstraintHelper可以引用页面中多个控件,可以很方便的为控件设置统一的行为
- Helper提供了view的onLayout()/onMeasure()等流程方法执行前后的回调
- 可以为引用的控件设置动画
- 多个ConstraintHelper可以引用同一个View控件
普通动画
class CustomConstraintHelper1 @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintHelper(context, attrs, defStyleAttr) {
@JvmOverloads
fun f1(p1:Int = 1, p2:String?, p3:Long = 100){
}
override fun updatePostLayout(container: ConstraintLayout?) {
getViews(container).forEach {
animate(it)
}
}
private fun animate(view: View) {
val alpha: ValueAnimator = ObjectAnimator.ofFloat(view, "alpha", 0.2F, 1.0F)
alpha.duration = 3000
val scale: ValueAnimator = ObjectAnimator.ofFloat(view, "scaleX", 2.0F, 1.0F)
scale.duration = 3000
alpha.start()
scale.start()
}
}
圆形揭露动画
使用ViewAnimationUtils的createCircularReveal函数,可以很方便的创建圆形揭露动画:
class CustomConstraintHelper1 @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ConstraintHelper(context, attrs, defStyleAttr) {
override fun updatePostLayout(container: ConstraintLayout?) {
getViews(container).forEach {
ViewAnimationUtils.createCircularReveal(
it, it.width / 2, it.height / 2, 0F, Math.hypot(
(it.width / 2).toDouble(), (it.height / 2).toDouble()
).toFloat()
).apply {
duration = 3000
start()
}
}
}
}
Fling动画/View从屏幕四周回到其初始位置
class CustomConstraintHelper2 @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ConstraintHelper(context, attrs, defStyleAttr) {
override fun updatePostLayout(container: ConstraintLayout?) {
val centerX = (left + right) / 2
val centerY = (top + bottom) / 2
getViews(container).forEach {
val translationX = if ((it.left + it.right) / 2 > centerX) 300F else -300F
val translationY = if ((it.top + it.bottom) / 2 > centerY) 300F else -300F
val animX = ObjectAnimator.ofFloat(it, "translationX", translationX, 0F)
val animY = ObjectAnimator.ofFloat(it, "translationY", translationY, 0F)
val anim: AnimatorSet = AnimatorSet()
anim.duration = 1000
anim.playTogether(animX, animY)
anim.start()
}
}
}
引入ConstraintHelper前
引入ConstraintHelper后
7. ConstraintLayoutStates
ConstraintLayoutStates用于为Activity/Fragment切换不同的ConstraintLayout布局.
- 不同的ConstraintLayout xml布局中要包含相同的View
- 只有visibility,位置等属性可以不同
- xml中创建ConstraintLayout实例的状态描述文件
R.xml.states_for_constraintlayout
<?xml version="1.0" encoding="utf-8"?>
<ConstraintLayoutStates xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<State
android:id="@+id/state1"
app:constraints="@layout/activity_constraint_layout_states_1" />
<State
android:id="@+id/state2"
app:constraints="@layout/activity_constraint_layout_states_2" />
</ConstraintLayoutStates>
activity_constraint_layout_states_1.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/root"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".constraintlayout.ConstraintLayoutStatesActivity">
<View
android:id="@+id/v1"
android:layout_width="100dp"
android:layout_height="100dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:background="@color/purple_500"
/>
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tv"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:text="State"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
activity_constraint_layout_states_2.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/root"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".constraintlayout.ConstraintLayoutStatesActivity">
<View
android:id="@+id/v1"
android:layout_width="100dp"
android:layout_height="100dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:background="@color/purple_500"
/>
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tv"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:text="State"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
Activity
class ConstraintLayoutStatesActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_constraint_layout_states_1)
val root: ConstraintLayout = findViewById(R.id.root)
val v1: View = findViewById(R.id.v1)
//1: 为ConstraintLayout实例关联 状态配置文件
root.loadLayoutDescription(R.xml.states_for_constraintlayout)
root.postDelayed({
v1.setBackgroundColor(getColor(R.color.teal_200))
//2: 设置ConstraintLayout实例当前状态/布局文件
root.setState(R.id.state2, 0, 0)
}, 4000)
}
}
8. ConstraintSet
ConstraintSet用于通过代码动态修改ConstraintLayout中View的约束条件,也可以切换Activity/Fragment的布局.
class ConstraintSetActivity : AppCompatActivity() {
lateinit var root: ConstraintLayout
lateinit var iv1: View
lateinit var iv2: View
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_constraint_set1)
root = findViewById(R.id.parent)
iv1 = findViewById(R.id.iv1)
iv2 = findViewById(R.id.iv2)
iv1.setOnClickListener {
changePosition()
}
iv2.setOnClickListener {
changeLayout2()
}
}
//修改ConstraintLayout中View的约束条件
private fun changePosition() {
val constraintSet: ConstraintSet = ConstraintSet()
constraintSet.clone(root)
constraintSet.centerHorizontally(R.id.iv1, ConstraintSet.PARENT_ID)
constraintSet.centerVertically(R.id.iv1, ConstraintSet.PARENT_ID)
constraintSet.connect(R.id.iv2, ConstraintSet.TOP, R.id.iv1, ConstraintSet.BOTTOM)
constraintSet.connect(
R.id.iv2,
ConstraintSet.BOTTOM,
ConstraintSet.PARENT_ID,
ConstraintSet.BOTTOM
)
constraintSet.setVerticalBias(R.id.iv2, 0.2F)
//TransitionManager.beginDelayedTransition(root)
val transition: AutoTransition = AutoTransition()
transition.duration = 4000
TransitionManager.beginDelayedTransition(root, transition)
constraintSet.applyTo(root)
(iv1.layoutParams as MarginLayoutParams).leftMargin = 0
}
//为当前Activity切换布局xml
private fun changeLayout2() {
val constraintSet = ConstraintSet()
constraintSet.clone(this, R.layout.activity_constraint_set2)
TransitionManager.beginDelayedTransition(root)
constraintSet.applyTo(root)
}
}
修改ConstraintLayout中View的约束条件
- 获取根布局的ConstraintLayout实例
- val root:ConstraintLayout = findViewById(id)
- 创建ConstraintSet实例,并复制根布局的约束
- val constraintSet : ConstraintSet()
- constraintSet.clone(root)
- 修改其中View的约束条件
- 如果要实现View在ConstraintLayout中水平居中,要注意布局xml中不要用start/end,要用"layout_constraintLeft_toLeftOf".
- 如果要修改View的margin值,要在4结束后执行 : (iv1.layoutParams as MarginLayoutParams).leftMargin = 0
- 将修改结果应用到根布局
- constraintSet.applyTo(root)
- 提前执行 TransitionManager.beginDelayedTransition(root) 可以为变化添加动画,也可以自定义Transition的属性.
切换Activity/Fragment的布局
- 获取根布局的ConstraintLayout实例
- val root:ConstraintLayout = findViewById(id)
- 创建ConstraintSet实例,并复制待切换的布局约束
- val constraintSet : ConstraintSet()
- constraintSet.clone(Context context, int targetLayoutId)
- 将修改结果应用到根布局
- constraintSet.applyTo(root)
注意:
- 如果要切换xml布局文件,则多个布局文件需要有相同的View,相同id的View之间才能进行切换.
- TransitionManager.beginDelayedTransition(root),可以用于任意父容器(未必是根布局容器).不限于ConstraintLayout.
如 FragmeLayout中1个子控件,想让其水平位置是end,且宽高*2
//一定要在子控件布局参数变化前调用 TransitionManager.beginDelayedTransition(frameLayout) //在修改子View布局属性前,使用TransitionManager为其父容器布局变化添加动画 val params:FrameLayout.LayoutParams = child.layoutParams as FrameLayout.LayoutParams params.width *= 2 params.height *= 2 params.gravity = Gravity.END v.layoutParams = params - 使用ConstraintSet切换布局xml,控件的内容,比如EditText中的输入内容会保留,因为它并不会移除原始布局中的View, 仅仅是复制了最新布局xml中的布局参数,应用到原始布局.