Android 約束佈局(ConstraintLayout)1.1.0 版詳解

Airsaid發表於2018-04-22

前言

上一篇文章中,我們對 ConstraintLayout 1.0.2 版進行了詳細的瞭解。而當時說好的 1.1.0 版本的文章卻直到現在才出來,相隔了好久。其實關於 1.1.0 beta 版的文章早已寫完,但卻一直沒有釋出,這是因為當時擔心後面的穩定版會和現有的衝突(事實上的確有),所以一直等到上週四,Google 宣佈 ConstraintLayout 1.1.0 穩定版釋出,於是在週末休息時重新整理髮布了這篇文章。

如果對 ConstraintLayout 不瞭解,並且還沒有觀看上篇文章的,強烈建議先觀看完上篇文章,因為本篇只是對上篇的補充。如果有遺漏或錯誤,歡迎各位補充和指正。

準備

implementation 'com.android.support.constraint:constraint-layout:1.1.0'
複製程式碼

Circular Positioning

圓形定位(Circular Positioning)可以讓一個控制元件以另一個控制元件的中心為中心點,來設定其相對與該中心點的距離和角度。 可以設定的屬性有:

  • layout_constraintCircle:引用另一個控制元件的 id。
  • layout_constraintCircleRadius:到另一個控制元件中心的距離。
  • layout_constraintCircleAngle:控制元件的角度(順時針,0 - 360 度)。

下面以給頭像設定 badge 為例,演示下其用法:

<?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="com.github.airsaid.constraintlayoutdemo.MainActivity">

    <ImageView
        android:id="@+id/img_avatar"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:src="@mipmap/ic_avatar"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/txt_label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="VIP"
        android:textColor="#FFFF00"
        android:textStyle="bold"
        app:layout_constraintCircle="@id/img_avatar"
        app:layout_constraintCircleAngle="45"
        app:layout_constraintCircleRadius="30dp" />

</android.support.constraint.ConstraintLayout>
複製程式碼

執行結果:

Android 約束佈局(ConstraintLayout)1.1.0 版詳解

Enforcing constraints

在 1.1 版本之前,如果將控制元件的尺寸設定為了 WRAP_CONTENT,那麼對控制元件設定約束(如:minWidth 等)是不起作用的。那麼強制約束(Enforcing constraints)的作用就是,在控制元件被設定 WRAP_CONTENT 的情況下,使約束依然生效。

需要使用到的屬性有:

  • app:constrainedWidth="true|false"
  • app:constrainedHeight="true|false"

下面的例子演示了沒有設定強制約束和設定了強制約束的對比:

<ImageView
        android:id="@+id/img_avatar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_avatar"
        app:layout_constrainedWidth="false"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintWidth_max="100dp" />

<ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_avatar"
        app:layout_constrainedWidth="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/img_avatar"
        app:layout_constraintWidth_max="100dp" />
複製程式碼

執行結果:

Android 約束佈局(ConstraintLayout)1.1.0 版詳解

Dimensions

1.1 版本中,當控制元件的尺寸設定為了 MATCH_CONSTRAINT 時( 0dp),在設定尺寸上又多了二個新的修飾屬性:

  • layout_constrainWidth_percent。
  • layout_constrainHeight_percent。

這兩個屬性的作用就是指定當前控制元件的寬度或高度是父控制元件的百分之多少。可設定的值在 0 - 1 之間,1 就是 100%。

設定頭像的寬度佔父控制元件寬度的 80%(父控制元件佔滿全屏)例子:

<ImageView
    android:id="@+id/img_avatar"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:scaleType="centerCrop"
    android:src="@mipmap/ic_avatar"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintWidth_percent="0.8" />
複製程式碼

執行結果:

Android 約束佈局(ConstraintLayout)1.1.0 版詳解

Margins and chains

在 1.1.0-beta4 版本中(已知),為鏈中的控制元件設定 marginRight/End 是無效的(個人感覺這應該是個 Bug)。而在 1.1 穩定版中,無論設定右邊距還是左邊距都是有效果的,會累計計算。並且在計算剩餘空間時,會將邊距一起考慮。

Optimizer

需要知道的是,當我們使用 MATCH_CONSTRAINT 時,ConstraintLayout 將不得不對控制元件進行 2 次測量,而測量的操作是昂貴的。

而優化器(Optimizer)的作用就是對 ConstraintLayout 進行優化,對應設定給 ConstraintLauyout 的屬性是:

  • layout_optimizationLevel。

可設定的值有:

  • none:不應用優化。
  • standard:僅優化直接約束和屏障約束(預設的)。
  • direct:優化直接約束。
  • barrier:優化屏障約束。
  • chain:優化鏈約束(實驗)。
  • dimensions:優化尺寸測量(實驗)。

在設定值時,可以設定多個,如:

app:layout_optimizationLevel="direct|barrier|dimensions"
複製程式碼

Barrier

當我們在佈局時,有時候就會遇到佈局會隨著資料的多少而改變大小的情況。以下圖為例:

Android 約束佈局(ConstraintLayout)1.1.0 版詳解

(圖片來自官方)

通過上圖就可以發現,當在 A、B 控制元件的大小都不確定的情況下, View3 以誰作為約束物件都不對。如果以 A 作為約束物件,那麼當 B 的寬度過寬時就會被遮擋,同理以 B 作為約束也是如此。

那麼此時,Barrier(屏障)就派上用場了。這是個非常好用的東東,和 GuideLine 一樣,它是一個虛擬的 View,對介面是不可見的。目的就是輔助佈局。

對 Barrier 可以使用的屬性有:

  • barrierDirection:設定 Barrier 所建立的位置。可設定的有:bottom、end、left、right、start、top。
  • constraint_referenced_ids:設定 Barrier 引用的控制元件。可設定多個,設定的方式是:id, id。(無需加 @id/)
  • barrierAllowsGoneWidgets:預設為 true,即當 Barrier 引用的控制元件被 GONE 掉時,則 Barrier 預設的建立行為是在已 GONE 掉控制元件的已解析位置上進行建立。如果設定為 false,則不會將 GONE 掉的控制元件考慮在內。

說再多不如看程式碼,還是以上圖為例,來看看 Barrier 是如何解決的:

<?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"
    android:padding="16dp"
    tools:context="com.github.airsaid.constraintlayoutdemo.MainActivity">

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Title"
        android:textSize="16sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/desc"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="This is a descriptive text."
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/title" />

    <android.support.constraint.Barrier
        android:id="@+id/barrier"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:barrierDirection="end"
        app:constraint_referenced_ids="title, desc" />

    <TextView
        android:id="@+id/content"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="10dp"
        android:text="This is a piece of content that is very long and long very long and long ..."
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/barrier"
        app:layout_constraintTop_toTopOf="parent" />


</android.support.constraint.ConstraintLayout>
複製程式碼

執行結果:

Android 約束佈局(ConstraintLayout)1.1.0 版詳解

另一種情況:

Android 約束佈局(ConstraintLayout)1.1.0 版詳解

完美解決。

Group

Group 的作用就是控制一組控制元件的可見性。其可使用到的屬性為:

  • constraint_referenced_ids:指定所引用控制元件的 id。

例:

<android.support.constraint.Group
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:visibility="gone"
    app:constraint_referenced_ids="title, desc" />
複製程式碼

如果有多個 Group,是可以同時指定相同的控制元件的,最終是以 XML 中最後宣告的 Group 為準。

Placeholder

Placeholder(佔位符)是一個虛擬物件,作用和它的名字一樣,就是佔位。

當放置好 Placeholder 後,可以通過 setContentId() 方法將佔位符變為有效的檢視。如果檢視已經存在於螢幕上,那麼檢視將會從原有位置消失。

除此之外,還可以通過 setEmptyVisibility() 方法設定當檢視不存在時佔位符的可見性。

下面的例子演示了佔位符的使用,當點選頂部頭像時,頂部頭像會消失並在佔位符處顯示:

<?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"
    android:padding="16dp"
    tools:context="com.github.airsaid.constraintlayoutdemo.MainActivity">

    <ImageView
        android:id="@+id/avatar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_avatar"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <android.support.constraint.Placeholder
        android:id="@+id/placeholder"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>
複製程式碼

執行結果:

Android 約束佈局(ConstraintLayout)1.1.0 版詳解

總結

通過本篇文章可以看到, ConstraintLayout 正在不斷的強大、優化中,並且更是推出了優化器來讓效能更出色。那麼,還有什麼理由不用 ConstraintLayout 呢?!

相關文章