ConstraintLayout 的解析,扁平化布局

前言

ConstraintLayout从推出到现在也有好长时间了,依然没有成为主流布局,首选布局RelativeLayout,LinearLayout,FrameLayout,简单的布局一个RelativeLayout就能实现,稍微复杂点的布局就开始嵌套使用了。其实如果大家去扒一扒自己app的布局,找一个复杂点的界面,然后去看下布局结构,嵌套了多少层,或者去开发者选项去开启调制过渡重绘,看看有多少界面是红色的,甚至是深红色。布局嵌套的越多,初始化布局用于计算,定位,绘制所花的时间越多,如果这个界面再有些图片要显示,那这个界面肯定会有卡顿现象。ConstraintLayout 其实就是Google为了解决布局嵌套次数太多推出的一个新的布局,它有一个最大的好处,就是扁平化布局——几乎不需要任何嵌套。

约束

ConstraintLayout翻译过来也叫约束布局,通过各种约束条件,定位view的位置。在约束这一特点上,跟RelativeLayout的布局有些相似。

ConstraintLayout 的解析,扁平化布局_第1张图片

先看个效果图:


ConstraintLayout 的解析,扁平化布局_第2张图片
图1

这个布局很简单,按钮2在按钮1右边,如果用RelativeLayout实现的话:



        

换成ConstraintLayout实现:




    

实现的效果都是一样的。那我们来对比下这两种实现方式有什么不同:
btn1中有三个属性:
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/btn2"
app:layout_constraintTop_toTopOf="parent"
从字面上就可以理解:

app:layout_constraintLeft_toLeftOf:左对齐,设置为parent为与父布局左对齐。 对应RelativeLayout类似于layout_alignParentLeft和layout_alignLeft的两个属性;

app:layout_constraintRight_toLeftOf:右对左,简单的就可以理解为btn1的右对齐btn2的左侧。对应RelativeLayout类似于layout_toLeftOf属性;

app:layout_constraintTop_toTopOf:上对齐:设置为parent为与父布局左对齐。对应RelativeLayout类似于layout_alignParentTop和layout_alignTop两个属性;

btn2与btn1不同在于这个:

app:layout_constraintLeft_toRightOf:左对右:简单的理解为btn2左对齐btn1的右侧。对应RelativeLayout类似于layout_toRightOf属性;

其中从这个简单的例子上就可以看出ConstraintLayout和RelativeLayout有些不同:ConstraintLayout可以相互约束,RelativeLayout却不能相互“约束”——btn2可以toRightOf btn1,而btn1却不能toLeftOf btn2。

原因在于RelativeLayout布局解析是从上而下,btn1解析的时候还没解析btn2,而btn1却引用btn2的id,就会报错;解析到btn2的时候btn1已经解析完毕,btn2引用btn1的id时就不会有问题。

注:如果发生编译错误,那就在错误的地方使用@+id替换@id

如果只实现单约束条件:即btn2的左对齐btn1的右,也是可以实现上述需求。那相互约束又有什么优势?在btn1有一个属性app:layout_constraintLeft_toLeftOf="parent",那如果我们对btn2也来一个右对齐父布局,会产生什么效果呢?

添加这个属性后,布局变成这样:


ConstraintLayout 的解析,扁平化布局_第3张图片
图2

而这个功能却是LinearLayout使用weight才能实现。其实这些也都还好吧,切换下布局也就能实现,但如果是下面这种效果图呢:


ConstraintLayout 的解析,扁平化布局_第4张图片
图3

按钮1和按钮2紧挨着,仅仅使用单个RelativeLayout或单个LinearLayout和两个Button布局,是无法实现图3的功能,要么添加一个View辅助布局,要么布局嵌套实现,但无论是哪种实现,都会增加额外的开销。
那如果用ConstraintLayout又该如何实现呢?看代码:


    

对比原来的布局文件,细心的朋友会发现多了一行:

app:layout_constraintHorizontal_chainStyle="packed"

没错,只需要添加这个属性,就可以实现图3的样式。

Chains链

上面提到一个属性就行解决的问题,到底是何方神圣?其实这就是ConstraintLayout特有的Chains链。Chains链大家可以简单的理解为在同一级(垂直或者水平)条件下,相近的View有相互约束的存在,便构成了Chains链结构,位于最左侧或者最上侧的View被称为链头,想要实现特殊样式,只需要更改链头的chainStyle属性即可,且只有链头的chainStyle属性更改会生效,其他View的chainStyle属性设置了也不会起作用。
链头的属性有三种:

  • spread

  • packed

  • spread_inside

默认不设置的话属性是spread。我们拿一张官方图片来看看:


ConstraintLayout 的解析,扁平化布局_第5张图片
Chains链

这张图片有三种样式比较容易理解,Spread Chain、Spread Inside Chain、Packed Chain,这三种样式只需要更改链头的chainStyle属性值就可以实现,那么其余两种又是怎么实现的呢?

Weighted Chain:
ConstraintLayout 的解析,扁平化布局_第6张图片
图4

代码实现:



    

只需要将btn2和btn3的width设置为0dp即可。这地方需要做一点答疑,为什么是0dp而不是match_parent?因为在ConstraintLayout中,若要使用约束条件,match_parent被0dp所替代。意思就是说,如果你将btn2的0dp换成了match_parent,那么整个布局里就只有btn存在,其余布局全部遮挡,就是图5的样子:

ConstraintLayout 的解析,扁平化布局_第7张图片
图5

再做一步变化:


ConstraintLayout 的解析,扁平化布局_第8张图片
图6

按钮1:按钮2:按钮3 = 1:3:2的样式分布,看代码:



    

多了这个属性app:layout_constraintHorizontal_weight,这个属性就比较容易理解了,跟LinearLayout的weight相似,对剩余空间的划分利用,但前提是view的width必须设置成0dp才会有效。

Packed Chain With Bias

看其名知其意,肯定是与bias有关。老规矩,先看图:


ConstraintLayout 的解析,扁平化布局_第9张图片
图7

看代码:



    

首先app:layout_constraintHorizontal_chainStyle = packed,其次所有的View宽都是自适应,然后链头多一个属性app:layout_constraintHorizontal_bias = 0.2。简单的讲,链头左边的距离占剩余未利用的空间的比例。设置为0.2意思就是链头左边空余空间占据总剩余未利用空间的1/5。

学会上面这些,ConstraintLayout基本就掌握了80%了,来写布局啥的基本上没有什么问题,剩下的一些东西都是为了提高效率。

辅助工具类Group

业务开发中,难免会遇到这种情况,根据条件判断,某些View要展示,某些View要隐藏。基本上这些强关联性的View都会具有相同的visibility属性,那么按照以前的写法,就要每个View都需要设置一遍visibility才行。但是,现在有了Group辅助类,就不需要那么麻烦了。
先看代码:


app:constraint_referenced_ids这个属性的意思是关联具有相同visibility属性View的id,关联上之后,给Group设置android:visibility="gone",三个Button就会全部隐藏,从控制三个View到控制一个View,效率提升很多,代码也变简洁了。

辅助类Guideline

顾名思义,就是一个辅助线,当约束条件变的比较困难的时候,可以使用Guideline来辅助定位,可以使用属性android:orientation来确定是横向还是纵向。
GuideLine有三个属性来控制定位:

  • layout_constraintGuide_begin:距离左侧或者顶部的距离;
  • layout_constraintGuide_end:距离右侧或底部的距离;
  • layout_constraintGuide_percent:百分比控制,指定在父控件中的宽度或高度的百分比,如0.8,表示距离顶部或者左侧的80%的距离。

辅助类Barrier

Barrier 是用多个 View 作为限制源来决定自身位置的一种辅助线。字面上不好理解,先看图:


ConstraintLayout 的解析,扁平化布局_第10张图片
图8

类似于这种场景其实app中还是比较常见的,左边TextView后边EditText,输入一些信息。通常的做法是左边的ViewGroup限制宽度,右边一个ViewGroup保证所有的EditText起始位置保持一致;亦或是找到TextView中文案最长一个的宽度,将其他TextView的宽度也设置成最长的那个,然后右边EditText,也能做到图8这样的效果。但问题是最长的宽度,你怎么能够确定就是那一个呢?如果哪天某个文案变了,原本宽度最大的变成第二大的,是不是代码也要跟着改一大堆保证右侧EditText起始位置一致呢?那么现在,有了Barrier 一切都变的简单了。上代码:



    

    

    

四个View,然后:


先看app:barrierDirection这个属性是指定限制的方向,有6个值,分别是 , 有 left,right,top,bottom,start,end。app:constraint_referenced_ids这个属性跟Group的类似,约束View的id。

Barrier约束两个TextView,同时设置属性为right,表示在俩TextView的右侧,而俩EditText以Barrier为约束条件,标明在Barrier的右侧。而Barrier的位置能根据俩TextView文案的长短,自动的调整位置。这样无论我们改多少个字,改哪个TextView,都不需要去担心右侧对齐的问题了。上两张图,便于理解:


ConstraintLayout 的解析,扁平化布局_第11张图片
ConstraintLayout 的解析,扁平化布局_第12张图片

虚线就是Barrier自动调整的位置。

百分比视图

ConstraintLayout确实很强大,将百分比的布局功能也加了进来,而且设置起来非常方便。使用场景最多的就是首页Banner,先上图:


ConstraintLayout 的解析,扁平化布局_第13张图片

Banner宽高比设置为横向的16:9,上代码:


app:layout_constraintDimensionRatio就是这个关键属性,有三种写法:

  • 直接写X:X,表示宽高比;
  • 写了H与不写H效果一致,也是表示宽高比;
  • 写了W,表示高宽比

补充:2019/12/30

1.最近有这么一个需求,要求一个TextView后面跟一个Image,image紧跟着TextView,如图:
ConstraintLayout 的解析,扁平化布局_第14张图片
TIM图片20191230160641.png

当TextView超出一行时,TextView多余文案用...代替,而image固定在最右边。如图:
ConstraintLayout 的解析,扁平化布局_第15张图片
TIM图片20191230160659.png

乍一看好像也没啥吧,实际写的时候就会有各种各样的问题,要么是image不能跟随要么就是image被顶没了。如果按照以往的想法,那就是重写一个布局,实时计算空间预留,不过很麻烦,现在用ConstraintLayout就可以完美实现:



    

关键属性是TextView的android:layout_width="0dp"app:layout_constraintWidth_default="wrap",这两个属性组合起来使用的大致意思就是,既能让TextView的宽跟随文案长度自动适配,又能让文案顶到一行时TextView的宽度固定,不再占用其他布局空间。
以上俩属性是关键属性,如果要做到图上那种效果,还有俩属性需要设置:app:layout_constraintHorizontal_chainStyle="packed"app:layout_constraintHorizontal_bias="0"。这俩属性就不再多做介绍了。

PS:新版本中,app:layout_constraintWidth_default="wrap"这个属性已经被标为了过期的属性,替代属性:app:layout_constrainedWidth="true",要想达到同样的效果,需要设置TextView的宽为wrap:android:layout_width="wrap_content"


结语

纸上得来终觉浅,看过了,看不懂,看的迷迷糊糊的朋友们,最好是去开个Demo自己的体验一下,你会发现,原来就是这么回事啊!
ConstraintLayout 的解析,扁平化布局_第16张图片

你可能感兴趣的:(ConstraintLayout 的解析,扁平化布局)