細(xì)細(xì)品讀!深入淺出,官方文檔看ConstraintLayout

本文已授權(quán)微信公眾號(hào) 【Android技術(shù)經(jīng)驗(yàn)分享】 發(fā)布
轉(zhuǎn)載請(qǐng)注明出處:http://www.lxweimin.com/p/38ee0aa654a8

寫(xiě)在前面

之前品讀了郭霖大神寫(xiě)的《Android新特性介紹,ConstraintLayout完全解析》,受其感染,寫(xiě)了一篇《未來(lái)布局之星——ConstraintLayout》,回過(guò)頭來(lái)看,感覺(jué)這一篇文章太注重可視化操作,于是去翻閱了一下ConstraintLayout的官方文檔,決定從官方文檔的角度在代碼層面來(lái)了解一下ConstraintLayout。

繼承關(guān)系

繼承關(guān)系

ConstraintLayout和其他布局一樣,繼承自ViewGroup,但是不同點(diǎn)在于它調(diào)整控件的位置和大小時(shí)更加得靈活,功能更加強(qiáng)大。

版本支持

ConstraintLayout是一個(gè)Support庫(kù),意味著向前兼容,它可以兼容至API 9,也就是Android 2.3,鑒于現(xiàn)在市場(chǎng)上手機(jī)基本都是2.3及以上的,所以如果不是特殊情況,開(kāi)發(fā)者可以不用考慮版本問(wèn)題。下面是官網(wǎng)文檔引文:

Note: ConstraintLayout is available as a support library that you can use on Android systems starting with API level 9 (Gingerbread).

新特性

相對(duì)于傳統(tǒng)布局,ConstraintLayout在以下方面提供了一些新的特性:

  • 相對(duì)定位
  • 外邊距
  • 居中和傾向
  • 可見(jiàn)性的表現(xiàn)
  • 尺寸約束
  • Chain
  • 輔助工具

接下來(lái)就這些新特性進(jìn)行詳細(xì)了解。

相對(duì)定位

相對(duì)定位是在ConstraintLayout中創(chuàng)建布局的最基本構(gòu)建塊,也就是一個(gè)控件相對(duì)于另一個(gè)控件進(jìn)行定位,可以從橫向、縱向添加約束關(guān)系,用到的邊分別有:

  • 橫向:Left、Right、Start、End
  • 縱向:Top、Bottom、Baseline(文本底部的基準(zhǔn)線)
    通常是一條邊另一條邊添加約束,就像下面按鈕B要定位在按鈕A的右邊一樣:
Fig. 1 - 相對(duì)定位例子

這種情況代碼實(shí)現(xiàn)是這樣的:

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

這樣系統(tǒng)就會(huì)知道按鈕B的左側(cè)被約束在按鈕A的右側(cè),這里的約束可以理解為邊的對(duì)齊。

Fig. 2 - 相對(duì)定位的約束

上圖是相對(duì)定位的約束,圖中每一條邊(top、bottom、baseline、left、start、right、end)都可以與其他控件形成約束,羅列這些邊形成的相對(duì)定位關(guān)系如下:

* layout_constraintLeft_toLeftOf          // 左邊左對(duì)齊
* layout_constraintLeft_toRightOf         // 左邊右對(duì)齊
* layout_constraintRight_toLeftOf         // 右邊左對(duì)齊
* layout_constraintRight_toRightOf        // 右邊右對(duì)齊
* layout_constraintTop_toTopOf            // 上邊頂部對(duì)齊
* layout_constraintTop_toBottomOf         // 上邊底部對(duì)齊
* layout_constraintBottom_toTopOf         // 下邊頂部對(duì)齊
* layout_constraintBottom_toBottomOf      // 下邊底部對(duì)齊
* layout_constraintBaseline_toBaselineOf  // 文本內(nèi)容基準(zhǔn)線對(duì)齊
* layout_constraintStart_toEndOf          // 起始邊向尾部對(duì)齊
* layout_constraintStart_toStartOf        // 起始邊向起始邊對(duì)齊
* layout_constraintEnd_toStartOf          // 尾部向起始邊對(duì)齊
* layout_constraintEnd_toEndOf            // 尾部向尾部對(duì)齊

上面的這些屬性需要結(jié)合id才能進(jìn)行約束,這些id可以指向控件也可以指向父容器(也就是ConstraintLayout),比如:

<Button android:id="@+id/buttonB" ...
    app:layout_constraintLeft_toLeftOf="parent" />

外邊距

Fig. 3 - 相對(duì)定位的外邊距

這里的外邊距相信大家都理解,這里就不贅述了,羅列外邊距的屬性如下:

* android:layout_marginStart
* android:layout_marginEnd
* android:layout_marginLeft
* android:layout_marginTop
* android:layout_marginRight
* android:layout_marginBottom

來(lái)主要看一下外邊距的新屬性:GONE MARGIN
以圖 3為例,這里的gone margin指的是B向A添加約束后,如果A的可見(jiàn)性變?yōu)镚ONE,這時(shí)候B的外邊距可以改變,也就是B的外邊距根據(jù)A的可見(jiàn)性分為兩種狀態(tài)。

* layout_goneMarginStart
* layout_goneMarginEnd
* layout_goneMarginLeft
* layout_goneMarginTop
* layout_goneMarginRight
* layout_goneMarginBottom

居中和傾向

居中

相對(duì)定位一小節(jié),我們了解了兩個(gè)控件之間添加約束,現(xiàn)在來(lái)看看一個(gè)控件和父布局(ConstraintLayout)建立約束。當(dāng)相同方向上(橫向或縱向),控件兩邊同時(shí)向ConstraintLayout添加約束,情況就會(huì)像圖 4所示的這樣。

Fig. 4 - 居中定位

而代碼的書(shū)寫(xiě)是這樣的:

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

這種情況就感覺(jué)像是控件兩邊有兩個(gè)反向相等大小的力在拉動(dòng)它一樣,所以才會(huì)產(chǎn)生控件居中的效果。

這里說(shuō)明一下:如果在居中方向上(橫向或縱向)控件的尺寸和ConstraintLayout的尺寸一樣,那么就無(wú)所謂居中了,此時(shí)約束的存在是沒(méi)有意義的。

傾向

在這種約束是同向相反的情況下,默認(rèn)控件是居中的,但是也可以像拔河一樣,讓兩個(gè)約束的力大小不等,這樣就產(chǎn)生了傾向,其屬性是:

* layout_constraintHorizontal_bias
* layout_constraintVertical_bias
Fig. 5 - 帶傾向的居中

下面這段代碼就是讓左邊占30%,右邊占70%(默認(rèn)兩邊各占50%),這樣左邊就會(huì)短一些,如圖5所示,此時(shí)代碼是這樣的:

<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/>

通過(guò)設(shè)置傾向,可以非常便捷地實(shí)現(xiàn)屏幕適配。

可見(jiàn)性的表現(xiàn)

ConstraintLayout對(duì)可見(jiàn)性被標(biāo)記View.GONE的控件(后稱(chēng)“GONE控件”)有特殊的處理。一般情況下,GONG控件是不可見(jiàn)的,且不再是布局的一部分,但是在布局計(jì)算上,ConstraintLayout與傳統(tǒng)布局有一個(gè)很重要的區(qū)別:

  • 傳統(tǒng)布局下,GONE控件的尺寸會(huì)被認(rèn)為是0(當(dāng)做點(diǎn)來(lái)處理)
  • 在ConstraintLayout中,GONE控件尺寸仍然按其可見(jiàn)時(shí)的大小計(jì)算,但是其外邊距大小按0計(jì)算
Fig. 6 - 可見(jiàn)時(shí)的表現(xiàn)

這種特殊的行為讓我們?cè)跓o(wú)需打亂布局情況下,在標(biāo)記GONE控件的地方構(gòu)建布局,這樣的做法對(duì)于做簡(jiǎn)單的布局動(dòng)畫(huà)很有用。

說(shuō)明一下:筆者在理解原文這句話的時(shí)候不是很理解,希望有經(jīng)驗(yàn)的讀者可以指點(diǎn)迷津,原文如下:

This specific behavior allows to build layouts where you can temporarily mark widgets as being GONE, without breaking the layout (Fig. 6), which can be particularly useful when doing simple layout animations.

關(guān)于目標(biāo)控件(如圖 6中的A)設(shè)置為GONE時(shí),受約束的控件(如圖 6中的B)的外邊距的變化設(shè)置請(qǐng)查看上面的外邊距小節(jié)的GONE MARGIN屬性。

Note:The margin used will be the margin that B had defined when connecting to A (see Fig. 6 for an example). In some cases, this might not be the margin you want (e.g. A had a 100dp margin to the side of its container, B only a 16dp to A, marking A as gone, B will have a margin of 16dp to the container). For this reason, you can specify an alternate margin value to be used when the connection is to a widget being marked as gone (seethe section above about the gone margin attributes).

尺寸約束

ConstraintLayout中的最小尺寸
ConstraintLayout本身可以定義自己的最小尺寸:

  • android:minWidth 設(shè)置布局的最小寬度
  • android:minHeight 設(shè)置布局的最小高度

這些最小尺寸當(dāng)ConstraintLayout被設(shè)置為WRAP_CONTENT時(shí)有效。

控件尺寸約束
控件的尺寸可以通過(guò)android:layout_widthandroid:layout_height來(lái)設(shè)置,有三種方式:

  • 使用固定值
  • 使用WRAP_CONTENT
  • 使用0dp(相當(dāng)于MATCH_CONSTRAINT
Fig. 7 - 尺寸約束

前兩種方式和其他布局的用法相同,最后一種是通過(guò)填充約束來(lái)重新設(shè)置控件的尺寸(如圖 7,(a)是wrap_content,(b)是0dp)。代碼案例如下:

<Button android:layout_width="0dp" // 這里對(duì)寬度設(shè)置MATCH_CONSTRAINT,結(jié)合3、4兩行實(shí)現(xiàn)約束
    android:layout_height="wrap_content"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"/>

如果控件設(shè)置了外邊距,那么外邊距就會(huì)在尺寸計(jì)算中被考慮進(jìn)去,效果如圖圖 7 (c)所示。

敲黑板,劃重點(diǎn):一般MATCH_PARENT在ConstraintLayout布局下是不支持的,但是在簡(jiǎn)單的布局結(jié)構(gòu)(如控件的約束只與ConstraintLayout關(guān)聯(lián))下,MATCH_PARENT是被支持的,其作用與MATCH_CONSTRAINT相同。

比例
這里的比例指的是寬高比,通過(guò)設(shè)置比例,讓寬高的其中一個(gè)隨另一個(gè)變化。為了實(shí)現(xiàn)比例,需要讓控件寬或高受約束,且尺寸設(shè)置為0dp(也可以是MATCH_CONSTRAINT),實(shí)現(xiàn)代碼如下:

<Button android:layout_width="wrap_content"
    android:layout_height="0dp"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintDimensionRatio="1:1" />

上述代碼中,按鈕的高度滿足受約束且設(shè)置為0dp的條件,所以其尺寸會(huì)按照比例寬度調(diào)整。

比例的設(shè)置有兩種格式:

  • 寬度與高度的,可理解為受約束的一方尺寸:另一方尺寸
  • 受約束的一方尺寸/另一方尺寸得到的浮點(diǎn)數(shù)

如果寬高都設(shè)置了MATCH_CONSTRAINT0dp)和約束,那么需要在比例前添加W,H,以確定受約束的是高還是寬,然后受約束的一方根據(jù)不受約束的一方,按照比例計(jì)算自己的尺寸。

關(guān)于上面這段話,原文是:

You can also use ratio if both dimensions are set to MATCH_CONSTRAINT (0dp). In this case the system sets the largest dimensions the satisfies all constraints and maintains the aspect ratio specified. To constrain one specific side based on the dimensions of another. You can pre append W," or H, to constrain the width or height respectively.

筆者個(gè)人感覺(jué)官方文檔對(duì)于這一塊表述較模糊,可能跟自己的英語(yǔ)閱讀能力有關(guān),望有經(jīng)驗(yàn)的過(guò)來(lái)人答疑解惑。

關(guān)于上述的案例,代碼如下:

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

上述代碼對(duì)寬度和高度都進(jìn)行了約束,通過(guò)H,指定高度受約束,所以高度的尺寸會(huì)根據(jù)寬度大小按照比例得到,其效果如圖所示:

Ratio

至于為何高度填充屏幕而寬度不填充,其玄機(jī)在于下面這句話,能理解它,就理解了比例使用的精髓:

In this case the system sets the largest dimensions the satisfies all constraints and maintains the aspect ratio specified.

Chain

Chain是一系列雙向連接的控件連接在一起的形態(tài)(如圖 8所示,是最小單位的Chain,只有兩個(gè)組件)。

Fig. 8 - Chain

Chain頭部
橫向上,Chain頭部是Chain最左邊的控件;縱向上,Chain頭部是Chain最頂部的控件。

Chain外邊距
如果連接時(shí)定義了外邊距,Chain就會(huì)發(fā)生變化。在SPREAD CHAIN中,外邊距會(huì)從已經(jīng)分配好的空間中去掉。原文如下:

If margins are specified on connections, they will be taken in account. In the case of spread chains, margins will be deducted from the allocated space.

Chain樣式
當(dāng)對(duì)Chain的第一個(gè)元素設(shè)置layout_constraintHorizontal_chainStylelayout_constraintVertical_chainStyle屬性,Chain就會(huì)根據(jù)特定的樣式(默認(rèn)樣式為CHAIN_SPREAD)進(jìn)行相應(yīng)變化,樣式類(lèi)型如下:

  • CHAIN_SPREAD 元素被分散開(kāi)(默認(rèn)樣式)
  • CHAIN_SPREAD模式下,如果一些控件被設(shè)置為MATCH_CONSTRAINT,那么控件將會(huì)把所有剩余的空間均分后“吃掉”
  • CHAIN_SPREAD_INSIDE Chain兩邊的元素貼著父容器,其他元素在剩余的空間中采用CHAIN_SPREAD模式
  • CHAIN_PACKED Chain中的所有控件合并在一起后在剩余的空間中居中
Fig. 10 - Chain樣式

帶權(quán)重的Chain
默認(rèn)的Chain會(huì)在空間里平均散開(kāi)。如果其中有一個(gè)或多個(gè)元素使用了MATCH_CONSTRAINT屬性,那么他們會(huì)將剩余的空間平均填滿。屬性layout_constraintHorizontal_heightlayout_constraintVertical_weight控制使用MATCH_CONSTRAINT的元素如何均分空間。
例如,一個(gè)Chain中包含兩個(gè)使用MATCH_CONSTRAINT的元素,第一個(gè)元素使用的權(quán)重為2,第二個(gè)元素使用的權(quán)重為1,那么被第一個(gè)元素占用的空間是第二個(gè)元素的2倍。

  • 輔助工具

這里“輔助工具”的原文是Virtual Helper objects,對(duì)于ConstraintLayout,其輔助工具目前是Guidline。關(guān)于Guideline的了解,讀者們可先嘗試《未來(lái)布局之星——ConstraintLayout》一文中的Guideline操作。在此基礎(chǔ)上,訪問(wèn)Guideline類(lèi)了解詳情,附上Guideline類(lèi)的代碼案例供讀者們了解:

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

相關(guān)方法

Public構(gòu)造方法
ConstraintLayout(Contextcontext)
[ConstraintLayout](https://developer.android.google.cn/reference/android/support/constraint/ConstraintLayout.html#ConstraintLayout(android.content.Context, android.util.AttributeSet))(Contextcontext,AttributeSetattrs)
[ConstraintLayout](https://developer.android.google.cn/reference/android/support/constraint/ConstraintLayout.html#ConstraintLayout(android.content.Context, android.util.AttributeSet, int))(Contextcontext,AttributeSetattrs, int defStyleAttr)
Public方法
void [addView](https://developer.android.google.cn/reference/android/support/constraint/ConstraintLayout.html#addView(android.view.View, int, android.view.ViewGroup.LayoutParams))(Viewchild, int index,ViewGroup.LayoutParamsparams)
ConstraintLayout.LayoutParams generateLayoutParams(AttributeSetattrs)
int getMaxHeight()
int getMaxWidth()
int getMinHeight()
int getMinWidth()
void onViewAdded(Viewview)
void onViewRemoved(Viewview)
void removeView(Viewview)
void requestLayout()
void setConstraintSet(ConstraintSetset)
void setMaxHeight(int value)
void setMaxWidth(int value)
void setMinHeight(int value)
void setMinWidth(int value)
Protected方法
boolean checkLayoutParams(ViewGroup.LayoutParamsp)
ConstraintLayout.LayoutParams generateDefaultLayoutParams()
ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParamsp)
void [onLayout](https://developer.android.google.cn/reference/android/support/constraint/ConstraintLayout.html#onLayout(boolean, int, int, int, int))(boolean changed, int left, int top, int right, int bottom)
void [onMeasure](https://developer.android.google.cn/reference/android/support/constraint/ConstraintLayout.html#onMeasure(int, int))(int widthMeasureSpec, int heightMeasureSpec)

寫(xiě)在最后

第一次用所剩無(wú)幾的英語(yǔ)能力蹩腳地翻譯官方文檔,看著密密麻麻的英文寫(xiě)到這里,如今已經(jīng)頭昏眼花、不知所云,若讀者們有發(fā)現(xiàn)文章錯(cuò)誤的地方,歡迎在文章下方評(píng)論留言。個(gè)人感覺(jué),想要深入理解ConstraintLayout,還是需要多使用、多實(shí)踐,并且要結(jié)合源碼分析,但愿通過(guò)這篇文章的學(xué)習(xí),讀者們可以達(dá)到入門(mén)ConstraintLayout的目的。翻譯不易,轉(zhuǎn)載請(qǐng)注明鏈接,最后附上官方文檔的地址,供讀者們比對(duì)學(xué)習(xí),感謝閱讀!
https://developer.android.google.cn/reference/android/support/constraint/ConstraintLayout.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容