美文网首页UIAndroid开发Android技术知识
项目需求讨论 — ConstraintLayout 详细使用教程

项目需求讨论 — ConstraintLayout 详细使用教程

作者: 青蛙要fly | 来源:发表于2017-12-18 10:25 被阅读387次

    题外话

    最近转方向参与到物联网的项目去了。看的都是C++和Node.js,表示从头学习,又变成了一个小菜鸟了。所以好久没写过文章了。

    废话不多说,关于ConstraintLayout的文章网上一抓一大把,而且ConstraintLayout在16年就已经出来了,但是我一直没有试着去使用(别问我为什么不去使用,当然是因为懒啊)。毕竟前面的LinearLayout搭配RelativeLayout用习惯了,但是毕竟能减少布局的嵌套。还是要抱着多学习的方式去接触。所以写下文章作为总结。

    前言

    大家都知道AS在写相关布局的时候,有二种方式:

    1. 拖拽方式

    就是在这里进行拖控件,各种操作,因为在以前RelativeLayout和LinearLayout的年代,自己拖会自动帮我们添加各种属性值不说,而且还很不方便,但是对于ConstraintLayout来说添加各种约束在这里操作反而很方便,而且这里的功能面板也增加了很多新功能,方便了很多。

    当然我也不多说,贴上郭霖大神写得在这里功能面板里面对ConstraintLayout 各种操作方式:
    操作面板拖拽方式来使用ConstraintLayout

    2.编写代码

    这种更为大家使用,而我这里也更多的是直接写代码的方式。

    正文

    控件如何确定自己的位置

    1.直接确定控件左上角的坐标

    在约束布局中,一个控件如何来确定自己的位置呢,有人可能说直接写死让它在界面的(XXX,XXX)位置不就好了么。

    比如在拖拽界面,我们把一个TextView拖到了界面中间。

    我们发现这个TextView的确在中间了,这时候我们看下它的代码:

    <?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"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="我在哪里"
            tools:layout_editor_absoluteX="164dp"
            tools:layout_editor_absoluteY="263dp" />
    
    
    </android.support.constraint.ConstraintLayout>
    

    我们发现了:

    tools:layout_editor_absoluteX="164dp"
    tools:layout_editor_absoluteY="263dp"
    

    的确我们告诉了TextView的左上角的坐标,这个TextView的确可以确定了位置,但是这二个属性只是单纯的进行演示,在真机操作的时候是无效的,就像"tools:text"一样,可以在写布局的时候方便查看TextView显示的文字,但是实际运行app的时候不会有相应内容。

    而且我们也可以看到布局文件中有错误提示,也告诉我们在真实运行时候会跳到(0,0)位置:

    This view is not constrained, it only has designtime positions, so it will jump to (0,0) unless you add constraints less...

    2.告诉控件相邻的二个边的位置情况

    如下图所示:


    我们怎么来确定它们的位置?比如我们红色的矩形A,我们是不是告诉它:你的左边靠着外面界面的左边,你的顶边靠着外面界面的顶边(然后是不是A就处在现在这个位置了)。绿色的矩形B我们可以告诉它:你的右边靠着外面界面的右边,你的底边靠着外面界面的底边(然后B就处在了现在这个位置)。

    所以基本操作就是:确定某个控件二个边的位置(比如靠在哪个控件旁边)。

    我们来看最简单的基本操作:
    layout_constraint[自己位置]_[目标位置]="[目标ID]"

    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
    

    举个例子:

    比如我们A按钮已经确定好位置了。我们现在要放B按钮,就像我们上面说的,我们B按钮的二个边的位置,我们可以设置让B按钮的左边靠着A按钮的右边(相当于B按钮的左边与A按钮的右边处于同一位置)。

    <Button android:id="@+id/buttonA" ... />
    
    <Button android:id="@+id/buttonB" ...
        app:layout_constraintLeft_toRightOf="@+id/buttonA" />
    

    我们可以看到app:layout_constraintLeft_toRightOf="@+id/buttonA",B的leftidbuttonA的控件的right相同位置。所以B的左侧就和A的右侧贴在了一起。

    我们发现上面还有一个layout_constraintBaseline_toBaselineOf,直接看下图就可以理解所有相关的属性:

    如果是相对于父布局,我们也可以不写入另外一个控件的id值,直接填parent值就可以了

    <android.support.constraint.ConstraintLayout ...>
        <Button android:id="@+id/button" ...
         app:layout_constraintLeft_toLeftOf="parent"
         />
         
    <android.support.constraint.ConstraintLayout/>
    

    所以以上就是基本的操作。我们接下来看下其他的特殊属性。


    Margin值相关

    比如我们上面的A和B按钮通过了app:layout_constraintLeft_toRightOf拼接在一起了,但是我同时希望A和B按钮中间能空一些距离,如下图所示:

    我们可以直接使用:

    android:layout_marginStart
    android:layout_marginEnd
    android:layout_marginLeft
    android:layout_marginTop
    android:layout_marginRight
    android:layout_marginBottom
    

    这时候就又会有一个问题,如果这时候A的visiblegone,这时候B的位置就会自动往左边了。因为A的所占的宽度没有了(但是A在里面对于其他控件的约束性都是还是存在的)


    但是如果我的需求就是A隐藏后,B还是在这个位置(当然有些人可能会说你可以让B根据其他控件来确定位置),而且我的B的位置就是根据A来确定的。那我们怎么处理,我们可以设置B的以下属性,就是当A处于gone的时候,我们可以让B的margin值是根据以下的属性值:
    layout_goneMarginStart
    layout_goneMarginEnd
    layout_goneMarginLeft
    layout_goneMarginTop
    layout_goneMarginRight
    layout_goneMarginBottom
    
    

    位置约束不止二个边

    我们上面提过,二个边的位置确定好了(也可以说二个边的位置被约束了),我们就可以确定这个控件的相应位置,而且还可以通过margin的改变,来继续调节控件的位置。那如果我这时候是三个边约束或者四个边都约束了呢,比如:

    <android.support.constraint.ConstraintLayout ...>
        <Button android:id="@+id/button" ...
         app:layout_constraintLeft_toLeftOf="parent"
         app:layout_constraintRight_toRightOf="parent/>
         
    <android.support.constraint.ConstraintLayout/>
    

    我们让按钮的左边与父布局的左边对齐,让按钮的右边与父布局的右边对齐。这时候因为不是单纯的一边对齐,而是相同直线上的二个边都被约束了。所以按钮无法紧靠着左边的或者右边的其中一个边界,所以这时候,这个按钮就会居于二个约束边界的中间位置。如下图所示:


    也许也有人问,我想在这二个约束条件下时候不是处于正中间,而是处于左边三分之一的位置,这时候你可以使用:

    layout_constraintHorizontal_bias
    layout_constraintVertical_bias
    

    分别是水平和垂直方向上的所占比例。


    <android.support.constraint.ConstraintLayout ...>
        <Button android:id="@+id/button" ...
         app:layout_constraintHorizontal_bias="0.3"
         app:layout_constraintLeft_toLeftOf="parent"
         app:layout_constraintRight_toRightOf="parent/>
    </android.support.constraint.ConstraintLayout>
    

    圆形布局

    有些需求我们可能需要让控件以某个控件为中心,绕着进行布局,如下图所示:


    ConstarintLayout自带了这些功能,我们可以使用:

    layout_constraintCircle : 引用另一个控件的id
    layout_constraintCircleRadius : 距离另外一个控件中心的距离
    layout_constraintCircleAngle : 应该在哪个角度(从0到360度)
    

    例如:

    <Button android:id="@+id/buttonA" ... />
    <Button android:id="@+id/buttonB" ...
      app:layout_constraintCircle="@+id/buttonA"
      app:layout_constraintCircleRadius="100dp"
      app:layout_constraintCircleAngle="45" />
    

    尺寸限制(Dimensions constraints)

    1.对ConstraintLayout进行限制:

    您可以为ConstraintLayout本身定义最小和最大尺寸:

    android:minWidth设置布局的最小宽度
    android:minHeight设置布局的最小高度
    android:maxWidth设置布局的最大宽度
    android:maxHeight设置布局的最大高度
    

    这些最小和最大尺寸将在ConstraintLayout使用

    2.对内部的控件进行限制:

    可以通过以3种不同方式设置android:layout_widthandroid:layout_height属性来指定控件的尺寸:

    • 用特定的值(如123dp等)
    • 使用WRAP_CONTENT,它会要求控件计算自己的大小
    • 使用0dp,相当于“MATCH_CONSTRAINT”

    WRAP_CONTENT(在1.1中添加)

    如果设置为WRAP_CONTENT,则在1.1之前的版本中, 约束不会限制生成的尺寸值。但是在某些情况下,您可能需要使用WRAP_CONTENT,但仍然执行约束来限制生成的尺寸值。在这种情况下,你可以添加一个相应的属性:

    应用:layout_constrainedWidth =”真|假”
    应用:layout_constrainedHeight =”真|假”
    

    MATCH_CONSTRAINT尺寸(也就是0dp)(在1.1中添加)

    设置为MATCH_CONSTRAINT时,默认是大小是占用所有可用空间。有几个额外的修饰符可用:

    layout_constraintWidth_min和layout_constraintHeight_min:将设置此维度的最小尺寸
    layout_constraintWidth_max和layout_constraintHeight_max:将设置此维度的最大尺寸
    layout_constraintWidth_percent和layout_constraintHeight_percent:将设置此维度的大小为父级的百分比
    

    百分比尺寸(Percent Dimensions)

    说到Percent Dimensions就不得不说ConstraintLayout中的0dp问题,当控件设置为0dp的时候(0dp的称呼又叫match_constraint),默认的行为是撑开(spread),占满可用空间,但是这个行为是可以用layout_constraintWidth_default 属性来设置的。在 ConstraintLayout 1.0.x中,这个属性还可以把它设置为wrap。而到了1.1.x,它又有了一个新的值:percent,允许我们设置控件占据可用空间的百分比。

    (注意:这在1.1-beta1和1.1-beta2中layout_constraintWidth_default是必须的,但是如果percent属性被定义,则在以下版本中不需要,然后将layout_constraintWidth_percent或layout_constraintHeight_percent属性设置为介于0和1之间的值)

    下面的TextView控件将占据剩余宽度的50%和剩余高度的50%:

      <TextView
        android:id="@+id/textView6"
        android:layout_width="0dp"
        android:layout_height="0dp"
        
        app:layout_constraintHeight_default="percent"
        app:layout_constraintHeight_percent="0.5"
        
        app:layout_constraintWidth_default="percent"
        app:layout_constraintWidth_percent="0.5" />
    

    宽高比(Ratio)

    您还可以控制控件的height或者width这二个值,让其中一个值与另外一个值的成特定的比例。为此,需要至少将一个值设置为0dp(即,MATCH_CONSTRAINT),并将属性layout_constraintDimensionRatio设置为给定比率。例如:

    <Button android:layout_width="wrap_content"
       android:layout_height="0dp"
       app:layout_constraintDimensionRatio="1:1" />
    

    这样这个按钮的宽和高是一样大小的。

    Ratio可以设置为:

    • 浮点值,表示宽度和高度之间的比率
    • “宽度:高度”形式的比率

    如果两个维都设置为MATCH_CONSTRAINT(0dp),则也可以使用比率: 在这种情况下,系统设置满足所有约束条件的最大尺寸并保持指定的宽高比。

    为了约束一个特定的边,可以根据另一个边的大小来限定宽度或高度:
    可以通过在比率前面添加字母W(用于限制宽度)或H(用于限制高度),用逗号分隔来指示哪一边应该受到约束:

    <Button android:layout_width="0dp"
       android:layout_height="0dp"
       app:layout_constraintDimensionRatio="H,16:9"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintTop_toTopOf="parent"/>
    

    将按照16:9的比例设置按钮的高度,而按钮的宽度将匹配父布局的约束。


    链(Chains)

    链在单个轴(水平或垂直)中提供类似组的行为。

    • 创建一个链:
      如果一组小部件通过双向连接链接在一起,则认为它们是一个链,如下图所示,是一个具有二个控件的最小的链:

    • 链头:
      链由在链的第一个元素(链的“头”)上设置的属性控制:


      (头是水平链最左边的部件,也是垂直链最顶端的部件。)
    • 链样式: 在链的第一个元素上设置属性layout_constraintHorizontal_chainStylelayout_constraintVertical_chainStyle时,链的行为将根据指定的样式进行更改(默认为CHAIN_SPREAD)。

    例如:

    <?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">
        
    
        <TextView
            android:id="@+id/textView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="AAAA"
            app:layout_constraintHorizontal_chainStyle="spread"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toStartOf="@id/textView3"
            />
    
        <TextView
            android:id="@+id/textView3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="BBBB"
            app:layout_constraintEnd_toStartOf="@id/textView4"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toEndOf="@+id/textView2" />
    
        <TextView
            android:id="@+id/textView4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="CCCC"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/textView3" />
    </android.support.constraint.ConstraintLayout>
    

    效果如下:



    屏障 (Barrier)

    Barrier是一个虚拟的辅助控件,它可以阻止一个或者多个控件越过自己,就像一个屏障一样。当某个控件要越过自己的时候,Barrier会自动移动,避免自己被覆盖。

    关于这个控件其他文章有详细的介绍,我直接附上地址:
    ConstraintLayout之Barrier


    组(Group)

    Group帮助你对一组控件进行设置。最常见的情况是控制一组控件的visibility。你只需把控件的id添加到Group,就能同时对里面的所有控件进行操作。

    <android.support.constraint.ConstraintLayout ...>
      <TextView
        android:id=”@+id/text1" ... />
      <TextView
        android:id=”@+id/text2" ... />
      <android.support.constraint.Group
        android:id=”@+id/group”
        ...
        app:constraint_referenced_ids=”text1,text2" />
    </android.support.constraint.ConstraintLayout>
    

    此时如果我们调用group.setVisibility(View.GONE);那么text1 和 text2 都将不可见。


    Guideline

    ConstraintLayout的辅助对象的实用程序类。Guideline不会显示在设备上(它们被标记为View.GONE),仅用于布局。他们只能在ConstraintLayout中工作。

    指引可以是水平的也可以是垂直的:
    垂直指南的宽度为零,它们的ConstraintLayout父项的高度为零
    水平指南的高度为零,其ConstraintLayout父项的宽度为零
    定位准则有三种不同的方式:

    • 指定布局左侧或顶部的固定距离(layout_constraintGuide_begin)
    • 从布局的右侧或底部指定固定距离(layout_constraintGuide_end)
    • 指定布局的宽度或高度的百分比(layout_constraintGuide_percent)

    相应的代码为setGuidelineBegin(int,int),setGuidelineEnd(int,int)和setGuidelinePercent(int,float)函数。

    然后控件就可以被Guideline来约束。(换句话就是说弄了一个隐藏的View,来约束我们的控件,我们的控件相对的就更容易进行位置定位)。

    限制于垂直Guideline的按钮示例:

    <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">
    
        <android.support.constraint.Guideline
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/guideline"
                app:layout_constraintGuide_begin="100dp"
                android:orientation="vertical"/>
    
        <Button
                android:text="Button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/button"
                app:layout_constraintLeft_toLeftOf="@+id/guideline"
                android:layout_marginTop="16dp"
                app:layout_constraintTop_toTopOf="parent" />
    
    </android.support.constraint.ConstraintLayout>
    
    

    Placeholder

    大家具体使用可以看这篇文章:
    New features in ConstraintLayout 1.1.x
    我以下Placeholder内容也就转载这个文章里面的例子:

    Placeholder顾名思义,就是用来一个占位的东西,它可以把自己的内容设置为ConstraintLayout内的其它view。因此它用来写布局的模版,也可以用来动态修改UI的内容。

    用作模版:
    我们用Placeholder创建一个名为template.xml的模版:

    模版写好了我们来填充真正的东西。

    我们把刚才定义的模版include到真正的布局文件中,并且在这个布局文件中添加真实的控件,注意这里的控件无需添加任何约束,因为它们的位置是由Placeholder决定的。

    还有一点就是模版要放在被引用的所有控件之前:

    <?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:id="@+id/root"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        tools:context="com.app.androidkt.constraintlayoutb.MainActivity"
        tools:showIn="@layout/activity_main">
        <include layout="@layout/template" />
        <ImageView
            android:id="@+id/top_image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:scaleType="fitXY"
            android:src="@drawable/place_holder_demo" />
        
        <ImageButton
            android:id="@+id/save"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="16dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:srcCompat="@drawable/ic_save_black_24dp" />
     
        <ImageButton
            android:id="@+id/edit"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:srcCompat="@drawable/ic_edit_black_24dp" />
     
        <ImageButton
            android:id="@+id/cancel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="16dp"
     
            app:srcCompat="@drawable/ic_cancel_black_24dp" />
     
        <ImageButton
            android:id="@+id/delete"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="16dp"
     
            app:srcCompat="@drawable/ic_delete_black_24dp" />
     
     
    </android.support.constraint.ConstraintLayout>
    

    以上就是PlaceHolder的使用场景之一模版功能。

    动态替换:
    PlaceHolder还可以在Java代码中动态替换自己的内容:

    public class MainActivity extends AppCompatActivity {
      private Placeholder placeholder;
      private ConstraintLayout root;
      @Override
      public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ...
      }
      public void onClick(View view) {
        placeholder.setContentId(view.getId());
      }
    }
    

    如果结合过渡动画的话,就可以实现一些比较有趣的效果:

    public class MainActivity extends AppCompatActivity {
      private Placeholder placeholder;
      private ConstraintLayout root;
      @Override
      public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ...
      }
      public void onClick(View view) {
        TransitionManager.beginDelayedTransition(root);
        placeholder.setContentId(view.getId());
      }
    }
    

    下面是使用PlaceHolder结合过渡动画实现的效果:


    而这个Demo也是上面那篇文章作者附上的,Demo地址是PlaceHolder动态替换


    结语:

    还是老话,哪里不对。可以在留言处写出来。我会进行更正,哈哈。

    相关文章

      网友评论

      本文标题:项目需求讨论 — ConstraintLayout 详细使用教程

      本文链接:https://www.haomeiwen.com/subject/pdvpwxtx.html