ConstraintLayout在項目中實踐與總結

ConstraintLayout,讓佈局更優雅。html

1、爲何要用ConstraintLayout

image.jpg

上圖是網易100分的選課首頁,在Banner圖的下部是推薦類目模塊,其中數學、語言、小低和小高分別是推薦類目Item。可見每一個類目的子類目個數是不肯定的,根據個數的不一樣,子類目的排列方式也不同。android

如今咱們來實現Item的佈局。若是用LinearLayout、RelativeLayout和FrameLayout去實現Item佈局,我目前想到的最低也須要兩層佈局。以下所示:git

<Relative>  
    <ImageView />
    <TextView />
    <LinearLayout>
        <TextView />
        <TextView />
        <TextView />
    </LinearLayout>
    <LinearLayout>
        <TextView />
        <TextView />
    </LinearLayout>
</Relative>
複製代碼

能夠發現沒有一種佈局容器是能夠單靠本身搞定這個佈局的,須要嵌套不一樣佈局。這樣佈局層級增長,佈局計算時間也加長了。這些都是傳統佈局存在的問題,歸納起來有如下三點:github

  • 複雜佈局能力差,須要不一樣佈局嵌套使用。
  • 佈局嵌套層級高。不一樣佈局的嵌套使用,致使佈局的嵌套層級偏高。
  • 頁面性能低。較高的嵌套層級,須要更多的計算佈局時間,下降了頁面性能。

正是因爲目前佈局容器存在的問題,咱們須要尋找一種能夠解決這些問題的佈局容器。正好,ConstraintLayout能夠。編程

2、ConstraintLayout是什麼

ConstraintLayout,中文稱約束佈局,在2016年Google I/O大會時提出,2017年2月發佈正式版,目前穩定版本爲1.0.2。約束佈局做爲Google從此主推的佈局樣式,能夠徹底替代其餘佈局,下降頁面佈局層級,提高頁面渲染性能。bash

3、怎麼用ConstraintLayout

3.1 環境搭建

ConstraintLayout支持最低Android Studio版本是2.2,可是有些屬性在2.2的佈局編輯器上不支持編輯,如比例和baseline等約束。因此推薦使用2.3的版本,固然3.0的版本那就更好了。要使用ConstraintLayout,須要在項目中進行以下配置:app

  • 在項目外層定義google maven倉庫
repositories {
    maven {
        url 'https://maven.google.com'
    }
}
複製代碼
  • 在要使用ConstraintLayout的module的build.gradle文件中引入約束佈局庫
dependencies {
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
}

複製代碼

3.2 佈局引入

按照上述配置好環境後,咱們就能夠在項目中使用ConstraintLayout了。有兩種方式使用:maven

  1. layout轉換的方式使用編輯器

    • 首先,打開一個非ConstraintLayout的佈局文件,切換到Design Tabide

    • 在Component Tree窗口,選中要轉換的layout文件根佈局,點擊右鍵,而後選擇Convert layout to ConstraintLayout

  2. 直接新建一個layout文件使用

經過以下方式引入約束佈局:

<android.support.constraint.ConstraintLayout

/>
複製代碼

3.3 屬性介紹

ConstraintLayout的佈局屬性,乍一看有不少,其實能夠分爲8個部分,下面一一介紹。

3.3.1 相對位置
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
複製代碼

以上這些屬性,用於設置一個控件相對於其餘控件、Guideline或者父容器的位置。以layout_constraintLeft_toLeftOf爲例,其中layout_部分是固定格式,主要的信息包含在下面兩部分:

  • constraintXXX:指定當前控件須要設置約束的屬性部分。如constraintLeft表示對當前控件的左邊進行約束設置。
  • toXXXOf:其指定的內容是做爲當前控件設置約束須要依賴的控件或父容器(能夠理解爲設置約束的參照物)。並經過XXX指定被依賴對象用於參考的屬性。如toLeftOf="parent" :表示當前控件相對於父容器的左邊進行約束設置。

ConstraintLayout的相對位置佈局比較靈活,相比於RelativeLayout,ConstraintLayout能夠經過layout_constraintBaseline_toBaselineOf設置兩個控件之間的文字相對於baseline對齊。一個佈局效果的例子,以下所示:

<?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_relative_position"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="zr.com.constraintdemo.normal.RelativePositionActivity">

    <Button
        android:id="@+id/btn_A"
        android:text="A"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        />

    <Button
        android:text="在A下方,與A左對齊"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/btn_A"
        app:layout_constraintLeft_toLeftOf="@id/btn_A"
        android:layout_marginTop="32dp"
        />

    <Button
        android:text="在A上方,與A居中對齊"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toTopOf="@id/btn_A"
        app:layout_constraintLeft_toLeftOf="@id/btn_A"
        app:layout_constraintRight_toRightOf="@id/btn_A"
        android:layout_marginBottom="32dp"
        />

    <Button
        android:text="baseline對齊"
        android:layout_width="wrap_content"
        android:layout_height="80dp"
        app:layout_constraintBaseline_toBaselineOf="@id/btn_A"
        app:layout_constraintLeft_toLeftOf="parent"
        android:layout_marginLeft="8dp"
        android:gravity="bottom"
        />

    <Button
        android:text="水平居中對齊"
        android:layout_width="wrap_content"
        android:layout_height="80dp"
        android:gravity="bottom"
        app:layout_constraintTop_toTopOf="@id/btn_A"
        app:layout_constraintBottom_toBottomOf="@id/btn_A"
        app:layout_constraintLeft_toRightOf="@id/btn_A"
        android:layout_marginLeft="16dp"
        />

</android.support.constraint.ConstraintLayout>
複製代碼

相對佈局例子圖

3.3.2 邊距

在ConstraintLayout中,控件除了能夠設置普通的邊距屬性,還能夠設置當控件依賴的控件GONE以後的邊距屬性。即咱們能夠理解能夠根據被依賴控件是否GONE的狀態,設置兩種邊距值。分別經過以下屬性進行設置:

  • 普通邊距屬性
android:layout_marginStart
android:layout_marginEnd
android:layout_marginLeft
android:layout_marginTop
android:layout_marginRight
android:layout_marginBottom
複製代碼
  • 被依賴控件GONE以後的邊距屬性
layout_goneMarginStart
layout_goneMarginEnd
layout_goneMarginLeft
layout_goneMarginTop
layout_goneMarginRight
layout_goneMarginBottom
複製代碼

這種特性,能夠比較方便實現一些特定的需求,且無需代碼中進行額外設置。如B控件依賴A,A距離父容器左邊20dp,B在A右邊,距離A爲20dp。需求當A設置爲GONE以後,B距離父容器左邊60dp。這在ConstraintLayout中實現起來就很簡單,對B同時設置以下屬性便可:

<?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_margin"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="zr.com.constraintdemo.normal.MarginActivity">
    <Button
        android:id="@+id/btn_a"
        android:text="A"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="100dp"
        />

    <Button
        android:text="B"
        android:textAllCaps="false"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toRightOf="@id/btn_a"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginLeft="20dp"
        app:layout_goneMarginLeft="60dp"
        android:layout_marginTop="100dp"
        />

</android.support.constraint.ConstraintLayout>
複製代碼
3.3.3 居中
  • 水平居中:相對一個控件或者父容器左右對齊
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent 複製代碼
  • 垂直居中:相對一個控件或者父容器上下對齊
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
複製代碼

例子:

<?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_center_position"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="zr.com.constraintdemo.normal.CenterPositionActivity">

    <Button
        android:text="水平居中"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        />

    <Button
        android:text="垂直居中"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        />

    <Button
        android:text="水平垂直居中"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        />

</android.support.constraint.ConstraintLayout>
複製代碼

實現效果截圖

3.3.4 偏移

在設置控件的居中屬性以後,經過偏移屬性能夠設置讓控件更偏向於依賴控件的某一方,偏移設置爲0~1之間的值。相應屬性:

layout_constraintHorizontal_bias // 水平偏移
layout_constraintVertical_bias   // 垂直偏移
複製代碼

例子:

<?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:text="水平偏移30%"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintHorizontal_bias="0.3"
        />

    <Button
        android:text="垂直偏移30%"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintVertical_bias="0.3"
        />

    <Button
        android:text="水平垂直偏移70%"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintVertical_bias="0.7"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintHorizontal_bias="0.7"
        />

</android.support.constraint.ConstraintLayout>
複製代碼

偏移截圖

3.3.5 可見性

可見性這個屬性你們應該很熟悉,可是約束佈局的可見性屬性和其它佈局相比,存在如下區別:

  • 當控件設爲GONE時,被認爲尺寸爲0。能夠理解爲佈局上的一個點。

  • 若GONE的控件對其它控件有約束,則約束保留並生效,但全部的邊距(margin)會清零。

3.3.6 尺寸
幾種設置方式:
  • 設置固定尺寸,如123dp
  • 使用wrap_content,根據內容計算合適大小
  • match_parent,填充滿父佈局,此時設置的約束都不生效了。(早以前的約束佈局版本貌似不容許在其子view中使用match_parent屬性,可是我寫文章的時候發現也是能夠用上去的)
  • 設置0dp,至關於MATCH_CONSTRAINT屬性,基於約束最終肯定大小
MATH_CONSTRAINT
  • layout_constraintWidth_minlayout_constraintHeight_min:設置最小值

  • layout_constraintWidth_maxlayout_constraintHeight_max:設置最大值

  • layout_constraintWidth_percentlayout_constraintHeight_percent:設置控件相對於父容器的百分比大小(1.1.0開始支持)。使用以前須要先設置爲百分比模式,而後設置設置寬高值爲0~1之間。

    設置爲百分比模式的屬性:

    複製代碼

app:layout_constraintWidth_default="percent" app:layout_constraintHeight_default="percent" ```

  • 強制約束
    當一個控件設爲wrap_content時,再添加約束尺寸是不起效果的。如需生效,須要設置以下屬性爲true:
app:layout_constrainedWidth=」true|false」	 
app:layout_constrainedHeight=」true|false複製代碼

看個具體例子:

<?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_dimen"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="zr.com.constraintdemo.normal.DimenActivity">

    <Button
        android:id="@+id/btn_1"
        android:text="minWidth設置爲200dp"
        android:textAllCaps="false"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:minWidth="200dp"
        />

    <Button
        android:id="@+id/btn_2"
        android:text="設置爲MATCH_CONSTRAINT"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/btn_1"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        />

    <Button
        android:id="@+id/btn_3"
        android:textAllCaps="false"
        android:text="layout_constrainedWidth開啓"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/btn_2"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constrainedWidth="true"
        app:layout_constraintWidth_min="300dp"
        />
    <Button
        android:id="@+id/btn_4"
        android:textAllCaps="false"
        android:text="layout_constrainedWidth關閉"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/btn_3"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintWidth_min="300dp"
        />
    <Button
        android:id="@+id/btn_5"
        android:textAllCaps="false"
        android:text="寬50%高30%佈局"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintTop_toBottomOf="@id/btn_4"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintWidth_default="percent"
        app:layout_constraintHeight_default="percent"
        app:layout_constraintWidth_percent="0.5"
        app:layout_constraintHeight_percent="0.3"
        />

</android.support.constraint.ConstraintLayout>
複製代碼

dimen截圖

3.3.7 比例

控件能夠定義兩個尺寸之間的比例,目前支持寬高比。 前提條件是至少有一個尺寸設置爲0dp,而後經過layout_constraintDimentionRatio屬性設置寬高比。設置方式有如下幾種:

  • 直接設置一個float值,表示寬高比
  • 以」 width:height」形式設置
  • 經過設置前綴W或H,指定一邊相對於另外一邊的尺寸,如」H, 16:9」,高比寬爲16:9

若是寬高都設置爲0dp,也能夠用ratio設置。這種狀況下控件會在知足比例 約束的條件下,儘量填滿父佈局。

下面看個例子:

<?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"
    tools:context="zr.com.constraintdemo.normal.RatioActivity">

    <Button
        android:id="@+id/btn_1"
        android:text="寬高比設置爲2:1"
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        app:layout_constraintDimensionRatio="1:1"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        />

    <Button
        android:id="@+id/btn_2"
        android:text="寬高都設置爲0dp,高寬比是16:9"
        android:textAllCaps="false"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintDimensionRatio="H,16:9"
        app:layout_constraintTop_toBottomOf="@id/btn_1" />

</android.support.constraint.ConstraintLayout>
複製代碼

實現截圖

3.3.8 鏈

鏈這個概念是約束佈局新提出的,它提供了在一個維度(水平或者垂直),管理一組控件的方式。

建立一個鏈

多個view在同一個方向上雙向引用。以下圖所示:水平方向A、B、C,A位於B左邊,B位於A右邊,他們就是一對雙向引用。同理B和C也是。

雙向引用佈局代碼以下所示。A經過app:layout_constraintRight_toLeftOf="@+id/btn_2"引用右邊的B,B經過app:layout_constraintLeft_toRightOf="@+id/btn_1"引用A。

<Button
    android:id="@+id/btn_1"
    android:text="A"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintTop_toBottomOf="@id/tv_spread"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toLeftOf="@+id/btn_2"
    app:layout_constraintHorizontal_chainStyle="spread_inside"
    />

<Button
    android:id="@+id/btn_2"
    android:text="B"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintTop_toBottomOf="@id/tv_spread"
    app:layout_constraintLeft_toRightOf="@+id/btn_1"
    app:layout_constraintRight_toLeftOf="@+id/btn_3"
    />
    
    ...
複製代碼
鏈頭

最左邊或最上面的控件,鏈的屬性由鏈頭控制。

設置

經過layout_constraintHorizontal_chainStylelayout_constraintVertical_chainStyle在鏈的第一個元素上設置。默認spread樣式。如上所示,A做爲鏈頭,設置了chainStyle:app:layout_constraintHorizontal_chainStyle="spread_inside"

幾種鏈的樣式以下圖所示:

鏈的展現圖

鏈的佈局代碼比較多,你們能夠看demo。主要是經過修改鏈頭的chainStyle樣式改變鏈的類型。

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>

複製代碼

3.5 代碼中設置約束

經過ConstraintSet,容許在代碼中進行約束設置,進行佈局變換。(API 19及以上支持trasmition動畫)

建立ConstraintSet對象的幾種方式:

  • 手動
c = new ConstraintSet(); 
c.connect(....);
複製代碼
  • 經過一個R.layout.xxx對象
c.clone(context, R.layout.layout1);
複製代碼
  • 經過一個ConstraintLayout對象
c.clone(clayout);
複製代碼

佈局變化開啓平滑動畫的方式:

TransitionManager.beginDelayedTransition(constraintLayout);
複製代碼

其中參數constraintLayout表示動畫做用的約束佈局對象。

看個例子:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        // 4.4以上開啓佈局切換動畫
        TransitionManager.beginDelayedTransition(constraintLayout);

// 清空margin
        applyConstraintSet.setMargin(R.id.btn_1, ConstraintSet.START, 0);
        applyConstraintSet.setMargin(R.id.btn_1, ConstraintSet.END, 0);
        applyConstraintSet.setMargin(R.id.btn_2, ConstraintSet.START, 0);
        applyConstraintSet.setMargin(R.id.btn_2, ConstraintSet.END, 0);
        applyConstraintSet.setMargin(R.id.btn_3, ConstraintSet.START, 0);
        applyConstraintSet.setMargin(R.id.btn_3, ConstraintSet.END, 0);

// 所有相對於父容器居中
        applyConstraintSet.centerHorizontally(R.id.btn_1, R.id.activity_constraint_set);
        applyConstraintSet.centerHorizontally(R.id.btn_2, R.id.activity_constraint_set);
        applyConstraintSet.centerHorizontally(R.id.btn_3, R.id.activity_constraint_set);
        applyConstraintSet.applyTo(constraintLayout);
    }
複製代碼

看下在4.4系統以上動畫的一個效果:

4.4以上和如下切換的gif

更多ConstraintSet例子,推薦看這篇文章

4、開始實踐

說了這麼多,那麼約束佈局用起來到底怎麼樣呢?下面咱們來實踐下:

前面類目的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是什麼?請往下看。

5、性能怎麼樣?

本文主要介紹ConstraintLayout的使用,所以也不大篇幅講述性能相關內容。

  • 直觀可見的一點是,一樣一種複雜佈局,相對於傳統佈局方式,ConstraintLayout的佈局層級減小了。
  • 具體一些性能的對比,如渲染速度和計算次數等,能夠看這篇文章《瞭解使用 ConstraintLayout 的性能優點》。經過結論可知使用了ConstraintLayout,佈局計算次數下降了,渲染速度也相應提高了。

6、佈局編輯器

從Android studio 2.2版本開始,佈局編輯器支持拖拽的方式進行約束佈局。可是在2.2上佈局編輯器還不是很完善,部分約束不能設置,只能經過xml輸入方式實現。所以推薦用版本爲2.3或者更高的Android studio。

限於篇幅,這裏就不展開介紹佈局編輯器了。在這裏推薦兩篇文章,分別是ConstraintLayout 終極祕籍(下) Android新特性介紹,ConstraintLayout徹底解析。看完這兩篇,你們應該對佈局編輯器就會有比較深刻的瞭解了。

7、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 編輯器所使用的,用了輔助拖拽佈局的,在實際使用過程當中,能夠不用關心這些屬性。

8、即將到來的一些有意思的特性

最新的約束佈局beta版本,已經出到了1.1.0-beta3。在未來約束佈局1.1.0版本發佈後,其中會包含一下一些有意思的特性,讓人看了充滿期待。咱們先來一睹爲快:

  • Barrier
    Barrier是一個虛擬的輔助控件,它能夠阻止一個或者多個控件越過本身,就像一個屏障同樣。當某個控件要越過本身的時候,Barrier會自動移動,避免本身被覆蓋。

  • Group
    Group幫助你對一組控件進行設置。最多見的狀況是控制一組控件的visibility。你只需把控件的id添加到Group,就能同時對裏面的全部控件進行操做。

  • Circular positioning
    能夠相對另外一個控件,以角度和距離定義當前控件的位置,即提供了在圓上定義控件位置的能力。如圖所示:

  • Placeholder
    Placeholder顧名思義,就是用來一個佔位的東西,它能夠把本身的內容設置爲ConstraintLayout內的其它view。所以它用來寫佈局的模版,也能夠用來動態修改UI的內容。

  • 百分比佈局
    容許設置控件佔據可用空間的百分比,大大增長佈局靈活度和適配性。

總而言之,約束佈局的能力正在變得愈來愈強大。

9、最後

曾幾什麼時候,對於複雜佈局,不少時候不是一種佈局就能夠解決。這時須要考慮佈局嵌套,又或者須要在代碼中動態設置控件寬高比,無形中增長了開發的複雜性和佈局的嵌套層級,進而影響了頁面性能。隨着google推出了ContraintLayout,上述的問題大部分均可以獲得有效的解決。

總的來講,ConstraintLayout優點以下:

  • 佈局高效
  • 輕鬆應對複雜佈局
  • 嵌套層級少
  • 適配性好

本人經過在項目中的實踐,真切體會到了ConstraintLayout應對複雜佈局和自適應頁面的強大能力,不但下降了佈局難度,並且提高了開發效率。開發過程當中基本沒怎麼踩深坑,所以也很推薦你們在項目中去使用ConstraintLayout佈局。

附上demo的連接github.com/yushiwo/Con…,固然更建議你們本身去寫一遍,能夠加深印象。

參考文獻

  1. ConstraintLayout 終極祕籍(上)
  2. ConstraintLayout 終極祕籍(下)
  3. 瞭解使用 ConstraintLayout 的性能優點
  4. [譯]Constraint Layout 動畫 |動態 Constraint |用 Java 實現的 UI(這究竟是什麼)[第三部分]
  5. Constraint Layout 1.1.x帶來了哪些新東西?
  6. 固然還有官方文檔啦
相關文章
相關標籤/搜索