一 约束布局2.x新特性简介
2020.06.28更新: Flow使用详解
约束布局2.0未使用过约束布局的,可先查看上一篇文章ConstraintLayout 约束布局1.x
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta3'
约束布局是一个允许你灵活定义view
位置和大小的ViewGroup
,具有多种辅助工具,如GuideLine、Barrier、Group等。在灵活地放置各种各样的view时,并不会增加Layout层级。2.0版本出了优化布局性能外,还增加了一些新特性,使得开发过程更加方便:
- ConstraintHelper辅助工具的增加:Layer,flow。
- ConstraintHelper的自定义开放
- ConstraintLayoutStates 界面状态切换控制
- API使用优化
- **MotionLayout **构建一个动态的布局
开发实际发现,到现在竟然还有些人看不上约束布局,Google对于约束布局的信心和野心,难道还不足以引起重视??
二 ConstraintHelper辅助工具
2.1 Layer
Layer功能上可以理解为包含它所引用的view的一个父布局viewGroup,但并不会增加layout的层级。这点是非常好用的,在Layer之前,想往自己view统一加个背景限制,一般都是另外加个view来做纯背景展示。
像开发过程中这种带图标的dialog,可以用layer方便圈出背景。
另外,Layer支持对里面的 view 一起做变换,可看待成一个平常的父布局,一起做变换,设置visibility等(Layer本身也是继承自view)。
...
<androidx.constraintlayout.helper.widget.Layer
android:id="@+id/mLayer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_pet_white_with_corner"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="@id/mRecyclerView"
app:constraint_referenced_ids="mTvTitle,mRecyclerView,guide_line"
/>
...
2.2 自定义 ConstraintHelper
自定义ConstraintHelper(简称Helper),可以用来封装针对ui的一些固定行为,方便以后复用。而且,一个view又可以同时被多个helper所引用,可以很便捷地组合出多种效果。
注:Helper本身也是继承了view的
- Helper提供了getViews()方法获取所引用的所有view
- Helper提供了view的onLayout()/onMeasure()等流程方法执行前后的回调,如:updatePostLayout(container: ConstraintLayout?),onLayout()后 ;updatePreLayout(container: ConstraintLayout?) ,onLayout()前; 可利用这些方法和获得的view,封装一些通用操作
- 再应用到布局文件中,声明要包含的view的id:
app:constraint_referenced_ids="xxx,xxx"
class EnterAnimationHelper @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ConstraintHelper(context, attrs, defStyleAttr) {
override fun updatePostLayout(container: ConstraintLayout?) {
super.updatePostLayout(container)
val views = getViews(container)
views.forEach {
startEnterAnimation(it)
}
}
private fun startEnterAnimation(view: View) {
val translationY = -100f
view.translationY = translationY
val translationYHolder = PropertyValuesHolder.ofFloat(
View.TRANSLATION_Y,
translationY,
0f
)
val keyFrame1 =
Keyframe.ofFloat(0f, -6f)
val keyFrame2 =
Keyframe.ofFloat(0.6f, 25f)
val keyFrame3 =
Keyframe.ofFloat(1f, 0f)
val rotateHolder =
PropertyValuesHolder.ofKeyframe(View.ROTATION, keyFrame1, keyFrame2, keyFrame3)
ObjectAnimator.ofPropertyValuesHolder(
view,
translationYHolder,
rotateHolder
)
.apply { interpolator = AnticipateOvershootInterpolator() }
.setDuration(600L)
.start()
}
}
...
<com.cyq.x622.constraintlayoutsample.widget.EnterAnimationHelper
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="xxx,xxx"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
...
效果如下(此处是封装了一个,上下位移和左右旋转的动画效果):
三 ConstraintLayoutStates
ConstraintLayoutStates可以创建具有不同状态的布局并在它们之间轻松切换。通常,一个界面包含有加载状态,加载成功状态以及加载失败状态。利用ConstraintLayoutStates,可以很方便地在已定义好的状态之间相互切换。
3.1 创建不同状态的布局文件
根据需要,创建不同状态下的布局文件。每一个文件必须有相同的view,只有属性值,如visibility,和定位方式等可以不相同(简单起见,demo就定义了加载状态和成功状态,都是只含有一个ProgressBar和一个TextView,id都相同,只有visibility不同)
3.2 创建状态声明XML文件
在xml资源文件夹中,创建一份xml文件,定义了layout可拥有的所有状态constraint_set_test.xml:
<?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/loading"
app:constraints="@layout/activity_state_start"/>
<State
android:id="@+id/success"
app:constraints="@layout/activity_state_end"/>
</ConstraintLayoutStates>
3.3 加载声明的状态文件并在不同状态之间切换
在activity/fragment中,在要应用状态的ConstraintLayout上 使用loadLayoutDescription(),加载定义好的状态xml文件声明。然后便可以直接调用constraintLayout.setState()来切换状态.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_state_loading)
mStateConstraintLayout.loadLayoutDescription(R.xml.constraint_set_test)
//简单延时,模拟io操作
mStateConstraintLayout.postDelayed({
mTv.text = "加载完成"
mStateConstraintLayout.setState(R.id.success,0,0) //xml中定义的id
},4_000L)
}
效果如下:
四 流式API
2.0以后,对属性的修改提供了流式API。
ConstraintProperties(mBtnLayer)
.alpha(0.5f)
.margin(ConstraintSet.TOP, 100)
.apply()
这部分比较简单,可以直接查看官方文档ConstraintProperties
五 简易demo
六 Flow
Flow是一个特别强大的布局辅助工具,支持多种布局模式,可以快速构造多样性布局。
Flow部分的demo及文中结构大部分翻译自于Medium:Awesomeness of ConstraintLayout Flow
尽管约束布局已经特别强大,可以快速建立约束关系展示布局,如下面两个水平关系view
两个水平view
但如果要构建8个不同行的view,且相互之间有约束关系:
每行之间间隔均分
当然,使用三条约束链便可以实现,但写法上太过于繁琐,尤其是当垂直方向上还要设置对应关系时,更加繁琐。
这时候Flow就可以派上用场了。
Flow可以看成一个具有多种约束功能的流式布局,当空间不足时,能按设定的方式自动换行对齐。
Flow在使用上,有多个属性可以控制布局约束关系:
- orientation: horizontal 或者 vertical
- WrapMode
- Gap
- Styles
- Bias
- Alignment
6.1 orientation
布局方向:水平horizonta l或 垂直vertical
setOrientation(int orientation)
android:orientation="horizontal|vertical"
水平布局
垂直布局
6.2 WrapMode
WrapMode属性决定了Flow将如何控制所引用的views的布局方法:NONE,CHAIN,ALIGN
app:flow_wrapMode = " none | chain | aligned "
flow.setWrapMode( Flow.WRAP_NONE | Flow.WRAP_CHAIN | Flow.WRAP_ALIGNED )
NONE:默认值,空间不够情况下Views不会被自动换到另一行/列,直接超出屏幕范围。
Wrap Mode = NONE
CHAIN:该模式与约束布局的链式chain布局相似,不仅可以实现相同的效果,还会额外的自动换行/换列处理
【注】:与约束布局链式布局一样,CHAIN模式也有链式style:SPREAD(默认值),PACKED,SPREAD_INSIDE
Wrap Mode = CHIAN 且 style = SPREAD
ALIGNED:该模式与上面的CHAIN相同,但额外增加了对齐方式
【注】:与CHAIN模式一样也有链式style:SPREAD(默认值),PACKED,SPREAD_INSIDE
Wrap Mode = ALIGNED 且 style = SPREAD
6.3 Gap
Gap是放置views时的水平和垂直间隔。
app:flow_horizontalGap
app:flow_verticalGap
flow.setVerticalGap
flow.setHorizontalGap
因为比较简单,就不放图了,手动试试就知道了。
6.4 Styles
当wrapMode为chain或ALIGNED时生效。Flow的styles跟约束布局之前的基础链式布局style是一个概念,有SPREAD(默认值),PACKED,SPREAD_INSIDE。不了解的可先查看上一篇:约束布局的链式布局
app:flow_horizontalStyle = “ spread | spread_inside | packed ”
app:flow_verticalStyle = “ spread | spread_inside | packed ”
应用到Flow中,效果如下:
spread
Spread Inside
packed
6.5 Bias
flow的bias偏移,只在style为packed时生效,因为当style为spread或者spread_inside时,views是均匀分布的,bias无法起到作用。float值,范围为 0-1
app:flow_horizontalBias = “ float "
app:flow_verticalBias = “ float "
flow.setHorizontalBias( float)
flow.setVerticalBias( float)
这里只取两个端点值,0和1,方便理解。
bias为0,贴到最左边
bias为1,贴到最右边
6.6 Alignment
Alignment对齐方式,同样也有水平和垂直。Alignment的对齐方向,与flow的方向必须是相反的才能生效。比如当flow的方向是水平时,Alignment只有设为垂直才有效。views是水平放置,对齐是view与view之间在垂直方向上的对齐方式。关于这个属性,可以运行demo多试几遍理解理解。
app:flow_verticalAlignment = “ top | center | bottom | baseline ”
app:flow_horizontalAlignment = “ start| end ”
flow.setVerticalAlignment(
Flow.VERTICAL_ALIGN_TOP | Flow.VERTICAL_ALIGN_CENTER | Flow.VERTICAL_ALIGN_BOTTOM | Flow.VERTICAL_ALIGN_BASELINE )
flow.setHorizontalAlignment(
Flow.HORIZONTAL_ALIGN_START | Flow.HORIZONTAL_ALIGN_END )
Vertical Alignment = Bottom
6.7 Flow demo
flow的demo并非远程,可参考ConstraintFlowPlayground
后期计划MotionLayout
MotionLayout是2.x版本的一个重要的更新,尤其是MotionLayout构建动态布局。下期再计划编写了