布局优化:9种让你不得不使用约束布局Constraintlayout的场景

5,602 阅读8分钟

阅读本文获得什么?

其实本文是一个爽文,适用于解决之前知道约束布局但是实际工作中迟迟无法下定决定使用他的读者,读完本文你就能知道约束布局能有多爽,然后迫不及待的去使用他了。

三联布局

看一下 这个布局,头条里面的。 作为列表页中的一个item ,并且item中间是三张图并列, 通常我们拿到设计稿 发现这种类型的ui 肯定是 最外层是一个相对布局,然后我们存放三张图片的 又是一个 linearlayout 然后用weight 来做。这样虽然简单 但是又增加了一层布局。

使用约束布局,这种嵌套布局的情况就不会出现了

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".MainActivity">

    <!--    top-->
    <TextView
        android:id="@+id/v1"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@color/colorAccent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"></TextView>

    <TextView
        android:id="@+id/tv1"
        android:layout_width="0dp"
        android:layout_height="30dp"
        android:background="#CDCD00"
        android:gravity="center"
        android:text="图1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toStartOf="@id/tv2"
        app:layout_constraintTop_toBottomOf="@id/v1"></TextView>

    <TextView
        android:id="@+id/tv2"
        android:layout_width="0dp"
        android:layout_height="30dp"
        android:background="#FF7F24"
        android:gravity="center"
        android:text="图2"
        app:layout_constraintStart_toEndOf="@id/tv1"
        app:layout_constraintEnd_toStartOf="@id/tv3"
        app:layout_constraintTop_toBottomOf="@id/v1"></TextView>

    <TextView
        android:layout_width="0dp"
        android:layout_height="30dp"
        android:background="#AB82FF"
        android:gravity="center"
        android:text="图3"
        android:id="@+id/tv3"
        app:layout_constraintStart_toEndOf="@id/tv2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@id/v1">
    </TextView>
</androidx.constraintlayout.widget.ConstraintLayout>

来看下效果:

甚至你说 我就要weight的效果

那也是可以的

只要添加这个属性就可以

        app:layout_constraintHorizontal_weight="2"

相对某个控件居中

看一下这个布局,其实就是抖音的个人中心页面。 头像是一个圆形的,后面的编辑资料 好友都是在这个圆形头像的垂直居中

如果不用约束布局 我们应该怎么做? 肯定是又得在父布局中添加一个相对布局,然后相对布局的高度和头像icon的高度一致,然后剩余的编辑资料 好友等等,就直接设置垂直居中了。

这么做肯定可以,但是还是多了一层布局。 用约束布局 又很简单

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".MainActivity">


    <TextView
        android:id="@+id/tv1"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_marginLeft="30dp"
        android:layout_marginTop="30dp"
        android:background="#CDCD00"
        android:gravity="center"
        android:text="左边头像"
        app:layout_constraintEnd_toStartOf="@id/tv2"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"></TextView>

    <TextView
        android:id="@+id/tv2"
        android:layout_width="60dp"
        android:layout_height="30dp"
        android:background="#FF7F24"
        android:gravity="center"
        android:text="编辑资料"
        android:layout_marginLeft="30dp"
        app:layout_constraintStart_toEndOf="@id/tv1"
        app:layout_constraintBottom_toBottomOf="@id/tv1"
        app:layout_constraintTop_toTopOf="@id/tv1"></TextView>


    <TextView
        android:id="@+id/tv3"
        android:layout_width="60dp"
        android:layout_height="30dp"
        android:background="#00FF7F"
        android:gravity="center"
        android:text="+好友"
        android:layout_marginLeft="30dp"
        app:layout_constraintStart_toEndOf="@id/tv2"
        app:layout_constraintBottom_toBottomOf="@id/tv1"
        app:layout_constraintTop_toTopOf="@id/tv1"></TextView>


</androidx.constraintlayout.widget.ConstraintLayout>

看下效果

半透布局

先看一下效果:

这种布局其实也不少见,通常黄色的那部分是一个背景图,然后橙色的地方 有一半深入在黄色背景中。 我们拿到这种ui设计稿的时候 第一反应就是用相对布局 然后设置一个负的margin。

但是这么做有一个不好,除了增加布局嵌套以外,这个负的margin的值必须是动态计算出来的,因为你永远不知道 你这个橙色的图片实际高度是不是固定的。假设这边还有滑动动画要求,因为滑动的时候橙色部分高度也在变小,所以你还得不停的变更这个margin的负值,变得非常麻烦。

如果用约束布局 则上述问题 全都没有了

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".MainActivity">


    <View
        android:id="@+id/tv1"
        android:layout_width="0dp"
        android:layout_height="200dp"
        android:background="#CDCD00"
        android:gravity="center"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"></View>

    <TextView
        android:id="@+id/tv2"
        android:layout_width="160dp"
        android:layout_height="80dp"
        android:background="#FF7F24"
        android:gravity="center"
        android:text="一半在兄弟节点中"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv1"
        app:layout_constraintBottom_toBottomOf="@id/tv1"></TextView>



</androidx.constraintlayout.widget.ConstraintLayout>

baseline 对齐 与 组件对齐

常见的组件对齐

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".MainActivity">

    <!--    top-->
    <TextView
        android:id="@+id/v1"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginTop="30dp"
        android:layout_marginLeft="30dp"
        android:gravity="center"
        android:text="哈哈哈哈"
        android:background="@color/colorAccent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"></TextView>



    <TextView
        android:id="@+id/v2"
        android:layout_width="80dp"
        android:layout_height="50dp"
        android:layout_marginTop="30dp"
        android:layout_marginLeft="30dp"
        android:gravity="center"
        android:text="组件对齐"
        android:background="@color/colorAccent"
        app:layout_constraintLeft_toRightOf="@id/v1"
        app:layout_constraintBottom_toBottomOf="@id/v1"></TextView>

</androidx.constraintlayout.widget.ConstraintLayout>

想要文字baseline对齐 也很方便

只要增加一个属性

        app:layout_constraintBaseline_toBaselineOf="@id/v1"

角度定位

实际上这个功能 我觉得只有在你做一些复杂的自定义view 尤其是拖动滑动的时候才有用。 否则其实意义不是特别大,但是一旦你做动画的时候 你就会发现这个角度定位实在是太好用了!

首先看这个图,右上方的控件的位置是根据 中间的位置来的。 一共是三要素

第一:定位的原点。 也就是我们屏幕中间的tv的 中心点 第二:这个半径的长度, 也就是图中我们红线的长度 第三:角度 这个初中数学应该都有的教

那么 写一下

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".MainActivity">

    <!--    top-->
    <TextView
        android:id="@+id/v1"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:gravity="center"
        android:text="我在屏幕中间"
        android:background="@color/colorAccent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"></TextView>



    <TextView
        android:id="@+id/v2"
        android:layout_width="80dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:layout_marginLeft="30dp"
        android:gravity="center"
        android:text="我在中间的上方65度"
        android:background="@color/colorAccent"
        app:layout_constraintCircle="@id/v1"
        app:layout_constraintCircleRadius="180dp"
        app:layout_constraintCircleAngle="65"
      ></TextView>

</androidx.constraintlayout.widget.ConstraintLayout>

其实就主要这三个属性

 app:layout_constraintCircle="@id/v1"
        app:layout_constraintCircleRadius="180dp"
        app:layout_constraintCircleAngle="65"

可以想象 你要是有一个自定义view的动画之类的和view的轨迹有关,那么其实只要一个属性动画 来改变 这个半径或者角度就可以了,非常简单!

gone 不影响绝对位置

看一张这样的图

用相对布局来做 很简单,v2 只要在v1的右边就可以。 但是有时候实际业务中我们的v1 很可能因为某种情况不展示 或者宽度直接变为0. 这个时候v2 就会往左偏移了。

相对布局要想处理这样的情况非常麻烦。 但是使用约束布局,却非常简单

我们让蓝色图消失,但是蓝绿色的图 还保持原位

看下代码:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".MainActivity">



    <View
        android:id="@+id/v1"
        android:visibility="gone"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginLeft="30dp"
        android:layout_marginTop="30dp"
        android:background="@color/colorPrimary"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"></View>

    <View
        android:id="@+id/v2"
        app:layout_goneMarginLeft="130dp"
        android:layout_width="100dp"
        android:layout_height="100dp"
        app:layout_constraintLeft_toRightOf="@id/v1"
        app:layout_constraintTop_toTopOf="@id/v1"
        android:background="@color/colorAccent"></View>




</androidx.constraintlayout.widget.ConstraintLayout>

其实重要就是 app:layout_goneMarginLeft 这个属性在发挥作用

多个view 一起居中

看这个图

如果我不用约束布局 想让这2个view 垂直居中。那我只能外面再包一层layout。

但是 如果 用约束布局 就很简单

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".MainActivity">

    <View
        android:id="@+id/v1"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="@color/colorPrimary"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@id/v2"
        app:layout_constraintVertical_chainStyle="packed"

        ></View>

    <View
        android:id="@+id/v2"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_marginTop="30dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/v1"
        app:layout_constraintBottom_toBottomOf="parent"
        android:background="@color/colorAccent"></View>


</androidx.constraintlayout.widget.ConstraintLayout>

要注意 app:layout_constraintVertical_chainStyle 他的使用,一定要头尾相接.

宽高比view

有时 我们希望 给定一个宽度 和一个宽高比 来动态设置一个view的高度,或者高度固定 动态设置宽度。以往要实现还要自己写自定义view 或者代码中动态计算。

约束布局 就简单多了

 <View
        android:id="@+id/v1"
        android:layout_width="100dp"
        android:layout_height="0dp"
        app:layout_constraintDimensionRatio="2:5"
        android:background="@color/colorPrimary"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintVertical_chainStyle="packed"
        ></View>

效果:

注意 app:layout_constraintDimensionRatioandroid:layout_height="0dp" 要搭配使用

app:layout_constraintWidth_percent= 这个与之类似 是百分比布局的关键属性,比较简单 我就不演示了

利用不可见的辅助线 削减嵌套布局

注意这个虚线实际上是看不到的,这里只是xml预览中为了辅助显示用的。 实际ui稿 的登录注册 很容易见到这种需求, 用户名和密码 是右对齐的。 碰到这种需求 我们往往需要又要增加嵌套布局了 但是有了约束布局,这里又简单不少,可以定义一个看不见的辅助线 来完成类似的需求

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".MainActivity">


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

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="80dp"
        android:text="用户名"
        android:gravity="right|center_vertical"
        app:layout_constraintEnd_toStartOf="@+id/guideline"
        tools:layout_editor_absoluteY="36dp" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="63dp"
        android:layout_height="80dp"
        android:gravity="right|center_vertical"
        android:text="密码"
        tools:layout_editor_absoluteX="60dp"
        tools:layout_editor_absoluteY="158dp" />

    <EditText
        android:id="@+id/editText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ems="10"
        android:inputType="textPassword"
        app:layout_constraintBottom_toBottomOf="@+id/textView"
        app:layout_constraintTop_toTopOf="@+id/textView"
        app:layout_constraintVertical_bias="0.6"
        tools:layout_editor_absoluteX="123dp" />

    <EditText
        android:id="@+id/editText2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ems="10"
        android:inputType="textPassword"
        tools:layout_editor_absoluteX="123dp"
        tools:layout_editor_absoluteY="169dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

关键字就是androidx.constraintlayout.widget.Guideline

设置一组view 的可见性

经过前面的学习 我们已经知道约束布局 本质是希望我们削减层级嵌套, 但有的时候 也许你会希望有层级嵌套来帮助你实现一组view的可见性 例如

图中的三个并排textview 我希望控制他一起展示或者不展示。 考虑到我们不希望增加嵌套布局了。

所以需要这么写:

<androidx.constraintlayout.widget.Group android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:visibility="gone"
        app:constraint_referenced_ids="textView3,textView4,textView5"
        >

    </androidx.constraintlayout.widget.Group>

类似的 还有androidx.constraintlayout.widget.Barrier等Helper。