转载文章:
约束布局ConstraintLayout看这一篇就够了
万字长文 - 史上最全ConstraintLayout(约束布局)使用详解
使用ConstraintLayout遇到的些许问题
1.ConstraintLayout介绍
约束布局ConstraintLayout 是一个ViewGroup,可以在Api9以上的Android系统使用它,它的出现主要是为了解决布局嵌套过多的问题,以灵活的方式定位和调整小部件。从 Android Studio 2.3 起,官方的模板默认使用 ConstraintLayout。
ConstraintLayout 官方文档
2.ConstraintLayout属性及使用
2.1 位置约束
2.1.1 基本方向约束
基本方向约束(部件对上下左右方向的约束),例如:
如图所示,TextView2在TextView1的右边,TextView3在TextView1的下面,这个时候在布局文件里面应该这样写:
<TextView
android:id="@+id/TextView1"
android:text="TextView1" />
<TextView
android:id="@+id/TextView2"
app:layout_constraintLeft_toRightOf="@+id/TextView1" />
<TextView
android:id="@+id/TextView3"
app:layout_constraintTop_toBottomOf="@+id/TextView1" />
上面代码中在TextView2里用到了app:layout_constraintLeft_toRightOf="@+id/TextView1"这个属性,他的意思是把TextView2的左边约束到TextView1的右边,如下图所示:
同理TextView3在TextView1的下面,就需要用到app:layout_constraintTop_toBottomOf="@+id/TextView1",即把TextView3的上面约束到TextView1的下面。
下面来看看相对定位的常用属性:
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_constraintStart_toEndOf
layout_constraintStart_toStartOf
layout_constraintEnd_toStartOf
layout_constraintEnd_toEndOf
2.1.2 文本基线对齐
layout_constraintBaseline_toBaselineOf比较特殊的约束,Baseline指的是文本基线,举个例子:
如图所示,两个TextView的高度不一致,但是又希望他们文本对齐,这个时候就可以使用layout_constraintBaseline_toBaselineOf,代码如下:
<TextView
android:id="@+id/TextView1"
<TextView
android:id="@+id/TextView2"
app:layout_constraintLeft_toRightOf="@+id/TextView1"
app:layout_constraintBaseline_toBaselineOf="@+id/TextView1"/>
效果如下:
2.1.3 角度定位
角度定位指的是可以用一个角度和一个距离来约束两个空间的中心。举个例子:
<TextView
android:id="@+id/TextView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/TextView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintCircle="@+id/TextView1"
app:layout_constraintCircleAngle="120"
app:layout_constraintCircleRadius="150dp" />
上面例子中的TextView2用到了3个属性:
app:layout_constraintCircle="@+id/TextView1"
目标控件id
app:layout_constraintCircleAngle="120"(角度)
对于目标的角度(0-360)
app:layout_constraintCircleRadius="150dp"(距离)
到目标中心的距离
指的是TextView2的中心在TextView1的中心的120度,距离为150dp,效果如下:
2.1.4 百分比偏移
有的时候我们需要让控件在父布局的水平方向或垂直方向的百分之多少的位置,可以使用如下属性:
app:layout_constraintHorizontal_bias="" 水平偏移 取值范围是0-1的小数
app:layout_constraintVertical_bias="" 垂直偏移 取值范围是0-1的小数
举个例子:
<TextView
android:id="@+id/TextView1"
app:layout_constraintHorizontal_bias="0.3"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
假如现在要实现水平偏移,给TextView1的layout_constraintHorizontal_bias赋一个范围为 0-1 的值,假如赋值为0,则TextView1在布局的最左侧,假如赋值为1,则TextView1在布局的最右侧,假如假如赋值为0.5,则水平居中,假如假如赋值为0.3,则更倾向于左侧。
2.1.5 居中
在RelativeLayout中,把控件放在布局中间的方法是把layout_centerInParent设为true,而在ConstraintLayout中的写法是:
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
意思是把控件的上下左右约束在布局的上下左右,这样就能把控件放在布局的中间了。
2.2 边距
2.2.1 外边距(margin)
ConstraintLayout的边距常用属性如下:
android:layout_marginStart
android:layout_marginEnd
android:layout_marginLeft
android:layout_marginTop
android:layout_marginRight
android:layout_marginBottom
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/TextView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</android.support.constraint.ConstraintLayout>
TextView1的左边和上边约束到parent的左边和上边10dp,效果如下:
2.2.3 内边距(padding)
和外边距相似,只不过是相对于本身的边距,相当于在自己的外边加一片空白。
android:layout_margin
android:layout_marginStart
android:layout_marginLeft
android:layout_marginTop
android:layout_marginEnd
android:layout_marginRight
android:layout_marginBottom
2.2.3 goneMargin
goneMargin主要用于约束的控件可见性被设置为gone的时候使用的margin值,属性如下:
layout_goneMarginStart
layout_goneMarginEnd
layout_goneMarginLeft
layout_goneMarginTop
layout_goneMarginRight
layout_goneMarginBottom
举个例子:
假设TextView2的左边约束在TextView1的右边,并给TextView2设一个app:layout_goneMarginLeft="10dp",代码如下:
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:id="@+id/TextView1"
<TextView
android:id="@+id/TextView2"
app:layout_constraintLeft_toRightOf="@+id/TextView1"
app:layout_goneMarginLeft="10dp" />
</android.support.constraint.ConstraintLayout>
效果如下,TextView2在TextView1的右边,且没有边距。
这个时候把TextView1的可见性设为gone,效果如下:
TextView1消失后,TextView2有一个距离左边10dp的边距。
2.3 控件尺寸
2.3.1 尺寸限制
控件的尺寸可以通过3种不同方式指定:
-
使用指定的尺寸
-
使用wrap_content,让控件自己计算大小
当控件的高度或宽度为wrap_content时,可以使用下列属性来控制最大、最小的高度或宽度:
android:minWidth 最小的宽度
android:minHeight 最小的高度
android:maxWidth 最大的宽度
android:maxHeight 最大的高度
注意!当ConstraintLayout为1.1版本以下时,使用这些属性需要加上强制约束,如下所示:
app:constrainedWidth=”true”
app:constrainedHeight=”true”
-
使用 0dp (MATCH_CONSTRAINT)
官方不推荐在ConstraintLayout中使用match_parent,可以设置 0dp (MATCH_CONSTRAINT) 配合约束代替match_parent,举个例子:
2.3.2 比例宽高(Ratio)
当宽或高至少有一个尺寸被设置为0dp时,可以通过属性layout_constraintDimensionRatio设置宽高比,举个例子:
<TextView
android:id="@+id/TextView1"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
宽设置为0dp,宽高比设置为1:1,这个时候TextView1是一个正方形,效果如下:
除此之外,在设置宽高比的值的时候,还可以在前面加W或H,分别指定宽度或高度限制。 例如:
app:layout_constraintDimensionRatio="H,2:3"指的是 高:宽=2:3
app:layout_constraintDimensionRatio="W,2:3"指的是 宽:高=2:3
默认是宽:高
2.4 链
如果两个或以上控件通过下图的方式约束在一起,就可以认为是他们是一条链(图为横向的链,纵向同理)。
用代码表示:
<TextView
android:id="@+id/TextView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/TextView2" />
<TextView
android:id="@+id/TextView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@+id/TextView1"
app:layout_constraintRight_toLeftOf="@+id/TextView3"
app:layout_constraintRight_toRightOf="parent" />
<TextView
android:id="@+id/TextView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@+id/TextView2"
app:layout_constraintRight_toRightOf="parent" />
3个TextView相互约束,两端两个TextView分别与parent约束,成为一条链,效果如下:
一条链的第一个控件是这条链的链头,我们可以在链头中设置 layout_constraintHorizontal_chainStyle来改变整条链的样式。chains提供了3种样式,分别是:
CHAIN_SPREAD —— 展开元素 (默认);
CHAIN_SPREAD_INSIDE —— 展开元素,但链的两端贴近parent;
CHAIN_PACKED —— 链的元素将被打包在一起。
如图所示:
上面的例子创建了一个样式链,除了样式链外,还可以创建一个权重链。
可以留意到上面所用到的3个TextView宽度都为wrap_content,如果我们把宽度都设为0dp,这个时候可以在每个TextView中设置横向权重layout_constraintHorizontal_weight(constraintVertical为纵向)来创建一个权重链,如下所示:
<TextView
android:id="@+id/TextView1"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/TextView2"
app:layout_constraintHorizontal_weight="2" />
<TextView
android:id="@+id/TextView2"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@+id/TextView1"
app:layout_constraintRight_toLeftOf="@+id/TextView3"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintHorizontal_weight="3" />
<TextView
android:id="@+id/TextView3"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@+id/TextView2"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintHorizontal_weight="4" />
效果如下:
3.辅助工具
转载:
万字长文 - 史上最全ConstraintLayout(约束布局)使用详解
3.1 Guideline(参考线)
Guideline是一条参考线,可以帮助开发者进行辅助定位,并且实际上它并不会真正显示在布局中,像是数学几何中的辅助线一样,使用起来十分方便,经常被使用,Guideline也可以用来做一些百分比分割之类的需求,有着很好的屏幕适配效果,Guideline有水平和垂直方向之分,位置可以使用针对父级的百分比或者针对父级位置的固定距离
android:orientation="horizontal|vertical" 辅助线的对齐方式
app:layout_constraintGuide_percent="0-1" 距离父级宽度或高度的百分比(小数形式)
app:layout_constraintGuide_begin="" 距离父级起始位置的距离(左侧或顶部)
app:layout_constraintGuide_end="" 距离父级结束位置的距离(右侧或底部)
<androidx.constraintlayout.widget.Guideline
android:id="@+id/Guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.5" />
<TextView
android:id="@+id/A"
android:layout_width="120dp"
android:layout_height="80dp"
android:background="#FFFFFF"
android:gravity="center"
android:text="A"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/Guideline" />
代码效果如下:
3.2 Barrier(屏障)
首先我们先看一个布局,布局有三个文本视图:左边的textView1和textView2;右边的textView3。textView3被限制在textView1的末尾,这工作得很好——它完全根据我们需要来定位和大小textView3。布局的效果如下:
但是如果textView2中的文本比textView1中的文本长(现实中经常出现),就会出现以下情况:
textView2被textView3覆盖,显然这不是我们想要的情况。如果我们在textView1和textView2的bottom后面设置一个barrier,然后把textView3约束到这个barrier后面,就解决了这个问题。
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
android:text="hshsf"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginTop="8dp"
android:text="hsahffdbfdbnd"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView1" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="end"
app:constraint_referenced_ids="textView2,textView1" />
<TextView
android:id="@+id/textView3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:text="safbasbjfasllaksfn.s;djbgo;grw;ogu;ovfabjkv
jbnoeab';orgnabeobiourenb;oie;tbn;etobi;euobneotbnetobuoetbhetiav
boaejieiortbueotabuaoetb;rebhaoer;ugae;gaergieagure;ghr/ebogreobg
boerhbov;taeghvertgbejor;oguofgbrnoa'ubroeughforefgkrgfkkrsggsehj
sdhgayeribfgregfidudrghdraigrehgiirdgh;rdijerglirhgfkerifgbergfgb
sifgiergbrikgirgrghrgerf"
app:layout_constraintLeft_toRightOf="@+id/barrier"
app:layout_constraintTop_toTopOf="parent" />
效果图如下:
3.3 Group(组)
工作当中常常会有很多个控件同时隐藏或者显示的场景,传统做法要么是进行嵌套,对父布局进行隐藏或显示,要么就是一个一个设置,这显然都不是很好的办法,ConstraintLayout中的Group就是来解决这个问题的。Group的作用就是可以对一组控件同时隐藏或显示,没有其他的作用,它的属性如下:
app:constraint_referenced_ids="id,id" 加入组的控件id
例如:
上面效果图的代码如下:
<TextView
android:id="@+id/textView1"
android:layout_width="120dp"
android:layout_height="60dp"
android:layout_marginTop="56dp"
android:gravity="center"
android:text="textView1"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.115"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView2"
android:layout_width="120dp"
android:layout_height="60dp"
android:layout_marginTop="280dp"
android:gravity="center"
android:text="textView2"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.758"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView3"
android:layout_width="120dp"
android:layout_height="60dp"
android:layout_marginTop="164dp"
android:gravity="center"
android:text="textView3"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.437"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Group
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible"
app:constraint_referenced_ids="textView1,textView2,textView3" />
如上述代码,textView1、textView2和textView3受Group控制,当Group的visiablity为visiable时,它们都是正常显示的,设置为gone时,它们都会隐藏。
3.4 Placeholder(占位符)
Placeholder的作用就是占位,它可以在布局中占好位置,通过app:content=""属性,或者动态调用setContent()设置内容,来让某个控件移动到此占位符中
示例代码如下:
<TextView
android:id="@+id/textView"
android:layout_width="100dp"
android:layout_height="60dp"
android:gravity="center"
android:text="textView"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Placeholder
android:layout_width="100dp"
android:layout_height="60dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
代码效果图如下:
当我们设置android:id="@+id/textView"或者调用setContent()时,控件A就会被移动到Placeholder中,当然在布局中使用app:content=""显然就失去了它的作用。
3.5 Flow(流式虚拟布局)
2.6 Layer(层布局)
4.常见问题
使用ConstraintLayout遇到的些许问题
1、控件的宽、高都不可设置为 match_parent,否则所有的约束失效。
ConstraintLayout里尽量不要出现match_parent,用“0dp”代替即可
2、欲使设置的margin生效,须指定控件的约束对象。例如,欲使marginTop生效,须设定layout_constraintTop_toTopOf
3、被Group持有的所有控件,其visiblity只能由Group决定
4、以当前布局中不能准确知道高度的View为标准,例如:在一个布局中,左边TextView的内容是固定的文字,右边TextView的内容须根据具体业务进行动态设置,则其高度是不确定的,这种情况下高度约束均以右边为中心
5、自定义组合控件使用ConstraintLayout作为布局的根时,使用LinearLayout作为自定义控件的父布局时会造成自定义封装的布局展示不正确(宽高变形,与测量方式有关)。推荐使用FrameLayout作为根布局,同时固定高度
6、欲使layout_constraintVertical_bias属性生效,须设置上下约束。同样欲使layout_constraintHorizontal_bias属性生效,须设置左右约束
7、若ConstraintLayout不是根布局,则其中的子控件的约束不能指定为 parent,但可指定为父控件ConstraintLayout的id
8、layout_marginLeft和layout_marginRight不起作用:控件左右都约束了parent,但有时左右margin不起作用,可能是因为layout_width设置成了match_parent或者wrap_content,须改为match_constraint(即0dp)才会起作用
9、Guideline:
(1)layout_constraintGuide_percent属性值可以为负值,有时某些特殊的需求会用到
(2)对Guideline设置相对位置属性是不生效的,因此当我们想要一个相对于某个view的Guideline时,约束布局是不能满足的。此时可以用一个不可见的View作为Guideline的替代品来实现一些特殊布局需求,如布局重叠,这种方式可以弥补margin不能设置为负值的不足
10、Chain:
(1)在chainStyle=spread时才可以使用加权layout_constraintHorizontal_weight和layout_constraintVertical_weight 。
(2)注意Chain中的控件互相引用id的格式,是“@+id/button1”而非”@id/button1”。
(3)可以用Chain实现控件居中
11、注意View.inflate(getActivity(), R.layout.item, null)和LayoutInflater.from(getActivity()).inflate(R.layout.item, parent, false)区别,有时二者对ConstraintLayout布局会有不同的影响
12、在ConstraintLayout中,控件除了可以设置layout_marginXXX外,还可以设置其依赖的控件gone之后的margin,即layout_goneMarginXXX。就是说,可以根据被依赖控件的visibility,设置两种margin
13.ConstraintLayout没有负数布局,但是我们可以利用Space达到相同的效果