你可能在之前的UI开发中遇到过以下问题:
1.1 确保SDK Tools中已经下载了ConstraintLayout(以下简称CL)的支持库:
1.2 gradle中增加对ConstraintLayout的依赖:
compile 'com.android.support.constraint:constraint-layout:1.0.2'
1.3 在使用到ConstraintLayout的xml文件头部添加标签:
xmlns:app="http://schemas.android.com/apk/res-auto"
1.4 如果xml能正常联想出ConstraintLayout,并且其子View能正常联想出ConstraintLayout的相关属性,说明ConstraintLayout已经成功依赖:
先定一个小目标:将RL / LL实现的需求,通过CL来实现一遍。虽然Android Studio 2.3已经支持将其他布局自动转换成CL:
但还是建议先亲自上手码一遍:
RL最常见的使用场景:我要控件B在控件A/父布局的上、下、左、右边,我要控件B跟控件A/父布局间距xxx dp。 e.g:控件B位于控件A右侧50dp:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_a"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/colorPrimary"/>
<TextView
android:id="@+id/tv_b"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_toRightOf="@id/tv_a"
android:layout_marginLeft="50dp"
android:background="@color/colorAccent"/>
</RelativeLayout>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_a"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/colorPrimary"/>
<TextView
android:id="@+id/tv_b"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginLeft="50dp"
app:layout_constraintLeft_toRightOf="@id/tv_a"
android:background="@color/colorAccent"/>
</android.support.constraint.ConstraintLayout>
看上去几乎没有什么差别。事实上,RL中所有的属性CL中都有与之对应的项目,稍加熟悉即可完成转换:
CL属性 | RL属性 |
---|---|
layout_constraintLeft_toLeftOf | layout_alignLeft |
layout_constraintLeft_toRightOf | layout_toRightOf |
layout_constraintRight_toLeftOf | layout_toLeftOf |
layout_constraintRight_toRightOf | layout_alignRight |
layout_constraintTop_toTopOf | layout_alignTop |
layout_constraintTop_toBottomOf | layout_below |
layout_constraintBottom_toTopOf | layout_above |
layout_constraintBottom_toBottomOf | layout_alignBottom |
layout_constraintBaseline_toBaselineOf | layout_alignBaseline |
layout_constraintStart_toEndOf | layout_toEndOf (API 17) |
layout_constraintStart_toStartOf | layout_alignStart (API 17) |
layout_constraintEnd_toStartOf | layout_toStartOf (API 17) |
layout_constraintEnd_toEndOf | layout_alignEnd (API 17) |
而相对于父布局的相对布局属性,CL的规则是:将父布局当做一个id=”parent”的对象来对待。也比较好理解:
CL属性 | RL属性 | ||||
---|---|---|---|---|---|
layout_constraintTop_toTopOf=”parent” | layout_alignParentTop=”true” | ||||
layout_constraintBottom_toBottomOf=”parent” | layout_alignParentBottom=”true” | ||||
layout_constraintLeft_toLeftOf=”parent” | layout_alignParentLeft=”true” | ||||
layout_constraintRight_toRightOf=”parent” | layout_alignParentRight=”true” | ||||
layout_constraintStart_toStartOf=”parent” | layout_alignParentStart=”true” | ||||
layout_constraintEnd_toEndOf=”parent” | layout_alignParentEnd=”true” | ||||
layout_constraintLeft_toLeftOf=”parent” | layout_constraintRight_toRightOf=”parent” | layout_centerHorizontal=”true” | |||
layout_constraintTop_toTopOf=”parent” | layout_constraintBottom_toBottomOf=”parent” | layout_centerVertical=”true” | |||
layout_constraintLeft_toLeftOf=”parent” | layout_constraintRight_toRightOf=”parent” | layout_constraintTop_toTopOf=”parent” | layout_constraintBottom_toBottomOf=”parent” | layout_centerInParent=”true” |
layout_weight属性常见的使用场景:我要控件A的左间距和右间距的比例为x:(1-x)。 e.g:控件A左间距和右间距的比例是3:7:
<TableLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:shrinkColumns="0,2">
<TableRow>
<View
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.3"/>
<TextView
android:id="@+id/tv_a"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/colorPrimary"/>
<View
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.7"/>
</TableRow></TableLayout>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_a"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/colorPrimary"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintHorizontal_bias="0.3"/>
</android.support.constraint.ConstraintLayout>
在设置了各类居中属性的基础上,通过layout_constraintHorizontal_bias和layout_constraintVertical_bias两个属性,可以简单直观的完成间距比例的设置。
原先,在未指定宽高具体数值的情况下,让View / ViewGroup按照比例动态调整宽高比,实现起来比较麻烦。你可能需要等到View / ViewGroup绘制出来后,拿到它的LayoutParams,获取固定边的长度,计算出被动边的长度,最后将LayoutParams set回去。而有了CL提供的layout_constraintDimensionRatio属性,一行xml即可搞定。
e.g:控件A按照宽高比4:3展示,宽为固定边,高为被动边:
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!--layout_constraintDimensionRatio的H/W表示被动调整的是高(H)或是宽(W)-->
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginLeft="100dp"
android:layout_marginRight="100dp"
app:layout_constraintDimensionRatio="H,4:3"
android:background="@color/colorPrimary"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
</android.support.constraint.ConstraintLayout>
上面的示例中,layout_width和layout_height都设置为了0dp,0dp在CL布局中等同于MATCH_CONSTRAINT——CL的一个新属性常量。在CL中,子View / ViewGroup无法使用match_parent属性。
MATCH_CONSTRAINT搭配不同的属性有不同的意义:
layout_goneMarginStart layout_goneMarginEnd layout_goneMarginLeft layout_goneMarginTop layout_goneMarginRight layout_goneMarginBottom
layout_goneMargin系列是CL中新加入的属性。相对布局的两个控件,其中一方Visibility == Gone时,另外一方将会根据layout_goneMargin系列属性的值重新规划自己的间距。比较常用于在相对布局中保持各个控件的位置。
e.g:控件B在控件A设置Visibility == Gone后保持在原来的位置:
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="100dp">
<TextView
android:id="@+id/tv_a"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@color/colorPrimary"
android:gravity="center"
android:text="A"
android:textSize="30sp"/>
<TextView
android:id="@+id/tv_b"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginLeft="50dp"
app:layout_goneMarginLeft="100dp"
app:layout_constraintLeft_toRightOf="@id/tv_a"
android:background="@color/colorAccent"
android:gravity="center"
android:text="B"
android:textSize="30sp"/>
</android.support.constraint.ConstraintLayout>
chain是CL中新加入的控件与控件间的关系。组成chain的多个控件,可以在同一方向上更加方便的完成复杂的布局要求。
多个控件组成chain需要满足以下条件:
听起来很绕。e.g:控件A、B、C在水平方向上组成chain:
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_a"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginRight="50dp"
app:layout_constraintRight_toLeftOf="@+id/tv_b"/>
<TextView
android:id="@+id/tv_b"
android:layout_width="50dp"
android:layout_height="50dp"
app:layout_constraintLeft_toRightOf="@id/tv_a"
app:layout_constraintRight_toLeftOf="@+id/tv_c"/>
<TextView
android:id="@+id/tv_c"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginLeft="50dp"
app:layout_constraintLeft_toRightOf="@id/tv_b"/>
</android.support.constraint.ConstraintLayout>
示例中,控件A和控件B通过 layout_constraintRight_toLeftOf=”@+id/tv_b”和layout_constraintLeft_toRightOf=”@id/tv_a” 达成了水平方向的相互依赖关系。控件B与控件C同理。同时,又由于控件A与B、控件B与C均是水平方向的chain关系,控件A、B、C三者在水平方向组成了一条chain。
在xml的Design窗口下,组成chain的控件间会出现一条链条:
水平方向chain最左边的控件和垂直方向chain最顶部的控件被成为head chain。通过对head chain添加chainStyle属性,可以设置该条chain在水平或垂直方向上的chainStyle:
chainStyle属性一共有三种:spread、spread_inside、packed。再配合其他属性,最终可以组成五种chain style:
chain style | 设置方式 |
---|---|
Spread Chain | chainStyle = “spread” |
Spread Inside Chain | chainStyle = “spread_inside” |
Packed Chain | chainStyle = “packed” |
Packed Chain with Bias | chainStyle = “packed”layout_constraintHorizontal_bias layout_constraintVertical_bias |
Weighted Chain | chainStyle = “spread” layout_constraintHorizontal_weight layout_constraintVertical_weight layout_width = “0dp” layout_height = “0dp” |
官方的这张图已经比较清楚的展示了chain style各自的布局效果:
前四种chain style的设置和效果都比较简单,不再赘述。 重点介绍下Weighted Chain,Weighted Chain的设置方式相对比较复杂,以水平方向的chain为例:
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_a"
android:layout_width="0dp"
android:layout_height="50dp"
android:background="@color/colorPrimary"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintHorizontal_weight="0.33"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/tv_b"/>
<TextView
android:id="@+id/tv_b"
android:layout_width="0dp"
android:layout_height="50dp"
app:layout_constraintHorizontal_weight="0.33"
app:layout_constraintLeft_toRightOf="@id/tv_a"
app:layout_constraintRight_toLeftOf="@+id/tv_c"/>
<TextView
android:id="@+id/tv_c"
android:layout_width="0dp"
android:layout_height="50dp"
app:layout_constraintHorizontal_weight="0.33"
app:layout_constraintLeft_toRightOf="@id/tv_b"
app:layout_constraintRight_toRightOf="parent"/>
</android.support.constraint.ConstraintLayout>
除此以外,Weighted Chain还有以下特征:
官方称,CL相较于RL,在onMeasure() / onLayout()上的性能开销提升了40%:
为此,笔者也做了一个简单的性能试验来验证: 分别用CL和RL构造了一个3 × 2的相对布局矩阵,布局矩阵中的控件均使用wrap_content自适应大小,并设置有margin,使用ListView不断的绘制:
同时,使用API 24新加入的OnFrameMetricsAvailableListener回调,监听Window在渲染时,在onMeasure() / onLayout()上实际花费的时间。最终数据如下:
抛开绝对数值,CL相对RL,在onMeasure() / onLayout()上大致减少了10%的时间,并没有官方宣传的那么明显。可能是测试的布局嵌套并不是很深,亦或者布局中的控件并不是很多。
使用CL开发也有一段时间了,个人觉得CL与RL、LL、TL这些老前辈相比,在按比例布局、线性布局上面的支持更加完善,相关开发痛点可以用较少的xml描述完成了。简单的相对布局上,抛开微小的性能优势,CL和RL几乎没有什么差距,两者可以无缝转换。较复杂的相对布局上,CL相较RL代码不够直观,写出来的xml可读性比较差,chain + constraint相对布局属性的组合想要实现与嵌套RL相同的效果,往往需要更多的xml代码。许多人认为CL的出现就是为了替换RL,个人觉得倒是更适合替换TL,替换RL可能需要功能更加强大的chain才能解决。 但不管怎么说,越来越多的使用CL是趋势。
不足之处,请多指教。
https://android-developers.googleblog.com/2017/08/understanding-performance-benefits-of.html
https://developer.android.com/reference/android/support/constraint/ConstraintLayout.html
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。