ConstraintLayout-那些很有用但是你可能不知道的用法
前沿
本篇不讲ConstraintLayout的常规用法,会讲一些约束布局很用的,但是可能你并不知道的用法
1、(简单开胃菜)B相对于A底部居中对齐
如上图所示:需要B布局相对于A布局的底部垂直居中对齐,水平居中对齐
方案1 使用LinearLayout为外层布局
- 需要指定B一个固定高度
- 给定B一个负的向上的margin,值为B高度的一半
代码和效果如下所示:
缺陷:很明显,如果只是使用XML布局的形式(不通过代码动态计算),B布局需要固定一个高度才行,顶部margin也是固定值,如果B布局高度可变,还是很不方便的。
方案2 使用RelativeLayout为外层布局
- 同样需要给B一个固定的高度值
- B的alignBottom设置为A布局
- 给B设置一个负的marginBotttom
缺陷:同使用LinearLayout作为父布局一样,如果只是使用XML布局的形式(不通过代码动态计算),B布局需要固定一个高度才行,margin也是固定值,如果B布局高度可变,还是很不方便的。
终极方案:使用ConstraintLayout
- 水平方向相对于父布局居中
- 关键是垂直方向
- B的顶部相对于A的底部
- B的底部相对于A的底部
代码和效果图如下图所示:
对,没看错,就是这么简单
优势 不用管B的高度是多高,B布局都会自动相对A的底部剧中对齐
2、goneMargin属性的妙用
如上图所示:最外层是约束布局,A距离顶部138dp,B在A的下面,距离A的顶部38dp
==问题:==当A为gone的时候,需要B也距离顶部138dp的距离,如何实现?
方案1: 提枪上马,直接干,当A为gone时候,直接代码设置B的marginTop为138dp
方案2: A 和B外面再套一层布局,这层布局距离顶部是138dp,当A隐藏时候,将B的marginTop设置为0
方案3: 这里就不例举了,大都数实现方法都是需要动态设置的,不灵活不方便,不丝滑
终极方案:使用goneMargin属性
1、goneMargin属性,这里是B的顶部在A的底部,故使用
app:layout_goneMarginTop="138dp"属性即可
当A布局显示的时候,A相距顶部是138dp,B相距A的顶部是38dp,这时候goneMarginTop属性并不会生效(顾名思义gon eMargin),效果如下图所示:
2、将A设置为gone,这时候B的marginTop属性将会失效,取而代之的是B的goneMarginTop属性,这样就达到我们的要求了,效果图如下所示:
优势: 这个属性在涉及一些需要隐藏部分视图,同时需要改变相对的margin,对齐等等属性的时候,是非常有用的。无需使用代码动态计算,无需其他的一些嵌套,你会了吗?
2、Group的使用
如上图所示:
有三个TextView,如果需要控制AB同时隐藏显示,如何实现?
常规方案 一般有两种思路,其一就是逐个设置布局的Visible属性,其二可以在AB外面再包一层布局,通过设置外层布局的显示隐藏属性。
弊端 很明显,一个个设置很麻烦,而在外面包一层布局的话就增加了布局的层次,会增加布局渲染的开销,这也是我们不希望看到的。
使用ConstraintLayout 的Group 方案
- 新建一个Group ,引用A,B的id,这样就讲AB分成一个组了,只需要设置组的显示隐藏属性即可
将group_ab的属性visibility属性设置成gone,这样TextA和TextB就可以同时隐藏了。如下图所示:
3、圆弧定位
- layout_constraintCircle
- layout_constraintCircleAngle
- layout_constraintCircleRadius
它的作用就是你可以相对于锚点View的中心位置,声明一个角度和距离(半径)来确定View的位置
- ayout_constraintCircle 是关联的锚点View的id
- layout_constraintCircleRadius View的中心点与关联的锚点View的中心点的距离(圆弧半径)
- layout_constraintCircleAngle View的中心点与关联的锚点View的中心点的角度关系(0到360度)
官方示意图:
代码效果如下图所示:
4、真的会用0dp?
-
ConstraintLayout相对于其他布局提供给了一个0dp的属性(MATCH_CONSTRAINT ),这不是说让宽或高位0dp,而是一种标记。
-
它标记的含义会随着不同的constrain设置而有不同的体现。
-
有些时候0dp和layout_constraintWidth_default一起使用,会有意想不到的效果
-
layout_constraintWidth_default的三种取值
- 默认是spread,意思是占用所有的符合约束的空间
- percent, 顾名思义就是按照百分比来表示宽度
- wrap:匹配内容大小但不会超过约束的限制。(和指定宽度为wrap_content的区别是不会超过约束限制)
需求1
如下图,内容布局在A和B的中间
实现代码如下:
这个实现起来很简单,只要设置左右上下的约束就行了,同时设置android:layout_width=“0dp” 就行了,那么设置成android:layout_width=“wrap_content” 行不行呢?答案是不行的,当内容多的的时候,就会超出约束限制,变成下面的鸟样
0dp这里就是实现约束条件,让宽度不会超过约束
需求2 上面内容布局的居中,相信用过ConstraintLayout的都会,那么问题来了,如果需要给内容布局添加一个背景色,让那个背景色刚好包裹内容,如何实现呢?
如下图,我们直接加上背景色
显然这里是不符合要求的,我们需要背景色刚好包裹住内容,而不是显示在整个中间的空间,为什么会显示成这样呢?答案就是前面说的layout_constraintWidth_default默认是spread,占用所有的符合约束的空间也就是填充了中间所有的布局了,
实现 如果需要背景刚好包含内容,这里只需要将layout_constraintWidth_default设置成wrap就行了
如下图所示:
当内容较多时候,也是可以的,如下图所示:
杠精来了 我非要使用宽度的wrap_content来实现呢?
也是可以的,只要解决wrap_content内容过多时候超出约束的问题就行了,谷歌也是提供了属性的:app:layout_constrainedWidth=”true|false”
这样,使用wrap_content结合layout_constrainedWidth=true,也可以实现上面效果
布局效果图如下所示:
注:以上都是使用的宽度进行演示的,对于高度也是一样的
5、 layout_constraintDimensionRatio
ConstraintLayout不仅支持宽高比例的配置,还能配置宽高比例根据其中的一个计算出另外一个的
当宽度高度有一个为0dp时,另外一个可以根据指定的ratio来确认自己的大小。
需求
如下图所示,A,B平分整个屏幕宽度,中间有3%的屏幕宽度作为间距,同时A,B的宽高比都是2:1,如何实现?
分析
- 首先将layout_width和layout_height都设置为0dp,以应用我们的具体的约束
- 由于A,B宽度相对于屏幕是固定的,即每一个宽度:(100-3)/2=48.5,故可以按百分比布局指定宽度
- A,B的宽高比都是2:1,故将约束加在高度上,指定宽高比h,2:1即可
布局效果图如下:
so easy!
6. Barrier (栅栏,屏障)的使用
Barrier 是用多个 View 作为限制源来决定自身位置的一种辅助线
将多个view 放入barrier中,barrier的边界随着view的改变而改变
如图所示:
这里我们需要限定C和A顶部对齐,同时C在A和B的右侧,我们只需要将A和B加入barrier ,同时指定barrierDirection为right,然后指定C在barrier的右边,和A顶部对齐
布局效果图如下所示:
上面是基于最新稳定版本的一些特性
截止目前,最新稳定版本:
implementation ‘androidx.constraintlayout:constraintlayout:1.1.3’
下面的一些特性基于最新的测试版本,当然,你要在项目中使用也是可以的
截止目前,最新测试版本:
implementation ‘androidx.constraintlayout:constraintlayout:2.0.0-beta6’
7. Layer的使用
Layer 可以看作是它引用的 view 的边界(可以理解为包含这些 view 的一个 ViewGroup,但是Layer并不是ViewGroup,Layer并不会增加 view 的层级)。另外Layer支持对里面的 view 一起做变换。
需求 如下图所示:A,B,C在同一个约束布局中,如果需要不增加层级的前提下,给A,B,C作为一个整体添加一个背景色,怎么实现好呢?
使用Layer
- Layer不提供位置约束,也就是其他布局约束到layer是不起作用的
- Layer 相当于把引用的视图圈一起,可以进行设置背景色,padding等
- 给Layer添加动画,就是给里面的每一个视图添加动画,对于一些复杂的动画的场景还是比较有用的
这里我们讲A,B,C添加到一个Layer, 同时设置背景色和padding,如下图所示:就像是用一个ViewGroup把三个视图包起来一样,实际并没有增加一个层级
给Layer执行动画:放大,旋转360度,平移
btn.setOnClickListener {
val anim = ValueAnimator.ofFloat(0f, 360f)
anim.addUpdateListener { animation ->
val angle = animation.animatedValue as Float
layer.rotation = angle
layer.scaleX = 1 + (180 - Math.abs(angle - 180)) / 30f
layer.scaleY = 1 + (180 - Math.abs(angle - 180)) / 30f
var shift_x = 50 * Math.sin(Math.toRadians((angle * 5).toDouble())).toFloat()
var shift_y = 50 * Math.sin(Math.toRadians((angle * 7).toDouble())).toFloat()
layer.translationX = shift_x
layer.translationY = shift_y
}
anim.duration = 4000
anim.start()
}
效果图如下所示:
8. Flow 的使用
Flow 是 VirtualLayout,Flow 可以像 Chain 那样帮助快速横向/纵向布局constraint_referenced_ids里面的元素。 通过flow_wrapMode可以指定具体的排列方式,有三种模式
- wrap none : 简单地把constraint_referenced_ids里面的元素组成chain,即使空间不够
- wrap chain : 根据空间的大小和元素的大小组成一条或者多条 chain
- wrap aligned : wrap chain类似,但是会对齐
实例: 如何实现下面的简单计算器布局
原始方案
这里原是方案,可以通过LinearLayout 加上weight属性,通过权重来计算,但是这样就需要嵌套几层布局,而且众所周知,LinearLayout的weight效率不是那么友好的,那如何不通过前套实现上面的布局效果呢?
使用虚拟布局Flow
- 将所有的数字和“/”,“.",“*”放入Flow的引用
app:constraint_referenced_ids="tv_num_7,tv_num_8,tv_num_9,tv_num_4,tv_num_5,tv_num_6,tv_num_1,tv_num_2,tv_num_3,tv_num_0,tv_operator_div,tv_dot,tv_operator_times"
- 设置Flow的属性,如下所示
app:flow_horizontalGap="10dp" //每个item水平间距
app:flow_maxElementsWrap="3" //一行包含3个元素
app:flow_verticalGap="10dp" //每个item垂直间距
app:flow_wrapMode="aligned" //对齐方式
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- 由于Flow是虚拟布局,并没有占用布局层次,故对于Compute按钮,可以直接相对于Flow 的子元素就行布局
<TextView
android:id="@+id/opration"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#CCBCD4"
android:gravity="center"
android:text="Compute"
android:textColor="@android:color/white"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="@+id/tv_operator_times"
app:layout_constraintEnd_toEndOf="@+id/tv_dot"
app:layout_constraintStart_toStartOf="@+id/tv_operator_div"
app:layout_constraintTop_toTopOf="@+id/tv_operator_times" />
最上面的计算结果布局“0”,可以相对于Flow进行布局,在Flow的上面,左右和Flow进行对齐,代码如下
<TextView
android:id="@+id/result"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#CCA9F4"
android:gravity="right|center_vertical"
android:paddingEnd="16dp"
android:text="0"
android:textColor="@android:color/white"
android:textSize="58sp"
app:layout_constraintBottom_toTopOf="@+id/flow"
app:layout_constraintEnd_toEndOf="@+id/flow"
app:layout_constraintStart_toStartOf="@+id/flow"
app:layout_constraintTop_toTopOf="parent" />
这样,将使用Linear Layout的多层嵌套变成0层嵌套,而且布局清晰,简单方便明了
完整的布局效果图:
完整的布局代码:
<?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:ignore="MissingConstraints">
<androidx.constraintlayout.helper.widget.Flow
android:id="@+id/flow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FFCCCCC"
android:padding="20dp"
app:constraint_referenced_ids="tv_num_7,tv_num_8,tv_num_9,tv_num_4,tv_num_5,tv_num_6,tv_num_1,tv_num_2,tv_num_3,tv_num_0,tv_operator_div,tv_dot,tv_operator_times"
app:flow_horizontalGap="10dp"
app:flow_maxElementsWrap="3"
app:flow_verticalGap="10dp"
app:flow_wrapMode="aligned"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/tv_num_7"
style="@style/text_style"
android:text="7" />
<TextView
android:id="@+id/tv_num_8"
style="@style/text_style"
android:text="8" />
<TextView
android:id="@+id/tv_num_9"
style="@style/text_style"
android:text="9" />
<TextView
android:id="@+id/tv_num_4"
style="@style/text_style"
android:text="4" />
<TextView
android:id="@+id/tv_num_5"
style="@style/text_style"
android:text="5" />
<TextView
android:id="@+id/tv_num_6"
style="@style/text_style"
android:text="6" />
<TextView
android:id="@+id/tv_num_1"
style="@style/text_style"
android:text="1" />
<TextView
android:id="@+id/tv_num_2"
style="@style/text_style"
android:text="2" />
<TextView
android:id="@+id/tv_num_3"
style="@style/text_style"
android:text="3" />
<TextView
android:id="@+id/tv_num_0"
style="@style/text_style"
android:text="0" />
<TextView
android:id="@+id/tv_operator_div"
style="@style/text_style"
android:text="/"
tools:layout_editor_absoluteX="156dp"
tools:layout_editor_absoluteY="501dp" />
<TextView
android:id="@+id/tv_operator_times"
style="@style/text_style"
android:text="*" />
<TextView
android:id="@+id/tv_dot"
style="@style/text_style"
android:text="."
tools:layout_editor_absoluteX="278dp"
tools:layout_editor_absoluteY="501dp" />
<TextView
android:id="@+id/opration"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#CCBCD4"
android:gravity="center"
android:text="Compute"
android:textColor="@android:color/white"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="@+id/tv_operator_times"
app:layout_constraintEnd_toEndOf="@+id/tv_dot"
app:layout_constraintStart_toStartOf="@+id/tv_operator_div"
app:layout_constraintTop_toTopOf="@+id/tv_operator_times" />
<TextView
android:id="@+id/result"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#CCA9F4"
android:gravity="right|center_vertical"
android:paddingEnd="16dp"
android:text="0"
android:textColor="@android:color/white"
android:textSize="58sp"
app:layout_constraintBottom_toTopOf="@+id/flow"
app:layout_constraintEnd_toEndOf="@+id/flow"
app:layout_constraintStart_toStartOf="@+id/flow"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
布局里面使用的text_style样式:
<style name="text_style">
<item name="android:layout_width">90dp</item>
<item name="android:layout_height">90dp</item>
<item name="android:background">#aaFF88</item>
<item name="android:gravity">center</item>
<item name="android:textColor">#FFFFFF</item>
<item name="android:textSize">30sp</item>
</style>
这样,你可以直接拷贝上面的代码进行自己的尝试了。
最后
谢谢🙏有什么意见和建议,欢迎指正!