ConstraintLayout在项目中实践与总结(下篇)

达芬奇密码2018-07-10 09:40


3.4 Guideline

可以理解为布局辅助线,用于布局辅助,不在设备上显示。

有垂直和水平两个方向(android:orientation=“vertical/horizontal”)

  • 垂直:宽度为0,高度等于父容器
  • 水平:高度为0,宽度等于父容器

有三种放置Guideline的方式:

  • 给定距离左边或顶部一个固定距离(layout_constraintGuide_begin
  • 给定距离右边或底部一个固定距离(layout_constraintGuide_end
  • 给定宽高一个百分比距离(layout_constraintGuide_percent

看例子:

<?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/activity_guideline"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="zr.com.constraintdemo.GuidelineActivity">

    <!-- 垂直Guideline -->
    <android.support.constraint.Guideline
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/guideline"
        app:layout_constraintGuide_percent="0.5"
        android:orientation="vertical"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:text="GuideLine左边"
        android:textAllCaps="false"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/btn_1"
        app:layout_constraintRight_toLeftOf="@+id/guideline"
        android:layout_marginTop="16dp"
        app:layout_constraintTop_toTopOf="parent"
        />

    <Button
        android:text="GuideLine右边"
        android:textAllCaps="false"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/btn_2"
        app:layout_constraintLeft_toRightOf="@+id/guideline"
        android:layout_marginTop="16dp"
        app:layout_constraintTop_toTopOf="parent"
        />

    <!-- 水平Guideline -->
    <android.support.constraint.Guideline
        android:id="@+id/h_guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintGuide_begin="200dp"
        />

    <Button
        android:text="Guideline上面"
        android:textAllCaps="false"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toTopOf="@id/h_guideline"
        app:layout_constraintLeft_toLeftOf="parent"
        />

    <Button
        android:text="Guideline下面"
        android:textAllCaps="false"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/h_guideline"
        app:layout_constraintLeft_toLeftOf="parent"
        />

</android.support.constraint.ConstraintLayout>

四、开始实践

说了这么多,那么约束布局用起来到底怎么样呢?下面我们来实践下:

前面类目的Item布局具体实现

我们先来分析下类目Item,可以将类目Item分为两个部分:父类目和子类目两部分。父类目包括图片icon和文字描述。子类目包含根据个数布局可变的按钮。很明显,父类目通过约束布局的相对位置约束设置可以实现。子类目中的子控件,可以以父布局中的某个控件和子类目中其他子控件为参照物(依赖参照对象)实现布局。总共放置两排的按钮,第一排3个,第二排2个,宽度设置为MATH_CONSTRAINT。然后在代码中根据子类目的个数,设置相应按钮的可见性即可实现Item根据子类目个数展示不同布局的效果。

布局XML:

<?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:paddingTop="7.5dp"
    android:paddingBottom="7.5dp"
    android:paddingLeft="12.5dp"
    android:paddingRight="12.5dp"
    android:clipToPadding="false"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/img_icon"
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:layout_marginLeft="5dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:src="@mipmap/ic_launcher"
        />

    <TextView
        android:id="@+id/tv_parent_category_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="5dp"
        android:text="少儿编程"
        android:textSize="15sp"
        android:textColor="#333333"
        app:layout_constraintBottom_toBottomOf="@id/img_icon"
        app:layout_constraintLeft_toRightOf="@id/img_icon"
        app:layout_constraintTop_toTopOf="@id/img_icon" />

    <!-- 子类目布局开始 -->
    <Button
        android:id="@+id/btn_one"
        android:layout_width="0dp"
        android:layout_height="40dp"
        android:layout_marginTop="10dp"
        android:text="A"
        app:layout_constraintTop_toBottomOf="@id/img_icon"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/btn_two" />

    <Button
        android:id="@+id/btn_two"
        android:layout_width="0dp"
        android:layout_height="40dp"
        android:text="B"
        app:layout_constraintLeft_toRightOf="@id/btn_one"
        app:layout_constraintRight_toLeftOf="@+id/btn_three"
        app:layout_constraintTop_toTopOf="@id/btn_one" />

    <Button
        android:id="@+id/btn_three"
        android:layout_width="0dp"
        android:layout_height="40dp"
        android:text="C"
        app:layout_constraintLeft_toRightOf="@id/btn_two"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="@id/btn_two" />

    <!-- 第二排 -->
    <Button
        android:id="@+id/btn_four"
        android:layout_width="0dp"
        android:layout_height="40dp"
        android:layout_marginTop="10dp"
        android:text="D"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/btn_five"
        app:layout_constraintTop_toBottomOf="@id/btn_one" />

    <Button
        android:id="@+id/btn_five"
        android:layout_width="0dp"
        android:layout_height="40dp"
        android:text="E"
        app:layout_constraintLeft_toRightOf="@+id/btn_four"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="@id/btn_four" />


</android.support.constraint.ConstraintLayout>

实现效果:

下一道练习

要求:图片宽高比16:9,图片宽度固定110dp。

分析:宽高比16:9,需要比例布局;其他都是一些位置关系,用约束布局相对位置的一些约束可以实现。

具体实现:

布局XML:

<?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="wrap_content"
    android:paddingLeft="15dp"
    android:paddingTop="12dp">

    <ImageView
        android:id="@+id/iv_course"
        android:layout_width="110dp"
        android:layout_height="0dp"
        android:scaleType="fitXY"
        android:src="@mipmap/test"
        app:layout_constraintDimensionRatio="16:9"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_course_name"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="15dp"
        android:ellipsize="end"
        android:maxLines="2"
        android:textColor="#333333"
        android:textSize="15sp"
        app:layout_constraintLeft_toRightOf="@id/iv_course"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="@id/iv_course"
        tools:text="六年级单元过关检测六年级单元过关检测六年级单元过关检测" />

    <TextView
        android:id="@+id/tv_signature"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginRight="15dp"
        android:layout_marginTop="5dp"
        android:ellipsize="end"
        android:maxLines="1"
        android:textColor="#666666"
        android:textSize="12sp"
        app:layout_constraintLeft_toLeftOf="@id/tv_course_name"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv_course_name"
        tools:text="签名" />

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginRight="15dp"
        android:layout_marginTop="5dp"
        android:ellipsize="end"
        android:maxLines="1"
        android:textColor="#666666"
        android:textSize="12sp"
        app:layout_constraintLeft_toLeftOf="@id/tv_course_name"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv_signature"
        tools:text="内容内容内容内容内容内容内容内容内容内容" />

    <TextView
        android:id="@+id/tv_current_price"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:maxLines="1"
        android:textColor="#f6454a"
        android:textSize="15sp"
        app:layout_constraintLeft_toLeftOf="@id/tv_course_name"
        app:layout_constraintTop_toBottomOf="@id/tv_content"
        tools:text="¥ 480" />

    <TextView
        android:id="@+id/tv_origin_price"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="5dp"
        android:maxLines="1"
        android:textColor="#999999"
        android:textSize="12sp"
        app:layout_constraintBottom_toBottomOf="@id/tv_current_price"
        app:layout_constraintLeft_toRightOf="@id/tv_current_price"
        tools:text="¥ 1480" />

</android.support.constraint.ConstraintLayout>

实现截图:

复杂度升级

要求:图片宽度占整个布局30%,宽高比16:9。

分析:看到30%,首先考虑的是百分比布局,但是图片右边的view较多,每个都是设置一边百分比,实在是麻烦。因此,可以考虑使用Guideline,设置Guideline垂直,并距离父容器左边30%的距离,之后布局通过Guideline设置约束即可。

布局XML:

<?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="wrap_content"
    android:paddingLeft="15dp"
    android:paddingTop="12dp">

    <android.support.constraint.Guideline
        android:id="@+id/guideline"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <ImageView
        android:id="@+id/iv_course"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:scaleType="fitXY"
        android:src="@mipmap/test"
        app:layout_constraintDimensionRatio="16:9"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="@id/guideline"
        app:layout_constraintTop_toTopOf="parent" />
    ...
</android.support.constraint.ConstraintLayout>

复杂度再升级

要求:在之前基础上,底部加一根横线用于分隔,要求线与上面最近的控件距离是15dp。

分析:由于文字内容是可变的,当文字内容多的时候,线可能距离文字近;若文字不多,线也可能距离图片近。这个时候,基于当前最新1.0.2稳定版本的约束布局已经不能满足我们实现一层布局了,还是需要将图片和文字整体放入一个布局容器中,然后横线依赖这个布局容器设置约束实现,嵌套好像在所难免了。然而,当约束布局1.1.0稳定版本发布时,这问题也可以得到解决。我们先来看看在1.1.0上是怎么实现的:

<?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="wrap_content"
    android:paddingLeft="15dp"
    android:paddingTop="12dp">

    ...

    <android.support.constraint.Barrier
        android:id="@+id/barrier"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:barrierDirection="bottom"
        app:constraint_referenced_ids="iv_course, tv_origin_price"
        />

    <View
        android:layout_width="match_parent"
        android:layout_height="1px"
        android:background="#d8d8d8"
        android:layout_marginLeft="15dp"
        android:layout_marginTop="12dp"
        app:layout_constraintTop_toBottomOf="@id/barrier"
        />

</android.support.constraint.ConstraintLayout>

原来可以通过Barrier实现,那么Barrier是什么?请往下看。

五、性能怎么样?

本文主要介绍ConstraintLayout的使用,因此也不大篇幅讲述性能相关内容。

  • 直观可见的一点是,同样一种复杂布局,相对于传统布局方式,ConstraintLayout的布局层级减少了。
  • 具体一些性能的对比,如渲染速度和计算次数等,可以看这篇文章《了解使用 ConstraintLayout 的性能优势》。通过结论可知使用了ConstraintLayout,布局计算次数降低了,渲染速度也相应提升了。

六、布局编辑器

从Android studio 2.2版本开始,布局编辑器支持拖拽的方式进行约束布局。但是在2.2上布局编辑器还不是很完善,部分约束不能设置,只能通过xml输入方式实现。因此推荐用版本为2.3或者更高的Android studio。

限于篇幅,这里就不展开介绍布局编辑器了。在这里推荐两篇文章,分别是ConstraintLayout 终极秘籍(下) Android新特性介绍,ConstraintLayout完全解析。看完这两篇,大家应该对布局编辑器就会有比较深入的了解了。

七、ConstraintLayout使用小结

在使用约束布局的过程中,有一些需要强调的点和碰到的一些坑分享给大家。

7.1 margin只能设置正值或者0,负值无效

我们之前实现重叠布局时,会通过设置负的margin值实现。但是在约束布局中,负的margin值不会生效,只能设置0或者大于0的值,小于0也当作0处理。

7.2 链的书写方式注意

一般布局我们都是遵守先定义,后使用原则,但是约束布局实现链时,这个原则就遵守不了了。这个时候如果还是按照常规的@id/btn_2的方式指定依赖控件(这个控件在当前控件之后声明的),就会报Error:(23, 46) No resource found that matches the given name错误。解决方案其实很简单,只需要修改指定方式如下:@+id/btn_2即可。

7.3 ConstraintSet动画Api支持等级

在代码中设置控件约束,可以通过ConstraintSet实现。约束变了之后,布局肯定会跟着变。TransitionManager.beginDelayedTransition提供了平滑动画变换布局的能力,但是只支持Api 19及以上的版本。

7.4 自定义guideLine

对Guideline设置相对位置属性是不生效的,因此当我们想要一个相对于某个view的Guideline时,约束布局是不能满足我们的要求的。 看Guideline源码:

public class Guideline extends View {
    public Guideline(Context context) {
        super(context);
        super.setVisibility(8);
    }
    ...    
}

发现Guideline是一个不可见的view,那么我们可以布局时放置一个不可见的view来作为Guideline的替代品,实现一些特殊布局要求。如布局重叠:

<?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:id="@+id/btn_a"
        android:text="A"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:background="@color/colorAccent"
        />

    <View
        android:id="@+id/view"
        android:layout_width="match_parent"
        android:layout_height="1px"
        app:layout_constraintBottom_toBottomOf="@id/btn_a"
        android:layout_marginBottom="40dp"
        />

    <Button
        android:text="B"
        android:background="@color/colorPrimary"
        android:layout_width="wrap_content"
        android:layout_height="200dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/view"
        />
</android.support.constraint.ConstraintLayout>

这种方式可以弥补margin不能设置为负值的不足,而且并没有增加布局层级。

7.5 区分0dp、match_parentMATCH_CONSTRAINT

  • 0dp等价于MATCH_CONSTRAINT,对控件设置其它尺寸相关约束会生效。如app:layout_constraintWidth_min等约束。
  • match_parent,填充满父布局,之后设置约束属性无效。

7.6 使用布局编辑器多出了一些属性

layout_optimizationLevel
layout_editor_absoluteX
layout_editor_absoluteY
layout_constraintBaseline_creator
layout_constraintTop_creator
layout_constraintRight_creator
layout_constraintLeft_creator
layout_constraintBottom_creator

这几个属性是 UI 编辑器所使用的,用了辅助拖拽布局的,在实际使用过程中,可以不用关心这些属性。

八、即将到来的一些有意思的特性

最新的约束布局beta版本,已经出到了1.1.0-beta3。在将来约束布局1.1.0版本发布后,其中会包含一下一些有意思的特性,让人看了充满期待。我们先来一睹为快:

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

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

  • Circular positioning
    可以相对另一个控件,以角度和距离定义当前控件的位置,即提供了在圆上定义控件位置的能力。如图所示:

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

  • 百分比布局
    允许设置控件占据可用空间的百分比,大大增加布局灵活度和适配性。

总而言之,约束布局的能力正在变得越来越强大。

九、最后

曾几何时,对于复杂布局,很多时候不是一种布局就可以解决。这时需要考虑布局嵌套,又或者需要在代码中动态设置控件宽高比,无形中增加了开发的复杂性和布局的嵌套层级,进而影响了页面性能。随着google推出了ContraintLayout,上述的问题大部分都可以得到有效的解决。

总的来说,ConstraintLayout优势如下:

  • 布局高效
  • 轻松应对复杂布局
  • 嵌套层级少
  • 适配性好

本人通过在项目中的实践,真切体会到了ConstraintLayout应对复杂布局和自适应页面的强大能力,不但降低了布局难度,而且提升了开发效率。开发过程中基本没怎么踩深坑,因此也很推荐大家在项目中去使用ConstraintLayout布局。

附上demo的链接https://github.com/yushiwo/ConstraintDemo,当然更建议大家自己去写一遍,可以加深印象。

参考文献

  1. ConstraintLayout 终极秘籍(上)
  2. ConstraintLayout 终极秘籍(下)
  3. 了解使用 ConstraintLayout 的性能优势
  4. [译]Constraint Layout 动画 |动态 Constraint |用 Java 实现的 UI(这到底是什么)[第三部分]
  5. Constraint Layout 1.1.x带来了哪些新东西?
  6. 当然还有官方文档啦

相关阅读:ConstraintLayout在项目中实践与总结(上篇)

本文来自网易实践者社区,经作者郑睿授权发布。