上图是网易100分的选课首页,在Banner图的下部是推荐类目模块,其中数学、语言、小低和小高分别是推荐类目Item。可见每个类目的子类目个数是不确定的,根据个数的不同,子类目的排列方式也不一样。
现在我们来实现Item的布局。如果用LinearLayout、RelativeLayout和FrameLayout去实现Item布局,我目前想到的最低也需要两层布局。如下所示:
<Relative>
<ImageView />
<TextView />
<LinearLayout>
<TextView />
<TextView />
<TextView />
</LinearLayout>
<LinearLayout>
<TextView />
<TextView />
</LinearLayout>
</Relative>
可以发现没有一种布局容器是可以单靠自己搞定这个布局的,需要嵌套不同布局。这样布局层级增加,布局计算时间也加长了。这些都是传统布局存在的问题,概括起来有以下三点:
正是由于目前布局容器存在的问题,我们需要寻找一种可以解决这些问题的布局容器。正好,ConstraintLayout可以。
ConstraintLayout,中文称约束布局,在2016年Google I/O大会时提出,2017年2月发布正式版,目前稳定版本为1.0.2。约束布局作为Google今后主推的布局样式,可以完全替代其他布局,降低页面布局层级,提升页面渲染性能。
ConstraintLayout支持最低Android Studio版本是2.2,但是有些属性在2.2的布局编辑器上不支持编辑,如比例和baseline等约束。所以推荐使用2.3的版本,当然3.0的版本那就更好了。要使用ConstraintLayout,需要在项目中进行如下配置:
repositories {
maven {
url 'https://maven.google.com'
}
}
dependencies {
compile 'com.android.support.constraint:constraint-layout:1.0.2'
}
按照上述配置好环境后,我们就可以在项目中使用ConstraintLayout了。有两种方式使用:
layout转换的方式使用
首先,打开一个非ConstraintLayout的布局文件,切换到Design Tab
在Component Tree窗口,选中要转换的layout文件根布局,点击右键,然后选择Convert layout to ConstraintLayout
直接新建一个layout文件使用
通过如下方式引入约束布局:
<android.support.constraint.ConstraintLayout
/>
ConstraintLayout的布局属性,乍一看有很多,其实可以分为8个部分,下面一一介绍。
layout_constraintLeft_toLeftOf
layout_constraintLeft_toRightOf
layout_constraintRight_toLeftOf
layout_constraintRight_toRightOf
layout_constraintTop_toTopOf
layout_constraintTop_toBottomOf
layout_constraintBottom_toTopOf
layout_constraintBottom_toBottomOf
layout_constraintBaseline_toBaselineOf
layout_constraintStart_toEndOf
layout_constraintStart_toStartOf
layout_constraintEnd_toStartOf
layout_constraintEnd_toEndOf
以上这些属性,用于设置一个控件相对于其他控件、Guideline或者父容器的位置。以layout_constraintLeft_toLeftOf
为例,其中layout_
部分是固定格式,主要的信息包含在下面两部分:
constraintLeft
表示对当前控件的左边进行约束设置。XXX
指定被依赖对象用于参考的属性。如toLeftOf="parent"
:表示当前控件相对于父容器的左边进行约束设置。
ConstraintLayout的相对位置布局比较灵活,相比于RelativeLayout,ConstraintLayout可以通过layout_constraintBaseline_toBaselineOf
设置两个控件之间的文字相对于baseline对齐。一个布局效果的例子,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_relative_position"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="zr.com.constraintdemo.normal.RelativePositionActivity">
<Button
android:id="@+id/btn_A"
android:text="A"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
/>
<Button
android:text="在A下方,与A左对齐"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/btn_A"
app:layout_constraintLeft_toLeftOf="@id/btn_A"
android:layout_marginTop="32dp"
/>
<Button
android:text="在A上方,与A居中对齐"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/btn_A"
app:layout_constraintLeft_toLeftOf="@id/btn_A"
app:layout_constraintRight_toRightOf="@id/btn_A"
android:layout_marginBottom="32dp"
/>
<Button
android:text="baseline对齐"
android:layout_width="wrap_content"
android:layout_height="80dp"
app:layout_constraintBaseline_toBaselineOf="@id/btn_A"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginLeft="8dp"
android:gravity="bottom"
/>
<Button
android:text="水平居中对齐"
android:layout_width="wrap_content"
android:layout_height="80dp"
android:gravity="bottom"
app:layout_constraintTop_toTopOf="@id/btn_A"
app:layout_constraintBottom_toBottomOf="@id/btn_A"
app:layout_constraintLeft_toRightOf="@id/btn_A"
android:layout_marginLeft="16dp"
/>
</android.support.constraint.ConstraintLayout>
在ConstraintLayout中,控件除了可以设置普通的边距属性,还可以设置当控件依赖的控件GONE之后的边距属性。即我们可以理解可以根据被依赖控件是否GONE的状态,设置两种边距值。分别通过如下属性进行设置:
android:layout_marginStart
android:layout_marginEnd
android:layout_marginLeft
android:layout_marginTop
android:layout_marginRight
android:layout_marginBottom
layout_goneMarginStart
layout_goneMarginEnd
layout_goneMarginLeft
layout_goneMarginTop
layout_goneMarginRight
layout_goneMarginBottom
这种特性,可以比较方便实现一些特定的需求,且无需代码中进行额外设置。如B控件依赖A,A距离父容器左边20dp,B在A右边,距离A为20dp。需求当A设置为GONE之后,B距离父容器左边60dp。这在ConstraintLayout中实现起来就很简单,对B同时设置如下属性即可:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_margin"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="zr.com.constraintdemo.normal.MarginActivity">
<Button
android:id="@+id/btn_a"
android:text="A"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="100dp"
/>
<Button
android:text="B"
android:textAllCaps="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@id/btn_a"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginLeft="20dp"
app:layout_goneMarginLeft="60dp"
android:layout_marginTop="100dp"
/>
</android.support.constraint.ConstraintLayout>
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
例子:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_center_position"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="zr.com.constraintdemo.normal.CenterPositionActivity">
<Button
android:text="水平居中"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
/>
<Button
android:text="垂直居中"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
/>
<Button
android:text="水平垂直居中"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
/>
</android.support.constraint.ConstraintLayout>
在设置控件的居中属性之后,通过偏移属性可以设置让控件更偏向于依赖控件的某一方,偏移设置为0~1之间的值。相应属性:
layout_constraintHorizontal_bias // 水平偏移
layout_constraintVertical_bias // 垂直偏移
例子:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_bias"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="zr.com.constraintdemo.normal.BiasActivity">
<Button
android:text="水平偏移30%"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintHorizontal_bias="0.3"
/>
<Button
android:text="垂直偏移30%"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0.3"
/>
<Button
android:text="水平垂直偏移70%"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0.7"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintHorizontal_bias="0.7"
/>
</android.support.constraint.ConstraintLayout>
可见性这个属性大家应该很熟悉,但是约束布局的可见性属性和其它布局相比,存在以下区别:
当控件设为GONE时,被认为尺寸为0。可以理解为布局上的一个点。
若GONE的控件对其它控件有约束,则约束保留并生效,但所有的边距(margin)会清零。
wrap_content
,根据内容计算合适大小match_parent
,填充满父布局,此时设置的约束都不生效了。(早之前的约束布局版本貌似不允许在其子view中使用match_parent属性,但是我写文章的时候发现也是可以用上去的)
layout_constraintWidth_min
和layout_constraintHeight_min
:设置最小值layout_constraintWidth_max
和layout_constraintHeight_max
:设置最大值layout_constraintWidth_percent
和layout_constraintHeight_percent
:设置控件相对于父容器的百分比大小(1.1.0开始支持)。使用之前需要先设置为百分比模式,然后设置设置宽高值为0~1之间。
设置为百分比模式的属性:
app:layout_constraintWidth_default="percent"
app:layout_constraintHeight_default="percent"
强制约束
当一个控件设为wrap_content时,再添加约束尺寸是不起效果的。如需生效,需要设置如下属性为true:
app:layout_constrainedWidth=”true|false”
app:layout_constrainedHeight=”true|false”
看个具体例子:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_dimen"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="zr.com.constraintdemo.normal.DimenActivity">
<Button
android:id="@+id/btn_1"
android:text="minWidth设置为200dp"
android:textAllCaps="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:minWidth="200dp"
/>
<Button
android:id="@+id/btn_2"
android:text="设置为MATCH_CONSTRAINT"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/btn_1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
/>
<Button
android:id="@+id/btn_3"
android:textAllCaps="false"
android:text="layout_constrainedWidth开启"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/btn_2"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constrainedWidth="true"
app:layout_constraintWidth_min="300dp"
/>
<Button
android:id="@+id/btn_4"
android:textAllCaps="false"
android:text="layout_constrainedWidth关闭"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/btn_3"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintWidth_min="300dp"
/>
<Button
android:id="@+id/btn_5"
android:textAllCaps="false"
android:text="宽50%高30%布局"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/btn_4"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintWidth_default="percent"
app:layout_constraintHeight_default="percent"
app:layout_constraintWidth_percent="0.5"
app:layout_constraintHeight_percent="0.3"
/>
</android.support.constraint.ConstraintLayout>
控件可以定义两个尺寸之间的比例,目前支持宽高比。 前提条件是至少有一个尺寸设置为0dp,然后通过layout_constraintDimentionRatio
属性设置宽高比。设置方式有以下几种:
如果宽高都设置为0dp,也可以用ratio设置。这种情况下控件会在满足比例 约束的条件下,尽可能填满父布局。
下面看个例子:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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:context="zr.com.constraintdemo.normal.RatioActivity">
<Button
android:id="@+id/btn_1"
android:text="宽高比设置为2:1"
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
/>
<Button
android:id="@+id/btn_2"
android:text="宽高都设置为0dp,高宽比是16:9"
android:textAllCaps="false"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="H,16:9"
app:layout_constraintTop_toBottomOf="@id/btn_1" />
</android.support.constraint.ConstraintLayout>
链这个概念是约束布局新提出的,它提供了在一个维度(水平或者垂直),管理一组控件的方式。
多个view在同一个方向上双向引用。如下图所示:水平方向A、B、C,A位于B左边,B位于A右边,他们就是一对双向引用。同理B和C也是。
双向引用布局代码如下所示。A通过app:layout_constraintRight_toLeftOf="@+id/btn_2"
引用右边的B,B通过app:layout_constraintLeft_toRightOf="@+id/btn_1"
引用A。
<Button
android:id="@+id/btn_1"
android:text="A"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/tv_spread"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/btn_2"
app:layout_constraintHorizontal_chainStyle="spread_inside"
/>
<Button
android:id="@+id/btn_2"
android:text="B"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/tv_spread"
app:layout_constraintLeft_toRightOf="@+id/btn_1"
app:layout_constraintRight_toLeftOf="@+id/btn_3"
/>
...
最左边或最上面的控件,链的属性由链头控制。
通过layout_constraintHorizontal_chainStyle
和layout_constraintVertical_chainStyle
在链的第一个元素上设置。默认spread样式。如上所示,A作为链头,设置了chainStyle:app:layout_constraintHorizontal_chain
。
几种链的样式如下图所示:
链的布局代码比较多,大家可以看demo。主要是通过修改链头的chainStyle样式改变链的类型。
相关阅读:
ConstraintLayout在项目中实践与总结(下篇)
本文来自网易实践者社区,经作者郑睿授权发布。