專案需求討論 — ConstraintLayout 詳細使用教程

青蛙要fly發表於2019-03-04

題外話

關於ConstraintLayout的文章網上一抓一大把,而且ConstraintLayout在16年就已經出來了,但是我一直沒有試著去使用(別問我為什麼不去使用,當然是因為懶啊)。畢竟前面的LinearLayout搭配RelativeLayout用習慣了,但是畢竟能減少佈局的巢狀。還是要抱著多學習的方式去接觸。所以寫下文章作為總結。

前言

大家都知道AS在寫相關佈局的時候,有二種方式:

1. 拖拽方式

專案需求討論 — ConstraintLayout 詳細使用教程

就是在這裡進行拖控制元件,各種操作,因為在以前RelativeLayout和LinearLayout的年代,自己拖會自動幫我們新增各種屬性值不說,而且還很不方便,但是對於ConstraintLayout來說新增各種約束在這裡操作反而很方便,而且這裡的功能皮膚也增加了很多新功能,方便了很多。

當然我也不多說,貼上郭霖大神寫得在這裡功能皮膚裡面對ConstraintLayout 各種操作方式:
操作皮膚拖拽方式來使用ConstraintLayout

2.編寫程式碼

專案需求討論 — ConstraintLayout 詳細使用教程

這種更為大家使用,而我這裡也更多的是直接寫程式碼的方式。

正文

控制元件如何確定自己的位置

1.直接確定控制元件左上角的座標

在約束佈局中,一個控制元件如何來確定自己的位置呢,有人可能說直接寫死讓它在介面的(XXX,XXX)位置不就好了麼。

比如在拖拽介面,我們把一個TextView拖到了介面中間。

專案需求討論 — ConstraintLayout 詳細使用教程

我們發現這個TextView的確在中間了,這時候我們看下它的程式碼:

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


    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="我在哪裡"
        tools:layout_editor_absoluteX="164dp"
        tools:layout_editor_absoluteY="263dp" />


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

我們發現了:

tools:layout_editor_absoluteX="164dp"
tools:layout_editor_absoluteY="263dp"
複製程式碼

的確我們告訴了TextView的左上角的座標,這個TextView的確可以確定了位置,但是這二個屬性只是單純的進行演示,在真機操作的時候是無效的,就像”tools:text”一樣,可以在寫佈局的時候方便檢視TextView顯示的文字,但是實際執行app的時候不會有相應內容。

而且我們也可以看到佈局檔案中有錯誤提示,也告訴我們在真實執行時候會跳到(0,0)位置:

This view is not constrained, it only has designtime positions, so it will jump to (0,0) unless you add constraints less…

2.告訴控制元件相鄰的二個邊的位置情況

如下圖所示:

專案需求討論 — ConstraintLayout 詳細使用教程

我們怎麼來確定它們的位置?比如我們紅色的矩形A,我們是不是告訴它:你的左邊靠著外面介面的左邊,你的頂邊靠著外面介面的頂邊(然後是不是A就處在現在這個位置了)。綠色的矩形B我們可以告訴它:你的右邊靠著外面介面的右邊,你的底邊靠著外面介面的底邊(然後B就處在了現在這個位置)。

所以基本操作就是:確定某個控制元件二個邊的位置(比如靠在哪個控制元件旁邊)。

我們來看最簡單的基本操作:
layout_constraint[自己位置]_[目標位置]=”[目標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
複製程式碼

舉個例子:

專案需求討論 — ConstraintLayout 詳細使用教程

比如我們A按鈕已經確定好位置了。我們現在要放B按鈕,就像我們上面說的,我們B按鈕的二個邊的位置,我們可以設定讓B按鈕的左邊靠著A按鈕的右邊(相當於B按鈕的左邊與A按鈕的右邊處於同一位置)。

<Button android:id="@+id/buttonA" ... />

<Button android:id="@+id/buttonB" ...
    app:layout_constraintLeft_toRightOf="@+id/buttonA" />
複製程式碼

我們可以看到app:layout_constraintLeft_toRightOf="@+id/buttonA",B的leftidbuttonA的控制元件的right相同位置。所以B的左側就和A的右側貼在了一起。

我們發現上面還有一個layout_constraintBaseline_toBaselineOf,直接看下圖就可以理解所有相關的屬性:

專案需求討論 — ConstraintLayout 詳細使用教程

如果是相對於父佈局,我們也可以不寫入另外一個控制元件的id值,直接填parent值就可以了

<android.support.constraint.ConstraintLayout ...>
    <Button android:id="@+id/button" ...
     app:layout_constraintLeft_toLeftOf="parent"
     />
     
<android.support.constraint.ConstraintLayout/>
複製程式碼

所以以上就是基本的操作。我們接下來看下其他的特殊屬性。


Margin值相關

比如我們上面的A和B按鈕通過了app:layout_constraintLeft_toRightOf拼接在一起了,但是我同時希望A和B按鈕中間能空一些距離,如下圖所示:

專案需求討論 — ConstraintLayout 詳細使用教程

我們可以直接使用:

android:layout_marginStart
android:layout_marginEnd
android:layout_marginLeft
android:layout_marginTop
android:layout_marginRight
android:layout_marginBottom
複製程式碼

這時候就又會有一個問題,如果這時候A的visiblegone,這時候B的位置就會自動往左邊了。因為A的所佔的寬度沒有了(但是A在裡面對於其他控制元件的約束性都是還是存在的)

專案需求討論 — ConstraintLayout 詳細使用教程

但是如果我的需求就是A隱藏後,B還是在這個位置(當然有些人可能會說你可以讓B根據其他控制元件來確定位置),而且我的B的位置就是根據A來確定的。那我們怎麼處理,我們可以設定B的以下屬性,就是當A處於gone的時候,我們可以讓B的margin值是根據以下的屬性值:

layout_goneMarginStart
layout_goneMarginEnd
layout_goneMarginLeft
layout_goneMarginTop
layout_goneMarginRight
layout_goneMarginBottom

複製程式碼

位置約束不止二個邊

我們上面提過,二個邊的位置確定好了(也可以說二個邊的位置被約束了),我們就可以確定這個控制元件的相應位置,而且還可以通過margin的改變,來繼續調節控制元件的位置。那如果我這時候是三個邊約束或者四個邊都約束了呢,比如:

<android.support.constraint.ConstraintLayout ...>
    <Button android:id="@+id/button" ...
     app:layout_constraintLeft_toLeftOf="parent"
     app:layout_constraintRight_toRightOf="parent/>
     
<android.support.constraint.ConstraintLayout/>
複製程式碼

我們讓按鈕的左邊與父佈局的左邊對齊,讓按鈕的右邊與父佈局的右邊對齊。這時候因為不是單純的一邊對齊,而是相同直線上的二個邊都被約束了。所以按鈕無法緊靠著左邊的或者右邊的其中一個邊界,所以這時候,這個按鈕就會居於二個約束邊界的中間位置。如下圖所示:

專案需求討論 — ConstraintLayout 詳細使用教程

也許也有人問,我想在這二個約束條件下時候不是處於正中間,而是處於左邊三分之一的位置,這時候你可以使用:

layout_constraintHorizontal_bias
layout_constraintVertical_bias
複製程式碼

分別是水平和垂直方向上的所佔比例。

專案需求討論 — ConstraintLayout 詳細使用教程
<android.support.constraint.ConstraintLayout ...>
    <Button android:id="@+id/button" ...
     app:layout_constraintHorizontal_bias="0.3"
     app:layout_constraintLeft_toLeftOf="parent"
     app:layout_constraintRight_toRightOf="parent/>
</android.support.constraint.ConstraintLayout>
複製程式碼

圓形佈局

有些需求我們可能需要讓控制元件以某個控制元件為中心,繞著進行佈局,如下圖所示:

專案需求討論 — ConstraintLayout 詳細使用教程

ConstarintLayout自帶了這些功能,我們可以使用:

layout_constraintCircle : 引用另一個控制元件的id
layout_constraintCircleRadius : 距離另外一個控制元件中心的距離
layout_constraintCircleAngle : 應該在哪個角度(從0到360度)
複製程式碼
專案需求討論 — ConstraintLayout 詳細使用教程

例如:

<Button android:id="@+id/buttonA" ... />
<Button android:id="@+id/buttonB" ...
  app:layout_constraintCircle="@+id/buttonA"
  app:layout_constraintCircleRadius="100dp"
  app:layout_constraintCircleAngle="45" />
複製程式碼

尺寸限制(Dimensions constraints)

1.對ConstraintLayout進行限制:

您可以為ConstraintLayout本身定義最小和最大尺寸:

android:minWidth設定佈局的最小寬度
android:minHeight設定佈局的最小高度
android:maxWidth設定佈局的最大寬度
android:maxHeight設定佈局的最大高度
複製程式碼

這些最小和最大尺寸將在ConstraintLayout使用

2.對內部的控制元件進行限制:

可以通過以3種不同方式設定android:layout_widthandroid:layout_height屬性來指定控制元件的尺寸:

  • 用特定的值(如123dp等)
  • 使用WRAP_CONTENT,它會要求控制元件計算自己的大小
  • 使用0dp,相當於“MATCH_CONSTRAINT”

WRAP_CONTENT(在1.1中新增)

如果設定為WRAP_CONTENT,則在1.1之前的版本中, 約束不會限制生成的尺寸值。但是在某些情況下,您可能需要使用WRAP_CONTENT,但仍然執行約束來限制生成的尺寸值。在這種情況下,你可以新增一個相應的屬性:

應用:layout_constrainedWidth =”真|假”
應用:layout_constrainedHeight =”真|假”
複製程式碼

MATCH_CONSTRAINT尺寸(也就是0dp)(在1.1中新增)

設定為MATCH_CONSTRAINT時,預設是大小是佔用所有可用空間。有幾個額外的修飾符可用:

layout_constraintWidth_min和layout_constraintHeight_min:將設定此維度的最小尺寸
layout_constraintWidth_max和layout_constraintHeight_max:將設定此維度的最大尺寸
layout_constraintWidth_percent和layout_constraintHeight_percent:將設定此維度的大小為父級的百分比
複製程式碼

百分比尺寸(Percent Dimensions)

說到Percent Dimensions就不得不說ConstraintLayout中的0dp問題,當控制元件設定為0dp的時候(0dp的稱呼又叫match_constraint),預設的行為是撐開(spread),佔滿可用空間,但是這個行為是可以用layout_constraintWidth_default 屬性來設定的。在 ConstraintLayout 1.0.x中,這個屬性還可以把它設定為wrap。而到了1.1.x,它又有了一個新的值:percent,允許我們設定控制元件佔據可用空間的百分比。

(注意:這在1.1-beta1和1.1-beta2中layout_constraintWidth_default是必須的,但是如果percent屬性被定義,則在以下版本中不需要,然後將layout_constraintWidth_percent或layout_constraintHeight_percent屬性設定為介於0和1之間的值)

下面的TextView控制元件將佔據剩餘寬度的50%和剩餘高度的50%:

  <TextView
    android:id="@+id/textView6"
    android:layout_width="0dp"
    android:layout_height="0dp"
    
    app:layout_constraintHeight_default="percent"
    app:layout_constraintHeight_percent="0.5"
    
    app:layout_constraintWidth_default="percent"
    app:layout_constraintWidth_percent="0.5" />
複製程式碼

寬高比(Ratio)

您還可以控制控制元件的height或者width這二個值,讓其中一個值與另外一個值的成特定的比例。為此,需要至少將一個值設定為0dp(即,MATCH_CONSTRAINT),並將屬性layout_constraintDimensionRatio設定為給定比率。例如:

<Button android:layout_width="wrap_content"
   android:layout_height="0dp"
   app:layout_constraintDimensionRatio="1:1" />
複製程式碼

這樣這個按鈕的寬和高是一樣大小的。

Ratio可以設定為:

  • 浮點值,表示寬度和高度之間的比率
  • “寬度:高度”形式的比率

如果兩個維都設定為MATCH_CONSTRAINT(0dp),則也可以使用比率: 在這種情況下,系統設定滿足所有約束條件的最大尺寸並保持指定的寬高比。

為了約束一個特定的邊,可以根據另一個邊的大小來限定寬度或高度:
可以通過在比率前面新增字母W(用於限制寬度)或H(用於限制高度),用逗號分隔來指示哪一邊應該受到約束:

<Button android:layout_width="0dp"
   android:layout_height="0dp"
   app:layout_constraintDimensionRatio="H,16:9"
   app:layout_constraintBottom_toBottomOf="parent"
   app:layout_constraintTop_toTopOf="parent"/>
複製程式碼

將按照16:9的比例設定按鈕的高度,而按鈕的寬度將匹配父佈局的約束。


鏈(Chains)

鏈在單個軸(水平或垂直)中提供類似組的行為。

  • 建立一個鏈:
    如果一組小部件通過雙向連線連結在一起,則認為它們是一個鏈,如下圖所示,是一個具有二個控制元件的最小的鏈:
    專案需求討論 — ConstraintLayout 詳細使用教程
  • 鏈頭:
    鏈由在鏈的第一個元素(鏈的“頭”)上設定的屬性控制:
    專案需求討論 — ConstraintLayout 詳細使用教程

    (頭是水平鏈最左邊的部件,也是垂直鏈最頂端的部件。)

  • 鏈樣式: 在鏈的第一個元素上設定屬性layout_constraintHorizontal_chainStylelayout_constraintVertical_chainStyle時,鏈的行為將根據指定的樣式進行更改(預設為CHAIN_SPREAD)。
    專案需求討論 — ConstraintLayout 詳細使用教程

    例如:

<?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">
    

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="AAAA"
        app:layout_constraintHorizontal_chainStyle="spread"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toStartOf="@id/textView3"
        />

    <TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="BBBB"
        app:layout_constraintEnd_toStartOf="@id/textView4"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toEndOf="@+id/textView2" />

    <TextView
        android:id="@+id/textView4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="CCCC"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/textView3" />
</android.support.constraint.ConstraintLayout>
複製程式碼

效果如下:

專案需求討論 — ConstraintLayout 詳細使用教程

屏障 (Barrier)

Barrier是一個虛擬的輔助控制元件,它可以阻止一個或者多個控制元件越過自己,就像一個屏障一樣。當某個控制元件要越過自己的時候,Barrier會自動移動,避免自己被覆蓋。

關於這個控制元件其他文章有詳細的介紹,我直接附上地址:
ConstraintLayout之Barrier


組(Group)

Group幫助你對一組控制元件進行設定。最常見的情況是控制一組控制元件的visibility。你只需把控制元件的id新增到Group,就能同時對裡面的所有控制元件進行操作。

<android.support.constraint.ConstraintLayout ...>
  <TextView
    android:id=”@+id/text1" ... />
  <TextView
    android:id=”@+id/text2" ... />
  <android.support.constraint.Group
    android:id=”@+id/group”
    ...
    app:constraint_referenced_ids=”text1,text2" />
</android.support.constraint.ConstraintLayout>
複製程式碼

此時如果我們呼叫group.setVisibility(View.GONE);那麼text1 和 text2 都將不可見。


Guideline

ConstraintLayout的輔助物件的實用程式類。Guideline不會顯示在裝置上(它們被標記為View.GONE),僅用於佈局。他們只能在ConstraintLayout中工作。

指引可以是水平的也可以是垂直的:
垂直指南的寬度為零,它們的ConstraintLayout父項的高度為零
水平指南的高度為零,其ConstraintLayout父項的寬度為零
定位準則有三種不同的方式:

  • 指定佈局左側或頂部的固定距離(layout_constraintGuide_begin)
  • 從佈局的右側或底部指定固定距離(layout_constraintGuide_end)
  • 指定佈局的寬度或高度的百分比(layout_constraintGuide_percent)

相應的程式碼為setGuidelineBegin(int,int),setGuidelineEnd(int,int)和setGuidelinePercent(int,float)函式。

然後控制元件就可以被Guideline來約束。(換句話就是說弄了一個隱藏的View,來約束我們的控制元件,我們的控制元件相對的就更容易進行位置定位)。

限制於垂直Guideline的按鈕示例:

<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.support.constraint.Guideline
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/guideline"
            app:layout_constraintGuide_begin="100dp"
            android:orientation="vertical"/>

    <Button
            android:text="Button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/button"
            app:layout_constraintLeft_toLeftOf="@+id/guideline"
            android:layout_marginTop="16dp"
            app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

複製程式碼

Placeholder

大傢俱體使用可以看這篇文章:
New features in ConstraintLayout 1.1.x
我以下Placeholder內容也就轉載這個文章裡面的例子:

Placeholder顧名思義,就是用來一個佔位的東西,它可以把自己的內容設定為ConstraintLayout內的其它view。因此它用來寫佈局的模版,也可以用來動態修改UI的內容。

用作模版:
我們用Placeholder建立一個名為template.xml的模版:

專案需求討論 — ConstraintLayout 詳細使用教程

模版寫好了我們來填充真正的東西。

我們把剛才定義的模版include到真正的佈局檔案中,並且在這個佈局檔案中新增真實的控制元件,注意這裡的控制元件無需新增任何約束,因為它們的位置是由Placeholder決定的。

還有一點就是模版要放在被引用的所有控制元件之前:

<?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/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.app.androidkt.constraintlayoutb.MainActivity"
    tools:showIn="@layout/activity_main">
    <include layout="@layout/template" />
    <ImageView
        android:id="@+id/top_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:scaleType="fitXY"
        android:src="@drawable/place_holder_demo" />
    
    <ImageButton
        android:id="@+id/save"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:srcCompat="@drawable/ic_save_black_24dp" />
 
    <ImageButton
        android:id="@+id/edit"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:srcCompat="@drawable/ic_edit_black_24dp" />
 
    <ImageButton
        android:id="@+id/cancel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
 
        app:srcCompat="@drawable/ic_cancel_black_24dp" />
 
    <ImageButton
        android:id="@+id/delete"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
 
        app:srcCompat="@drawable/ic_delete_black_24dp" />
 
 
</android.support.constraint.ConstraintLayout>
複製程式碼
專案需求討論 — ConstraintLayout 詳細使用教程

以上就是PlaceHolder的使用場景之一模版功能。

動態替換:
PlaceHolder還可以在Java程式碼中動態替換自己的內容:

public class MainActivity extends AppCompatActivity {
  private Placeholder placeholder;
  private ConstraintLayout root;
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ...
  }
  public void onClick(View view) {
    placeholder.setContentId(view.getId());
  }
}
複製程式碼

如果結合過渡動畫的話,就可以實現一些比較有趣的效果:

public class MainActivity extends AppCompatActivity {
  private Placeholder placeholder;
  private ConstraintLayout root;
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ...
  }
  public void onClick(View view) {
    TransitionManager.beginDelayedTransition(root);
    placeholder.setContentId(view.getId());
  }
}
複製程式碼

下面是使用PlaceHolder結合過渡動畫實現的效果:

專案需求討論 — ConstraintLayout 詳細使用教程

而這個Demo也是上面那篇文章作者附上的,Demo地址是PlaceHolder動態替換


結語:

還是老話,哪裡不對。可以在留言處寫出來。我會進行更正,哈哈。

相關文章