0dp 与 match_parent
0dp 表示 在满足约束空间的前提下自定义宽高,需要配合 constraintWidth/Height 使用,默认时会占满剩余约束空间。如下宽度为 0dp 的 view2 占满了剩余的所有空间。在实际使用时 一定要使用 0dp
match_parent 表占满父布局的空间。如果对下图的 view2 设置成 match_parent,那么 view2 将会覆盖住 view1。
宽高
约束布局中宽高都由约束决定,约束布局中宽高有几种写法:
- 具体值
- wrap_content:由内容决定且不受约束空间限制。当内容的宽高超过约束空间时,可使用
layout_constrainedWidth 控制其是否可超过约束条件 - 0dp:表满足约束空间,在该前提条件下通过 _default 定义默认行为
layout_constraintWidth_default:设置 0dp 时的默认行为。wrap 表包裹内容(但会满足约束空间,这是与 wrap_content 最大的区别),spread 表占满约束空间(默认行为),percent 表百分比布局(需要结合 layout_constraintWidth_percent 设置百分比)
- 在 chain 中使用权重
layout_constraintWidth
好像是新加的属性,优先级高于 layout_width,汇总了在宽高设定时常见的行为:
- match_parent:等于 layout_width=match_parent
- wrap_content:等于 layout_width=wrap_content
- wrap_content_constrained:等于 layout_width=wrap_content 与 layout_constrainedWidth="true"
- match_constraint:layout_width=0dp 默认行为,表占满约束空间
bias
当子 view 未能占满约束定义的全部空间时,bias 可指定左右/上下两个空间的占比。类似于 LinearLayout 的 weight。但要注意 一定要指定双边约束,即上下/左右约束需要同时指定。如下,指定了垂直方向的约束,bias = 0.2,因此 view1 上下部分的占比为 0.2:0.8
<View
android:id="@+id/view1"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/purple_200"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.2" />
GuideLine
设计 guideline 的主要目的是:将常用的定位距离(如页面边距、分界线)抽象成一条参考线,方便多个视图统一使用和后期修改。
由于 view 基于 guideline 进行定位,因此对 guideline 执行动画操作时,所有基于它的 view 会同步进行动画
Guideline 并不会绘制,它只是一条参考线。因此 guideline 设置的宽高没有意义。挪到参考线时,所有与参考线建立约束的 view 会同时移动。
通过 orientation 指定参考线是垂直、水平方向,通过 guide_start/end/percent 指定参考线的位置。
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.3" />
chain
当一组视图在 同一轴线上(水平或垂直)通过双向约束连接在一起时,就形成了一个链。也就是说一个链上的元素需要相互约束,例如 A startToEnd B,那么 B 就需要 endToStart A。
链的行为由第一个元素通过 chainStyle 指定,一个链中的元素可以看作一个整体,chainStyle 决定链中元素如何排列布局:
- packed:当作一个整体,紧密贴合,并居中
- spread:元素间等距分隔,且链头、链尾与约束边界也参与分配
- spread_inside:元素间等距分隔,但链头、链尾紧贴约束边界
链中的元素也可分别指定 weight,它们将会 weigth 指定的比例分配约束空间,此时会忽略链头元素指定的 chainStyle。
使用 chain 可以实现跟随布局:
- 首先构建成链,chainStyle 要指定成 packed,这样所有的元素才会抱团
- 链头指定 bias = 0,这样链才会居左显示
- 需要动态调整宽度的 view 要满足两个条件:宽度跟随内容且不能超过约束条件。所以有两种写法:
- width = 0dp,constraintWidth_default="wrap":前者指定要满足约束条件,后者指定要满足约束条件时的默认行为为 wrap_content
- layout_width="wrap_content",layout_constrainedWidth="true" 强制指定宽度为 wrap_content 时要满足约束条件
barrier
与 guideline 类似,也是一个虚拟视图。但使用 guideline 时首先定义 guideline 的位置,其余 view 基于 guideline 定位;而 barrier 相反,它首先定位相关的 view,再基于这些 view 定位 barrier,因此它随着 view 变化而移动,永远定位在某个 view 组的最边缘。
例如当你有几个尺寸会变化的视图,并且希望另一个视图始终在这些视图的右侧(或下方)时,屏障非常有用。如下:无论 view,view2 谁的宽度比较宽,view3 会始终在它的右边
常用属性:
- barrierDirection:定义 barrier 的位置,如 end 表 view 组的最右侧等
- constraint_referenced_ids:定义 barrier 基于哪些 view 进行定位,多个 view 通过逗号分隔
<!-- 作为约束布局子 view 而存在 -->
<androidx.constraintlayout.widget.Barrier
android:id="@+id/layer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="end"
app:constraint_referenced_ids="view,view2" />
layer
统一对一组 view 执行变换,使用 layer 可以对不同 view 同时执行操作,无论这些 view 是否在同一个 viewGroup 下。
使用 constraint_referenced_ids 指定对应的 view id,多个 id 使用逗号分隔
<!--作为 ConstraintLayout 子布局而存在-->
<androidx.constraintlayout.helper.widget.Layer
android:id="@+id/layer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="end"
app:constraint_referenced_ids="view,view2" />
flow
以 flow 形式管理多个 view,会将 view 构建成 chain,是对 chain 的扩展,也因此 flow 有不少跟 chain 相关的属性。flow 会以行列形式管理
-
constraint_referenced_ids:指定哪些 view 以 flow 形式管理
-
flow_wrapMode:指定换行模式
- none 不换行
- chain 换行且每行都以 chain 形式管理
- aligned 换行且每行列元素都对齐,此时设置的所有与 chain 相关的属性都不生效
-
flow_horizontalStyle 与 flow_firstHorizontalStyle、flow_lastHorizontalStyle:分别设置中间行、第一行、最后一行的 chainStyle,其值与 chain 中的 chainStyle 一样
- packed:所有 view 都抱团
- spread: 所有 view 都分散对齐,且不紧靠左右边界
- spread_inside: 所有 view 分散对齐,但会紧靠左右边界
-
flow_horizontalBias,flow_firstHorizontalBias 与 flow_lastHorizontalBias: 分别指定中间行、第一行、最后一行的 bias。要注意:
它们必须与 style 一起使用,单用无效
- flow_horizontalGap 与 flow_verticalGap: 设置每列/行 view 之间的间隔
- flow_maxElementsWrap: 设置每一行最多容纳几个元素
基于 flow 可实现分散对齐效果(限制比较大,只在字数固定时可用)
ConstraintSet
约束布局的约束条件除了可以在 xml 中定义外,还可以通过代码进行设置,此时就需要使用 ConstraintSet。使用步骤:
- clone():先从约束布局中提取所有的约束条件
- 设置各种约束条件
- clear:清除指定 view 的约束条件,可以指定清除哪个方向的约束。同时会清除约束条件以及 margin
- connect:设置新的约束条件。第一个参数为要修改的 view,后面参数指定它的约束。它们后 xml 中的对应关系是:
第二个参数_to第四个参数Of=“第三个参数”,如下面代码中的 connect 就表示 start_toStartOf="parent" - setMargin:设置 margin,根据第二个参数决定是 marginStart 还是 marginEnd
- applyTo():代码处理完所有的约束条件后,应用到指定的约束布局中
val set = ConstraintSet()
val cl = findViewById<ConstraintLayout>(R.id.constraint)
// 先从布局中读取所有的约束条件
set.clone(cl)
// 修改约束条件
set.clear(R.id.tv3, ConstraintSet.BOTTOM)
// 设置 margin
set.setMargin(R.id.tv3, ConstraintSet.START, 100)
// 设置新的约束
set.connect(
R.id.tv3,
ConstraintSet.START,
ConstraintSet.PARENT_ID,
ConstraintSet.START
)
// 应用修改后的约束条件
set.applyTo(cl)
MotionLayout
常见问题
- xml 中定义完约束后不生效
- 可能是因为
Constraint 中没有定义 width 与 height - 可能用的全名空间是 app,
约束的定义应该全换成 motion
- 可能是因为
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto"
<!-- 这个 app 应该删除掉,不应该出现经 motionlayout 对应的 xml 中 -->
xmlns:app="http://schemas.android.com/tools" />
基本介绍
它是约束布局的子类,因此可以使用约束布局的一切属性。
与约束布局最大的不同是 MotionLayout 支持动画。它支持从一个约束视图(即 scene)采用动画形式过渡到另一个视图。比如开始时 A 靠近屏幕左边(即最初 scene),结束时 A 要靠近屏幕右边(即终止 scene),使用 motionlayout 可以自动在两个 scene 之间添加动画。
类似于在应用 ConstraintSet 之前调用 TransitionManager 的beginDelayedTransition(),如下:
// ConstraintSet 切换动画
val bound = ChangeBounds()
bound.duration = 1000
TransitionManager.beginDelayedTransition(layout, bound)
set.clone(layout)
if (flag) {
set.clear(R.id.tv, ConstraintSet.START)
set.connect(R.id.tv, ConstraintSet.START, R.id.start, ConstraintSet.END)
set.connect(R.id.tv, ConstraintSet.END, R.id.end, ConstraintSet.START)
} else {
set.clear(R.id.tv, ConstraintSet.END)
set.setMargin(R.id.tv, ConstraintSet.START, 50)
}
set.applyTo(layout)
MotionScene
MotionLayout 需要在 res/xml 文件夹中另定义一个 xml 文件,用来定义动画相关的内容,然后通过layoutDescription="@xml/xx 引用该 xml 文件,一个完整的定义如下:
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<!-- 过渡定义 -->
<Transition
motion:constraintSetStart="@+id/collapsed"
motion:constraintSetEnd="@+id/expanded"
motion:duration="1000"
motion:interpolator="easeInOut">
<!-- 点击触发器,点击 fab 时触发 -->
<OnClick
motion:targetId="@+id/fab"
motion:clickAction="toggle" />
<!-- 滑动手势 -->
<OnSwipe
motion:touchAnchorId="@+id/header"
motion:touchAnchorSide="top"
motion:dragDirection="dragDown" />
<!-- 关键帧 -->
<KeyFrameSet>
<KeyPosition
motion:motionTarget="@id/xx"
motion:framePosition="50"
motion:keyPositionType="parentRelative"
motion:percentX="0.8"
motion:percentY="0.2" />
<!-- position 可以定义位置,attribute 可以定义别的属性 -->
<!-- 如 alpha 等 -->
<KeyAttribute
motion:framePosition="75"
android:rotation="180" />
</KeyFrameSet>
</Transition>
<!-- 折叠状态 -->
<ConstraintSet android:id="@+id/collapsed">
<Constraint
android:id="@+id/fab"
android:layout_width="56dp"
android:layout_height="56dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
android:layout_margin="16dp">
<!-- 除常规的位置、大小等属性外,montionLayout 还支持修改透明度等 -->
<!-- 这些属性的修改就需要通过 CustomAttribute 定义-->
<CustomAttribute
motion:attributeName="text"
motion:customStringValue="你好个屁" />
</Constraint>
</ConstraintSet>
<!-- 展开状态 -->
<ConstraintSet android:id="@+id/expanded">
<Constraint
android:id="@+id/fab"
android:layout_width="120dp"
android:layout_height="56dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
android:layout_margin="16dp">
<CustomAttribute
motion:attributeName="backgroundColor"
motion:customColorValue="#FF5722" />
</Constraint>
</ConstraintSet>
</MotionScene>
- OnClick:定义
点击某个 view 时触发动画,也可通过代码控制。clickAction 定义点击时的行为- toggle:默认值,
向另一个状态切换,比如当前是 start 状态,则点击后会切换到 end 状态。 如果当前正在执行切换,也会停止切换并向另一个状态切换。 - transition/jump:切换到固定的状态。transition 会带动画,jump 不带
- toggle:默认值,
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@+id/start"
motion:duration="1000">
<OnClick
motion:clickAction="toggle"
motion:targetId="@+id/mClick" />
</Transition>
-
OnSwipe:指定响应 MotionLayout 滑动的 View
touchAnchorId/touchAnchorSide: 手指拖动时需要确定当前动画的进度,这两个属性就是用于告诉 MotionLayout 如何确定当前进度。它会根据 id 指定的 view 的边(由 side 指定)的起始、终止位置做为运动总距离,并结合手指的移动的距离计算出动画进度。- touchRegionId: 指定 view,该 view 发生 swipe 事件时会触发 anchor 执行动画;如果未指定,则默认为整个 motionlayout
- dragDirection: 指定响应的方向,如向上、左、右等
- onTouchUp: 当手指抬起时 view 如何处理。比如回到起始位置或者终止位置等
-
CustomAttribute:约束中只能定义位置、大小相关的属性,如果想
定义透明度等属性(比如背影色、透明度、文字等)就需要在 Constraint 内部使用 CustomAttribute 节点,它的使用方法可阅读ConstraintAttribute::parse() 方法。对于一个属性的修改至少有两个前置条件:- 如何修改,即想修改该属性需要调用 View 的哪个方法。可以通过
attributeName 或 methodName指定修改该属性时需要调用的方法 - 目标值,根据属性值的类型选择
使用不同的 customXXXValue 指定目标值。如完整示例中想修改文本,就需要使用 customStringValue 指定目标值
- 如何修改,即想修改该属性需要调用 View 的哪个方法。可以通过
-
KeyFrameSet定义动画中的关键帧- keyposition:定义动画中某个时刻 view 的位置,要注意:
定义的位置并不是 view 真正经过的位置,而是控制运动轨迹的控制点,运动轨迹是贝塞尔曲线形式的 - KeyAttribute: 定义动画中某个时刻 view 的透明度等其它属性
<KeyFrameSet> <KeyAttribute motion:motionTarget="@id/button" motion:framePosition="80" android:alpha="0.5" android:rotationX="45" android:scaleX="2" android:scaleY="2" android:translationZ="20dp"/> </KeyFrameSet>- KeyCycle: 用于重复性、周期性的动画想着的设置。比如周期性地透明度变化,左右摇晃等
- keyposition:定义动画中某个时刻 view 的位置,要注意: