ConstraintLayout控件使用淺析

ConstraintLayout控件使用淺析

標籤(空格分隔): Android


介紹

在介紹ConstraintLayout之前,先進行靈魂三問:ConstraintLayout是什麼?爲什麼要使用ConstraintLayout,它有什麼好處?怎麼用?
nbIKbV.jpg

是什麼?
約束佈局ConstraintLayout 是一個ViewGroup,可以在Api9以上的Android系統使用它,它的出現主要是爲了解決佈局嵌套過多的問題,以靈活的方式定位和調整小部件。從 Android Studio 2.3 起,官方的模板默認使用 ConstraintLayout。

爲什麼用ConstraintLayout?
ConstraintLayout的初衷是減少佈局嵌套,再複雜的佈局也可能由一個ConstraintLayout搞定,再加上近幾年支持的屬性越來越豐富,而且也是Google極力推薦的,ConstraintLayout在今後的開發和優化中一定會越來越不可或缺。甚至可以在一定程度上適配更多的屏幕尺寸。

怎麼用?
↓↓↓

本篇所舉例子和屬性都是基於2.0.0的beta版,引入方式:


    dependencies{
    	implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta3'
    }

位置約束

1.相對位置

相對位置是ConstraintLayout最基本的屬性,類似於RelativeLayout的相對佈局,控制子控件的相對位置。
說起來比較抽象,舉個例子:

nOHvZ9.png

像這樣的佈局需要在代碼中這樣寫:


	<Button
		android:id="@+id/btn_1"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="A"
		app:layout_constraintLeft_toLeftOf="parent"
		app:layout_constraintTop_toTopOf="parent" />
	
	<Button
		android:id="@+id/btn_2"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="B"
		app:layout_constraintLeft_toLeftOf="parent"
		app:layout_constraintTop_toBottomOf="@+id/btn_1" />
	
	<Button
		android:id="@+id/btn_3"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="C"
		app:layout_constraintLeft_toRightOf="@+id/btn_1"
		app:layout_constraintTop_toTopOf="parent" />

可見位置的控制主要是app:layout_constraintTop_toBottomOf="@+id/btn_1"屬性實現的,比如這句屬性表示:該View的頂部對齊至目標View的底部。

該系列屬性大概有12個,分別控制View的top,bottom,left,right,start,end

app:layout_constraintTop_toTopOf="@+id/xxx"
app:layout_constraintTop_toBottomOf="@+id/xxx"
app:layout_constraintBottom_toBottomOf="@+id/xxx"
app:layout_constraintBottom_toTopOf="@+id/xxx"
app:layout_constraintLeft_toLeftOf="@+id/xxx"
app:layout_constraintLeft_toRightOf="@+id/xxx"
app:layout_constraintStart_toStartOf="@+id/xxx"
app:layout_constraintStart_toEndOf="@+id/xxx"
app:layout_constraintRight_toRightOf="@+id/xxx"
app:layout_constraintRight_toLeftOf="@+id/xxx"
app:layout_constraintEnd_toEndOf="@+id/xxx"
app:layout_constraintEnd_toStartOf="@+id/xxx"

其中parent代表父控件,比如讓View居中可以這樣寫:


    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintBottom_toBottomOf="parent"

2.強制約束

如果要實現一個這樣的佈局:

使用ConstraintLayout實現起來代碼是這樣的:


    <Button
		......
	    android:id="@+id/btn_a"
	    app:layout_constraintStart_toStartOf="parent"
	    app:layout_constraintTop_toTopOf="parent" />
    
    <Button
	    android:id="@+id/btn_b"
	    ......
	    app:layout_constraintEnd_toEndOf="parent"
	    app:layout_constraintStart_toEndOf="@+id/btn_a"
	    app:layout_constraintTop_toBottomOf="@+id/btn_a" />

本來可以開心的摸魚了,但有一天A控件的長度變的突然很長,於是出現了下面這樣的情況

B控件並沒有絕對在A的右邊,而且都超出屏幕了,這樣下去肯定不行,於是就要用到強制約束來拯救:

app:layout_constrainedWidth=“true”

這時只要將上句代碼加入到B控件的xml配置中即可解決,效果:

縱向的緯度對應:

app:layout_constrainedHeight=“true”

3.基線對齊

如果子View是TextView還可以使用基線對齊:

layout_constraintBaseline_toBaselineOf="@+id/xxx"

來寫個例子看下對齊效果:

如果TextView的和大小高度可能不同:


可以看到如果是多行的文字,這個屬性只以文字第一行爲基線進行對齊。

使用這個屬性還是需要注意一下的。

4.圓心定位

圓心定位用一張盜來的圖就可以表達的很清楚:

由圖可見,圓心定位至少有三個屬性來確定約束關係

  1. app:layout_constraintCircle="@+id/btn_aa" 被約束控件Id
  1. app:layout_constraintCircleAngle=“0” 圓心偏移角度,豎直正方向方向爲0度。
  1. app:layout_constraintCircleRadius=“68dp” 圓半徑。

由這三個屬性就可以共同使用來進行圓心的約束啦。

下面寫個例子來試試:


	<--首選確定一個圓心View-->
	<Button
    	android:id="@+id/btn_aa"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="88dp"
        android:layout_marginEnd="8dp"
        android:text="圓心"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
		app:layout_constraintBottom_toBottomOf="parent />

	<--約束在圓心0度位置,也就是正上方-->
 	<View
        android:id="@+id/btn_bb"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:background="@color/colorPrimary"
        app:layout_constraintCircle="@+id/btn_aa"
        app:layout_constraintCircleAngle="0"
        app:layout_constraintCircleRadius="68dp" />

	<--約束在圓心45度位置,也就是右上方-->
    <View
        android:id="@+id/btn_cc"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:background="@color/colorPrimary"
        app:layout_constraintCircle="@+id/btn_aa"
        app:layout_constraintCircleAngle="45"
        app:layout_constraintCircleRadius="68dp" />

來看下效果:

我又在以上代碼上多添加了幾個約束,實現了一個鐘錶效果,其中綠色方塊中寫的是約束角度。

5.百分比約束

百分比約束的屬性有垂直百分比和水平百分比,分別是:

app:layout_constraintHorizontal_bias=“0.3”

app:layout_constraintVertical_bias=“0.3”

水平的百分比約束屬性意思是在確定了View的左側約束點和右側約束點之後,此時這個View相對於左右約束點的位置時居中的,也就是app:layout_constraintHorizontal_bias=“0.5”,如圖:

我將View設置爲相對於父佈局的水平居中位置,也就是

app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

此時切換到Design視圖,點擊該View,然後再右側可以看到

這樣的顯示,其中紅色箭頭所標識的50就代表默認的百分比約束位置。此時可以直接拖動這個浮標來修改水平百分比約束,也可以在代碼中添加**app:layout_constraintHorizontal_bias=“0.3”**屬性來修改(直接拖動浮標會生成這句代碼)。
這就是水平百分比約束的作用,同理,豎直方向上的百分比約束也一樣。

寫個例子試試:


	<Button
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="youlookwhat"
		app:layout_constraintBottom_toBottomOf="parent"
		app:layout_constraintEnd_toEndOf="parent"
		app:layout_constraintHorizontal_bias="0.2"
		app:layout_constraintStart_toStartOf="parent"
		app:layout_constraintTop_toTopOf="parent"
		app:layout_constraintVertical_bias="0.25" />

此按鈕在相對於父佈局水平方向20%,相對於父佈局豎直方向25%的位置。

6.Chains(鏈)

一組控件通過雙向約束關聯起來就形成了鏈。如圖:

鏈的兩端一定是約束於parent的,鏈的風格由一條鏈的第一個控件決定:

app:layout_constraintHorizontal_chainStyle=“spread”

layout_constraintHorizontal_chainStyle有三個枚舉值:

  • spread:均勻分配鏈所在緯度的所有空間 (默認)
  • spread_inside:將第一個元素和最後一個元素放置在邊緣上,並均勻分佈其餘元素
  • packed:將鏈中元素居中在鏈條的中心

一張圖來說明:
nqlcSx.gif

  • 注意一條鏈的屬性是由這條鏈的頭結點控制的

同時鏈還支持權重,如果將width設置爲0dp,並且增加layout_constraintHorizontal_weight屬性來設置權重,就可以創建出一條橫向的權重鏈了。

app:layout_constraintHorizontal_weight=“2” :水平方向上控件所佔權重

app:layout_constraintVertical_weight=“1” :豎直方向上控件所佔權重

舉個例子,來創建一個水平方向權重爲1,2,1的鏈:


    <Button
        android:id="@+id/btn_1"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="窩窩頭"
        app:layout_constraintHorizontal_chainStyle="spread"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/btn_2"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="一塊錢四個"
        app:layout_constraintHorizontal_weight="2"
        app:layout_constraintLeft_toRightOf="@+id/btn_1"
        app:layout_constraintRight_toLeftOf="@+id/btn_3"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_3"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="嘿嘿"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintLeft_toRightOf="@+id/btn_2"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

效果:

7.指定控件寬高百分比大小

ConstantLayout還可以指定子View的百分比尺寸(太強大了吧)。

一起來見證一下這個屬性:

app:layout_constraintHeight_percent=“0.5” :高相對於父佈局的百分比,取值0~1

app:layout_constraintWidth_percent=“0.5” :寬相對於父佈局的百分比

舉個例子,將下面的ImageView寬度設爲父佈局的一半,高度也是父佈局的一半:


    <ImageView
	    android:id="@+id/iv_111"
	    android:layout_width="0dp"
	    android:layout_height="0dp"
	    android:scaleType="fitXY"
	    android:src="@mipmap/hentai"
	    app:layout_constraintBottom_toBottomOf="parent"
	    app:layout_constraintHeight_percent="0.5"
	    app:layout_constraintLeft_toLeftOf="parent"
	    app:layout_constraintRight_toRightOf="parent"
	    app:layout_constraintTop_toTopOf="parent"
	    app:layout_constraintWidth_percent="0.5" />

效果:

  • 注意:當寬/高設爲父佈局的百分比後,寬/高也要指定爲0dp,否則不會生效。

8.goneMargin(隱藏邊距)

在子view的狀態爲gone時,goneMargin設置的邊距開始生效。

layout_goneMarginStart
layout_goneMarginEnd
layout_goneMarginLeft
layout_goneMarginTop
layout_goneMarginRight
layout_goneMarginBottom

9.指定寬高比

ConstantsLayout還有一個非常有趣的屬性:

app:layout_constraintDimensionRatio=“w,16:9”

可以通過該屬性來指定子控件的期望寬高比。

舉個例子來看,將一個圖片寬高比設置爲16:9,xml佈局:


    <ImageView
    	android:id="@+id/iv_111"
    	android:layout_width="0dp"
    	android:layout_height="wrap_content"
    	android:scaleType="fitXY"
    	android:src="@mipmap/hentai"
    	app:layout_constraintBottom_toBottomOf="parent"
    	app:layout_constraintDimensionRatio="w,16:9"
    	app:layout_constraintLeft_toLeftOf="parent"
    	app:layout_constraintRight_toRightOf="parent"
    	app:layout_constraintTop_toTopOf="parent" />

來看下效果,左圖是圖片原始效果,右圖是指定寬高後的亞子


說明一下,layout_constraintDimensionRatio比例前綴默認爲h,思是寬高比例,例如h,3:2意思爲寬3:高2,而指定了屬性w,3:2,意思就成了高寬比,即高3:寬2。

再說明一下,經過我的測試發現若要成功實現期望寬高比,寬或高一方要有固定的值或wrap_content,另一個屬性則要設爲0dp,否則該屬性不會生效。

輔助類

Group羣組

ConstrantLayout提供了Group來標記多個控件的羣組,羣組可以指定多個控件,並通過控制Group來實現對這多個控件的操作。

Group羣組是一個獨立的View控件,但不會真正顯示在屏幕上,該View最常用的屬性是constraint_referenced_ids,用來指定羣組中包含的id。

app:constraint_referenced_ids=“btn_a_1,btn_a_2,btn_a_3” :指定羣組包含的控件id。


    <androidx.constraintlayout.widget.Group
    	android:id="@+id/group_a"
    	android:layout_width="wrap_content"
    	android:layout_height="wrap_content"
    	app:constraint_referenced_ids="btn_a_1,btn_a_2,btn_a_3" />

舉個例子,點擊讓一個Group羣組的控件來顯示隱藏:

java代碼:


    public void showHide(View view) {
    	groupA.setVisibility((groupA.getVisibility() == View.GONE) ? View.VISIBLE : View.GONE);
    }

xml代碼:


    <Button
    	android:id="@+id/btn_a_1"
    	android:layout_width="wrap_content"
    	android:layout_height="wrap_content"
    	android:text="A1"
    	app:layout_constraintLeft_toLeftOf="parent"
    	app:layout_constraintTop_toTopOf="parent" />
    
    <Button
    	android:id="@+id/btn_a_2"
    	android:layout_width="wrap_content"
    	android:layout_height="wrap_content"
    	android:text="A2"
    	app:layout_constraintLeft_toLeftOf="parent"
    	app:layout_constraintTop_toBottomOf="@+id/btn_a_1" />
    
    <Button
    	android:id="@+id/btn_a_3"
    	android:layout_width="wrap_content"
    	android:layout_height="wrap_content"
    	android:text="A3"
    	app:layout_constraintLeft_toLeftOf="parent"
    	app:layout_constraintTop_toBottomOf="@+id/btn_a_2" />
    
    <androidx.constraintlayout.widget.Group
    	android:id="@+id/group_a"
    	android:layout_width="wrap_content"
    	android:layout_height="wrap_content"
    	app:constraint_referenced_ids="btn_a_1,btn_a_2,btn_a_3" />
    
    <Button
    	android:id="@+id/btn_sh"
    	android:layout_width="wrap_content"
    	android:layout_height="wrap_content"
    	android:onClick="showHide"
    	android:text="顯/隱"
    	app:layout_constraintLeft_toLeftOf="parent"
    	app:layout_constraintRight_toRightOf="parent"
    	app:layout_constraintTop_toTopOf="parent" />


看下效果:

Guideline

輔助線Guideline會在預覽的時候幫助你完成佈局,運行時並不會顯示在界面上。

Guideline最常用的兩個屬性是:

android:orientation=“horizontal” :輔助線方向,有兩個枚舉值:horizontal和vertical,顧名思義,代表水平和豎直方向。

app:layout_constraintGuide_percent=“0.5” :輔助線相對父佈局百分比位置。

Guideline的使用很簡單,下面舉個例子,來個拍照時常用的黃金分隔輔助線:


    <androidx.constraintlayout.widget.Guideline
    	android:layout_width="wrap_content"
    	android:layout_height="wrap_content"
    	android:orientation="horizontal"
    	app:layout_constraintGuide_percent="0.33" />
    
    <androidx.constraintlayout.widget.Guideline
    	android:layout_width="wrap_content"
    	android:layout_height="wrap_content"
    	android:orientation="horizontal"
    	app:layout_constraintGuide_percent="0.66" />
    
    <androidx.constraintlayout.widget.Guideline
    	android:layout_width="wrap_content"
    	android:layout_height="wrap_content"
    	android:orientation="vertical"
    	app:layout_constraintGuide_percent="0.33" />
    
    <androidx.constraintlayout.widget.Guideline
    	android:layout_width="wrap_content"
    	android:layout_height="wrap_content"
    	android:orientation="vertical"
    	app:layout_constraintGuide_percent="0.66" />

效果:

Barrier屏障

顧名思義就是對需要約束的View控件間施加一個水平或豎直的屏障,來實現一對多或多對多的約束關係。
文字太抽象,還是偷張圖來理解:

如圖,這也是經常碰到的需求,一個View要同時再幾個View的右側,並且這幾個View的長度還是可變的,這時無論依賴哪個單獨View都不滿足需求。這時候就需要Barrier屏障來滿足需求了。

Barrier是一個單獨的View,它不會顯示在界面上,可以放心大膽的用。
Barrier的屬性有:

app:constraint_referenced_ids=“btn_1,btn_2” :列出屏障所包含的View的id,對應於圖中左側的三個View。

app:barrierDirection=“right” :Barrier所在View組的位置,有right,left,top和bottom枚舉值可選。

app:barrierMargin=“10dp” :Barrier屏障與控件間間距。

app:barrierAllowsGoneWidgets=“true” :如果Barrier屏障所包含View被設置爲Gone,是否影響Barrier位置。如果你不想讓Barrier考慮GONE的view,可以通過將屬性barrierAllowsGoneWidgets設置 爲false(默認爲true)來更改此設置。

知道了這四個屬性,就可以得心應手的使用Barrier了,舉個例子:


	<Button
        android:id="@+id/btn_1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="grow"
        android:text="窩窩頭"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_bias="0.2"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.35" />

    <Button
        android:id="@+id/btn_2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="grow"
        android:text="一塊錢四個"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_bias="0.2"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.65" />

    <androidx.constraintlayout.widget.Barrier
        android:id="@+id/barrier_1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:barrierAllowsGoneWidgets="true"
        app:barrierDirection="right"
        app:barrierMargin="10dp"
        app:constraint_referenced_ids="btn_1,btn_2" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="嘿嘿"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toRightOf="@+id/barrier_1"
        app:layout_constraintTop_toTopOf="parent" />

點擊事件:


	public void grow(View view) {
        String str = ((TextView) view).getText().toString();
        str += "~~~";
        ((TextView) view).setText(str);
    }

上面代碼表示爲窩窩頭一塊錢四個 兩個View創建右側屏障Barrier_1,嘿嘿約束在Barrier_1屏障的右側,被屏障包含的窩窩頭和一塊錢四個寬度發生變化時,約束還是關係不會改變。

效果圖:
在這裏插入圖片描述

一不小心就把嘿嘿頂到了窗口外,因爲嘿嘿只有錯側約束了Barrier屏障,並沒有爲右側添加約束關係,所以才導致這個bug,同時也要注意本篇文章所講的強制約束屬性。

軟件開發還是要細心,要有窮盡所有可能情況的習慣,避免低級bug。

參考

https://juejin.im/post/5bac92f2f265da0aba70c1bf#heading-1

https://juejin.im/post/5b013e6f51882542c760dc7b

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章