oynix

于无声处听惊雷,于无色处见繁花

Android之ConstraintLayout

ConstraintLayout,约束布局,是现在Android里面最常用的布局方式,也是系统默认的布局方式,说一说它的日常用法。

1. 简介

约束布局的出现,主要为了减少视图的层级深度,即减少嵌套。显而易见的是,层级深度越浅,绘制效率越高。之前常用的布局,有LinearLayout线性布局,RelativeLayout相对布局,FrameLayout层级布局,有时为了实现UI给出的页面效果,一个页面内ViewGroup套ViewGroup,ViewGroup里面再套个View,散发着正宗的俄罗斯套娃血统,效率低,代码量也大,

2. 边界

约束布局下的元素,都是本着和谁对齐的基本理念来设定位置,比如水平方向和谁的边界对齐、垂直方向又要和谁的边界对齐,两个方向都确定了,那么这个元素的位置自然就确定了,这其实就是在确定元素四条边界的位置,确定了四条边界的位置,那么这个元素的位置和大小也便都确定了。

水平方向有两个边,左边和右边,即Start和End,垂直方向也有两个边,上边和下边,即Top和Bottom。

每个方向,可以只指定一个边界,也可以两个边界的位置都指定。一个方向上,如果只限定一条边界的位置时,那么这个元素就会靠此边界对齐,比如水平方向只限定了右边界的位置,那么这个元素水平方向上就会紧贴着右边界,垂直方向也是一样的道理,同时,如果一个方向限定了两个边界的位置,那么这个元素就会在这两个边界间居中,比如限定一个元素上边界和父布局对齐,下边界也和父布局对齐,那么此元素在视觉上的效果就是,垂直方向处于父布局中间。

3. 宽度,高度

从上面的介绍可以看出,因为约束布局里的子元素设定的四条边界的位置,所以在设定宽度和高度时,没有match_parent这个值,只有0dp和wrap_content,但是在布局文件里写match_parent是不会报错的,只是系统在计算的时候会把parent_match替换成0dp。0dp的意思是,元素会充满该方向的边界约束,wrap_content为按需填充。

这里有个问题,如果在一个方向上,只设定一个边界,但是设为0dp,那么会怎么样呢?如果设定了两个边界,那么0dp很好处理,直接填充两个边界间的距离即可,这个很好理解,只设定一个边界时,结果就未可知了,如果推断不出来时,那就是0dp,页面上就看不到了,如果能从其他参数判断出来,则可能得到预期的结果。所以,当需要设定为0dp时,就设定两个边界,最为稳妥。

4. 边界对齐

上面提到,约束布局里设定边界时,是指定和谁对齐,这里的对齐是指,水平方向边界只能和水平方向的边界对齐,垂直方向的边界也只能和垂直方向的边界对齐,而每个方向存在两个边界,两两组和,就有了多种对齐的选择

  • 上边界和目标元素的上边界对齐
  • 上边界和目标元素的下边界对齐
  • 下边界和目标元素的上边界对齐
  • 下边界和目标元素的下边界对齐
    这是垂直方向的,水平方向也是一样的道理,两两组和也会有四种选择。下面是其中的几个属性,属性的值写目标元素的id,如果是父布局就写parent。
    1
    2
    3
    4
    5
    6
    7
    8
    <ImageView
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:src="@drawable/image"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="@id/title" />

以上就是基本使用,是的,用这些就能实现线性布局、相对布局等的效果了。下来说一说约束布局独有的特点

5. GuideLine

GuideLine,引导线,这个在布局上是看不到的,但是可以帮助我们来指定其他控件的位置,比如,不管在哪种尺寸的屏幕上,你都需要在屏幕上30%的位置显示一行文字,在其他布局中,需要每次手动获取屏幕高度,计算出目标位置,然后再把文字放到这个位置上。但是在约束布局中,是用GuideLine就可实现。

引导线有两个方向,水平方向和垂直方向。此外,它有两种模式,百分比模式,和距离模式

  • 百分比模式,percent指定的是百分比,水平方向时指距离左侧,垂直方向时指距离顶部
    1
    2
    3
    4
    5
    <androidx.constraintlayout.widget.Guideline
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:orientation="horizontal"
    app:layout_constraintGuide_percent="0.3" />
  • 距离模式,begin和end设定一个就好,两个都设定时begin生效end无效
    1
    2
    3
    4
    5
    6
    <androidx.constraintlayout.widget.Guideline
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:orientation="horizontal"
    app:layout_constraintGuide_begin="30dp"
    app:layout_constraintGuide_end="30dp" />

6. 位置百分比

1
2
3
4
5
6
7
8
9
10
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/home_bg"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0" />

上面有说到,当指定了一个方向的两个边界时,这个控件就会在边界间居中对齐,这其实是默认值。还是上面那个例子,但是多了两个属性,一个是Horizotal_bias,一个是Vertical_bias,这两个属性的默认值都是0.5,意味着居中对齐。

当给一个方向设定了两条边界的时候,并且该控件在这个方向还没有填充满,也就是有可以移动的空间,比如这个例子,图片的横向是填充父布局的,宽度设定为wrap_content,图片的实际宽度只有屏幕宽度的一半,所以剩下的宽度就是可以动的空间,那么就可以通过Horizontal_bias来设定它在水平方向的位置,默认值0.5是居中,0是居最左,1是居最右,垂直方向同理。

当不存在可以动空间时,这个属性就不会生效,比如只设定了一个边界,或者设定了两个边界,但是填充满了。

7. 宽高比

1
app:layout_constraintDimensionRatio="9:7"

这个属性多用于控制图片的大小,当需要动态设定图片的宽度或者高度,但又不希望改变图片的原始宽高比,相比于原始的在代码中计算,使用这个属性就方便多了,首先明确的是,属性值中的比例是width:height,也就是宽高比。

在ConstraintLayout中,元素的边长有2种情况,一种是设为0dp,一种是设定具体的值,当然wrap_content也算具体的值,既然是宽高比所以,那么就需要确定一个值,根据比例去算另一个值。系统在根据这个属性计算宽和高时,要考虑多种情况:

  • 高固定,宽固定:二者都固定时,这个属性不会生效
  • 宽固定,高动态:根据宽度和比例计算高度
  • 高固定,宽动态:根据高度和比例计算宽度
  • 宽动态,高动态:这个时候要想元素设置最大的大小并且不超出约束边界,即可以完全显示出来,那么在计算最终边长时,就要根据约束边界的宽高比和属性值做比较了。

说说两个都是动态时的情况,可以这么想,这个约束边界就以一块720x1280(9:16)的屏幕为准,也就是有一张图片完全填充屏幕,如果图片的宽高比是4.5:16时,那么就要以屏幕的高为基准计算宽,也就是高占满1280,用比例计算出宽占360,相反,要是以屏幕宽为基准,占满720,那么计算出来的高就是2560,超出边界范围了;同理,当图片的宽高比是18:16时,就要以宽为基准,占满720,计算出高为640。

当然,也可以在属性值中手动指定谁是需要动态计算的,像下面这样

1
app:layout_constraintDimensionRatio="h,9:7"

这样写的意思就是说,h是动态的,所以要先计算出来w,然后根据9:7再计算出h。

总结起来就是,比例值是宽高比,谁是固定的,就用谁去计算另一个。两个都不固定时,如果没有指定谁是动态计算的话,那么就以不超出约束的边为基准的原则,计算边长。

8. ImageFilterView和ImageFilterButton

这两个类似,就是在ImageView的基础上加了额外的属性支持,常用的就是圆角图片,这算是官方首次对圆角图片提供支持,round设置圆角大小,roundPercent设置圆角比例。

此外,还有基于图片本身的操作,如亮度brightness、饱和度saturation、暖色调warmth,对比度contrast,等

9. 其他新特性

除了以上这些常用的属性,还有几个新特性,可以实现一些独特的需求。比如Group同时控制多个控件的显隐性,等等

https://www.cnblogs.com/sjjg/p/14434334.html

10. 几点说明

  • 当宽或高设定为wrap_content,并且设定了两条边界时,此时边界的作用是用来限定控件的位置,而不是宽或高,也就说,宽或高是有可能超出边界的范围的。
  • 如果有重叠,布局文件中写在下面的控件会盖在上面的控件,这一点和FrameLayout类似。
------------- (完) -------------
  • 本文作者: oynix
  • 本文链接: https://oynix.com/2022/04/f9b981637aaf/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

欢迎关注我的其它发布渠道