ConstraintLayout的使用小结+结合抽象布局优化

当小白的我一开始接触到ConstraintLayout(约束布局,以下简称为cons),我被这扁平化、减少嵌套的特殊布局吸引了。虽然我对其他布局了解不是太深入,但在很多博客、书籍里面都说过减少嵌套可以优化性能。在看了好几篇关于cons的实用技巧的博客,并且尝试学着文中布局之后,我成了cons的忠实拥趸。刚好想做一个复利计息的界面,按其他布局来计算应该会有至少三个层级的,我打算用cons下的一个层级就给搞定,然后真的做出来了 0.0 。实际上也修改了不少次,参考了不少博客,才做出来的。cons可能在上手时会有些难度,很多属性很抽象不好记忆,但对着下面我推荐的博客,假设一个嵌套层级较多的场景界面来练习几次,就会发现cons真的很好用。后面会有我觉得不错的博客,和我觉得我用的比较多的属性与小技巧。之后是我用cons一个层级完成的功能UI界面与代码(结合了抽象布局),可以参考一下,还有些我对cons使用的心得。


一、学习cons的博客推荐

首先推荐一下三篇关于cons的很棒的博客。

郭霖大神的简洁易懂的入门教程:Android新特性介绍,ConstraintLayout完全解析

接下来这俩篇就是进阶版了,因为是根据新版本来写的,所以用法更多样(俩篇侧重点不同,一起食用最好):

ConstraintLayout在项目中实践与总结

ConstraintLayout 用法全解析


二、cons常用属性和小技巧

从大佬们的博客里面学到了很多有趣的用法,这也是我想用cons的一方面原因。

约束布局的定位属性有十三种,我的记忆方式是 :

layout_constraint(本组件的部位)_to(建立连接的组件部位)Of:(建立连接的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

建议不用带left、right的属性,这样影响适配,横向只用start和end就好了。

cons中的每个属性建议至少使用俩个定位属性,这样大部分的界面都可以实现了。我的做法是一般都start和top位置的定位属性,这样对大多数左对齐的界面方便管理和编写代码。

另一个我觉得很实用的方法就是辅助线用法,我在看博客时学习了俩种辅助线的用法,一种是1px的view,用法如下:

<View
        android:id="@+id/view2"
        android:layout_width="match_parent"
        android:layout_height="1px"
        android:layout_marginBottom="280dp"
        app:layout_constraintBottom_toBottomOf="@id/view"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

 

 

 

辅助线用法

 

 

 上方一整块背景是用一个view+颜色背景实现的,以为cons设置背景色只能在主布局中设置背景色,所以想实现如图的样式需要设置辅助线。view2本是

app:layout_constraintBottom_toBottomOf="@id/view"

但用android:layout_marginBottom="280dp",将它在原有的位置上上升了280dp,这个也刚好是view的高度,然后再把所有的组件一个个在view2下方放置就好了。

第二个辅助线就是官方的GuideLine,使用方法如下。

<android.support.constraint.Guideline
        android:id="@+id/gl_top"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:orientation="horizontal"
        app:layout_constraintGuide_begin="0dp"/>
<!--界面最上方的横向辅助线-->
    <android.support.constraint.Guideline
        android:id="@+id/gl_left"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:orientation="vertical"
        app:layout_constraintGuide_begin="@dimen/margin_m" />
<!--界面最左边的纵向辅助线-->
    <android.support.constraint.Guideline
        android:id="@+id/gl_right"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:orientation="vertical"
        app:layout_constraintGuide_end="@dimen/margin_m" />
<!--界面最右边的纵向辅助线-->
    <android.support.constraint.Guideline
        android:id="@+id/gl_between"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.5" />
<!--界面最中间的纵向辅助线-->

我在项目中定义了四条辅助线,可以看出其实 gl_top跟第一个方法用的view2是一样的作用,这个等一下说。

我觉得辅助线的精髓在于下面这俩条属性。

app:layout_constraintGuide_percent="0.5"

app:layout_constraintGuide_end="@dimen/margin_m"

这是给的辅助线的定位,既有百分比,又有具体距离的设置。百分比的设置让适配变得方便,具体距离的设置让组建的位置更为精准。有这个辅助线是实现稳定的组件们的线性排列变得方便。场景可参考项目图中的俩个edittext和俩个button的横向线性排列,用GuideLine后取代了线性布局的嵌套。

你们肯定要问了,设置view2这种抖机灵的小部件有何用,还消耗性能!不不不,我在刚才写博客时也想了一下,View2这种“自制辅助线”到底有何用呢。GuideLine只能在cons主布局中做组件的定位的辅助线,它的百分比和距离定位都是相对于主布局的,没法哪一个组件具体的辅助线,这是我们的“自制辅助线”就派上用场了,与某个组件建立约束,修改特定的margin值,就成了专属辅助线,在此辅助线建立的约束可以消去与其他组件的耦合(cons下的每个组件之间的关联太紧密,取消一个如牵一发而动全身,这也是很多人不想用cons的原因,但这个也是它的优势,精准定位,适配方便)。

解释一下,其实在我做的这个项目中,界面最上方的辅助线其实是不需要的(我也是写博客时才发觉),之所以没代码中没去掉是为了简单地演示一下辅助线的用法,cons中一个点(每个组件包括parent都有上下左右四个点)都可以建立多个约束的,所以组件覆盖另一个组件也是ok的(这好像是帧布局的功能0.0)。

此外cons很多实用方法,在我推荐的博客上可以看到。cons真的很强大!


三、参考项目

以下是我强迫症般地使用cons的界面截图,该项目的idea仅做参考(我知道不怎么好看。。。)

仅一个层级的cons

可以看出整个布局毫无嵌套,仅一个层级。真机界面看下图

真机界面

当然,这个界面不算复杂,但只用一个层级就能做出来,那估计只有cons布局才能做到了。

你也许会说,这里有include,还有viewstub这种抽象布局,那里面肯定会有嵌套的啊。

客官莫急,且看代码。

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <View
        android:id="@+id/view"
        android:layout_width="match_parent"
        android:layout_height="280dp"
        android:background="@color/colorView"
        android:fitsSystemWindows="true"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <View
        android:id="@+id/view2"
        android:layout_width="match_parent"
        android:layout_height="1px"
        android:layout_marginBottom="280dp"
        app:layout_constraintBottom_toBottomOf="@id/view"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <android.support.constraint.Guideline
        android:id="@+id/gl_left"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:orientation="vertical"
        app:layout_constraintGuide_begin="@dimen/margin_m" />

    <android.support.constraint.Guideline
        android:id="@+id/gl_right"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:orientation="vertical"
        app:layout_constraintGuide_end="@dimen/margin_m" />

    <android.support.constraint.Guideline
        android:id="@+id/gl_between"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.5" />

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolBar"
        android:layout_width="match_parent"
        android:layout_height="43dp"
        android:layout_marginTop="8dp"
        app:layout_constraintTop_toTopOf="@+id/view2"></android.support.v7.widget.Toolbar>

    <include layout="@layout/calculator_merge" />

    <Button
        android:id="@+id/btn_calculate"
        android:layout_width="130dp"
        android:layout_height="43dp"
        android:layout_marginTop="@dimen/margin_l"
        android:background="@drawable/btn_small"
        android:text="@string/btn_calculate"
        app:layout_constraintEnd_toEndOf="@+id/gl_right"
        app:layout_constraintTop_toBottomOf="@+id/view" />
    <ViewStub
        android:id="@+id/scroll_text_view_stub"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout="@layout/info_scroll"
        android:layout_marginTop="73dp"
        android:layout_marginRight="@dimen/margin_s"
        android:layout_marginLeft="@dimen/margin_s"
        android:layout_marginBottom="@dimen/margin_s"
        app:layout_constraintEnd_toStartOf="@+id/gl_right"
        app:layout_constraintStart_toStartOf="@+id/gl_left"
        app:layout_constraintTop_toBottomOf="@+id/view"
        app:layout_constraintBottom_toTopOf="@id/btn_generating_bill"
        />
    <Button
        android:id="@+id/btn_generating_bill"
        android:layout_width="191dp"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:background="@drawable/btn_big"
        android:text="@string/btn_generating_bill"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <ViewStub
        android:id="@+id/calculator_view_stub"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout="@layout/calculator_viewstub_btn"
        app:layout_constraintEnd_toEndOf="@id/gl_right"
        app:layout_constraintTop_toBottomOf="@id/view"/>
</android.support.constraint.ConstraintLayout>

以上是主要布局代码,看起来很少是因为蓝色部分组件给放进了calculator_merge里面了,而merge并不会增加层级,只是把代码分开,优化UI方便修改,都在一个xml里面看起来太臃肿了。merge支持主布局的规则,当然也支持主布局是cons。下面就是merge部分代码。

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:parentTag="android.support.constraint.ConstraintLayout"
    tools:showIn="@layout/calculator_activity"
    >
    <TextView
        android:id="@+id/tv_hint1"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/margin_l"
        android:layout_marginHorizontal="@dimen/margin_s"
        android:text="@string/tv_hint_loan"
        android:textSize="@dimen/text_size_small"
        android:textStyle="bold"
        app:layout_constraintStart_toStartOf="@id/gl_left"
        app:layout_constraintTop_toBottomOf="@id/toolBar" />

    <EditText
        android:id="@+id/et_loan"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:hint="@string/et_hint_loan"
        android:inputType="number"
        android:textSize="@dimen/text_size_big"
        android:background="@null"
        android:layout_marginVertical="@dimen/margin_s"
        android:layout_marginStart="@dimen/margin_s"
        app:layout_constraintEnd_toEndOf="@id/gl_right"
        app:layout_constraintStart_toStartOf="@id/gl_left"
        app:layout_constraintTop_toBottomOf="@id/tv_hint1" />

    <TextView
        android:id="@+id/tv_hint2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="@dimen/margin_s"
        android:layout_marginTop="@dimen/margin_m"
        android:text="@string/tv_hint_interest"
        android:textSize="@dimen/text_size_small"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@id/gl_left"
        app:layout_constraintTop_toBottomOf="@id/et_loan" />

    <EditText
        android:id="@+id/et_interest"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:hint="@string/et_hint_interest"
        android:inputType="number"
        android:textSize="@dimen/text_size"
        android:background="@null"
        android:layout_marginVertical="@dimen/margin_s"
        android:layout_marginStart="@dimen/margin_s"
        app:layout_constraintEnd_toEndOf="@id/gl_right"
        app:layout_constraintStart_toStartOf="@id/gl_left"
        app:layout_constraintTop_toBottomOf="@id/tv_hint2" />

    <TextView
        android:id="@+id/tv_hint3"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/margin_m"
        android:paddingHorizontal="@dimen/margin_s"
        android:text="@string/tv_hint_start_end"
        android:textSize="@dimen/text_size_small"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@id/gl_left"
        app:layout_constraintTop_toBottomOf="@id/et_interest" />

    <EditText
        android:id="@+id/et_start_time"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/margin_s"
        android:paddingStart="@dimen/margin_m"
        android:background="@null"
        android:hint="@string/et_start_time"
        android:inputType="date"
        app:layout_constraintEnd_toStartOf="@+id/btn_start_time"
        app:layout_constraintStart_toStartOf="@id/gl_left"
        app:layout_constraintTop_toBottomOf="@id/tv_hint3" />

    <Button
        android:id="@+id/btn_start_time"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:background="@drawable/calculator_btn_bg"
        android:paddingHorizontal="@dimen/margin_s"
        app:layout_constraintTop_toBottomOf="@id/tv_hint3"
        app:layout_constraintEnd_toEndOf="@id/gl_between"
        />

    <EditText
        android:id="@+id/et_end_time"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/margin_s"
        android:paddingStart="@dimen/margin_m"
        android:background="@null"
        android:hint="@string/et_end_time"
        android:inputType="date"
        app:layout_constraintEnd_toStartOf="@+id/btn_end_time"
        app:layout_constraintStart_toStartOf="@id/gl_between"
        app:layout_constraintTop_toBottomOf="@id/tv_hint3" />

    <Button
        android:id="@+id/btn_end_time"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:background="@drawable/calculator_btn_bg"
        android:paddingEnd="@dimen/margin_s"
        android:paddingHorizontal="@dimen/margin_s"
        app:layout_constraintEnd_toEndOf="@id/gl_right"
        app:layout_constraintTop_toBottomOf="@id/tv_hint3" />
</merge>

最重要的是下面一行代码,是我大佬回复我评论告诉我的。没有这段代码无法建立约束关系。

tools:parentTag="android.support.constraint.ConstraintLayout"

写在merge里面,唯一的不好就是太烦了,很多地方没有代码提示,可以现在主布局代码里面写好,再移过去。

说完了merge,再说说viewstub吧。先上代码

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="wrap_content"
    android:layout_width="wrap_content"
    android:orientation="horizontal"
    xmlns:tools="http://schemas.android.com/tools"
    tools:showIn="@layout/calculator_activity">
    <Button
        android:id="@+id/btn_refresh"
        android:layout_width="57dp"
        android:layout_height="43dp"
        android:layout_marginEnd="@dimen/margin_m"
        android:layout_marginTop="@dimen/margin_l"
        android:background="@drawable/btn_small"
        android:text="清空"
        android:onClick="refreshClick"
         />

    <Button
        android:id="@+id/btn_goon"
        android:layout_width="57dp"
        android:layout_height="43dp"
        android:layout_marginTop="@dimen/margin_l"
        android:background="@drawable/btn_small"
        android:text="计算"
        android:onClick="goonClick"
         />
</LinearLayout>

这是calculator_viewstub_btn.xml的代码。(好了,你不要再说了,我知道这是个滚动嵌套布局,,,,)然而这个嵌套却可以不需要initView时加载。viewstub可以算作一个占位符,它是是一个不可见的,大小为0的View,需要inflate()方法将它加载出来,然而只要在合适的时机加载,就不会影响UI初始化的性能了。我这个隐藏button的idea可以让ViewStub的使用场景变得更多。具体的实现我会在我的下一篇博客《ViewStub的奇淫巧技 》详细介绍。

四、对cons的使用心得与看法

通过我的介绍各位看官可以看出cons的强大,很多常用布局它都可以实现,当它跟抽象布局配合起来更强大。因为强大,它的源码也是多得惊人,虽然官方称ConstraintLayout在测量/布局阶段比RelativeLayout好40%左右,但是在onDraw()的阶段呢。所以深度嵌套肯定首选cons。cons的使用在刚上手的时候是比较繁琐,但建议你在使用多层嵌套布局时去试着多练习一下cons的使用,为了优化!

这个界面把三个抽象布局标签都用了,也算圆满了,希望这种布局优化可以给你提供一点思路。

写博客真得让我关注到了做项目时很多不注意的细节,感觉对cons的记忆更深刻了,这也许就是博客让人提升的地方吧。

望指正,谢谢!

参考:https://blog.csdn.net/stevenhenry/article/details/78954981

           https://blog.csdn.net/xyz_lmn/article/details/14524567