歡迎光臨
每天分享高質量文章

再學一次ConstraintLayout   一些新特性

作者:xfhy

連結:https://juejin.im/post/5c0bd6b05188257c3045dc50

記得很早的時候我給大家寫過一篇ConstraintLayout,那篇文章還比較滿意,偏思考和實踐:

不過ConstraintLayout一直在加一些更加好用的屬性,這篇文章說到了一些之前沒有提過的,而且非常全面。不知道現在最新版有沒有又加了一些屬性~~

 

再者對於新技術,不妨不要抱著抵制的心理,逼迫自己去用也是一種學習的方式,可能用起來之後就是真香了,就像我之前很抵制 databinding,偶爾用了幾次之後,無法自拔~

平時使用ConstraintLayout,斷斷續續的,基本都是在自己的小demo裡面使用.公司的專案暫時還沒有使用.這次公司專案需要大改,我決定用上這個nice的佈局.減少巢狀(之前的老程式碼,實在是巢狀得太深了….無力吐槽).

首先,ConstraintLayout是一個新的佈局,它是直接繼承自ViewGroup的,所以在相容性方面是非常好的.官方稱可以相容到API 9.可以放心食用.

一、Relative positioning

先來看看下麵一段簡單示例:

 

<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="按鈕1"/>

    <Button
        android:id="@+id/btn2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toRightOf="@+id/btn1"
        android:text="按鈕2"/>

android.support.constraint.ConstraintLayout>

 

 

 

上面有一個簡單的屬性:layout_constraintLeft_toRightOf,表示將按鈕2放到按鈕1的左邊.如果沒有這一句屬性,那麼兩個按鈕會重疊在一起,就像FrameLayout.

像這樣的屬性還有很多:

 


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

上面的屬性都非常好理解,除了一個相對陌生的layout_constraintBaseline_toBaselineOf基線對齊.咱們上程式碼:

 

<TextView
    android:id="@+id/btn1"
    android:text="按鈕1"
    android:textSize="26sp"/>

<TextView
    android:id="@+id/btn2"
    android:text="按鈕2"
    app:layout_constraintBaseline_toBaselineOf="@+id/btn1"
    app:layout_constraintLeft_toRightOf="@+id/btn1"/>

 

 

一目瞭然,相當於文字的基線是對齊了的.如果沒有加layout_constraintBaseline_toBaselineOf屬性,那麼是下麵這樣的:

 

二、與父親邊緣對齊

當需要子view放在父view的底部或者最右側時. 我們使用:

 

<android.support.constraint.ConstraintLayout
    app:layout_constraintEnd_toEndOf="parent">

    <TextView
        android:text="按鈕2"
        app:layout_constraintBottom_toBottomOf="parent"/>

android.support.constraint.ConstraintLayout>

 

 

app:layout_constraintBottom_toBottomOf="parent"  我的底部與父親底部對齊
app:layout_constraintTop_toTopOf="parent"   我的頂部與父親的頂部對齊
app:layout_constraintLeft_toLeftOf="parent"  我的左側與父親的左側對齊
app:layout_constraintRight_toRightOf="parent"  我的右側與父親的右側對齊

三、居中對齊

 

下麵的TextView,與父親左側對齊,與父親右側對齊,所以,最右,它水平居中對齊.

 


<TextView
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"/>

 

 

可能你也想到了,居中對齊其實就是2個對齊方式相結合.最後產生的效果.  比如:

 

這是垂直居中
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"

 


位於父親的正中央
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"

四、邊距

 

邊距和原來是一樣的.

 

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

 

舉個例子:

 

<TextView
    android:id="@+id/btn1"
    android:text="按鈕1"
    android:textSize="26sp"/>
<TextView
    android:id="@+id/btn2"
    android:text="按鈕2"
    android:layout_marginStart="40dp"
    app:layout_constraintLeft_toRightOf="@+id/btn1"/>

 

效果如下:

 

Bias(偏向某一邊)

 

上面的水平居中,是使用的與父親左側對齊+與父親右側對齊.  可以理解為左右的有一種約束力,預設情況下,左右的力度是一樣大的,那麼view就居中了.

當左側的力度大一些時,view就會偏向左側.就像下麵這樣.

 

 

當我們需要改變這種約束力的時候,需要用到如下屬性:

 

layout_constraintHorizontal_bias  水平約束力
layout_constraintVertical_bias  垂直約束力

來舉個例子:

<android.support.constraint.ConstraintLayout
    <Button
        android:text="按鈕1"
        app:layout_constraintHorizontal_bias="0.3"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>
android.support.constraint.ConstraintLayout>    

 

 

可以看到,左右有2根約束線.左側短一些.那麼就偏向於左側

五、Circular positioning (Added in 1.1)

翻譯為:圓形的定位 ?

這個就比較牛逼了,可以以角度和距離約束某個view中心相對於另一個view的中心,

可能比較抽象,來看看谷歌畫的圖:

 

 

他的屬性有:

 

layout_constraintCircle :取用另一個小部件ID
layout_constraintCircleRadius :到其他小部件中心的距離
layout_constraintCircleAngle :小部件應該處於哪個角度(以度為單位,從0360

舉個例子:

<Button
    android:id="@+id/btn1"
    android:text="按鈕1"/>
<Button
    android:text="按鈕2"
    app:layout_constraintCircle="@+id/btn1"
    app:layout_constraintCircleRadius="100dp"
    app:layout_constraintCircleAngle="145"/>

 

六、Visibility behavior 可見性行為

當一個View在ConstraintLayout中被設定為gone,那麼你可以把它當做一個點(這個view所有的margin都將失效).  這個點是假設是實際存在的.

 

 

舉個例子:

 


<Button
    android:id="@+id/btn1"
    android:text="按鈕1"
    android:textSize="26sp"/>


<Button
    android:id="@+id/btn2"
    android:layout_marginStart="20dp"
    android:text="按鈕2"
    android:visibility="gone"
    app:layout_constraintLeft_toRightOf="@+id/btn1"/>

<Button
    android:id="@+id/btn3"
    android:layout_marginStart="20dp"
    android:text="按鈕3"
    app:layout_constraintLeft_toRightOf="@+id/btn2"/>

 

 

可以看到,按鈕3和按鈕1中間的margin只有20.

再舉個例子:

 

 <Button
    android:id="@+id/btn2"
    android:layout_marginStart="20dp"
    android:text="按鈕2"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"/>

<Button
    android:id="@+id/btn3"
    android:text="按鈕3"
    app:layout_constraintLeft_toRightOf="@+id/btn2"
    app:layout_constraintTop_toTopOf="@+id/btn2"
    app:layout_constraintBottom_toBottomOf="@+id/btn2"/>

 

我將按鈕3放到按鈕2的右側,這時是沒有給按鈕2加android:visibility=”gone”的.

 

 

現在我們來給按鈕2加上android:visibility=”gone”

 

 

這時,按鈕2相當於縮小成一個點,那麼按鈕3還是在他的右側不離不棄.

七、Dimensions constraints 尺寸限制

在ConstraintLayout中,可以給一個view設定最小和最大尺寸.

屬性如下(這些屬性只有在給出的寬度或高度為wrap_content時才會生效):

 

android:minWidth 設定佈局的最小寬度
android:minHeight 設定佈局的最小高度
android:maxWidth 設定佈局的最大寬度
android:maxHeight 設定佈局的最大高度

八、Widgets dimension constraints 寬高約束


平時我們使用android:layout_width和 android:layout_height來指定view的寬和高.

在ConstraintLayout中也是一樣,只不過多了一個0dp.

  • 使用長度,例如

  • 使用wrap_content,view計算自己的大小

  • 使用0dp,相當於“ MATCH_CONSTRAINT”

 

下麵是例子

<Button
    android:id="@+id/btn1"
    android:layout_width="100dp"
    android:layout_height="wrap_content"
    android:text="按鈕1"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"/>

<Button
    android:id="@+id/btn2"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:text="按鈕2"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/btn1"/>


<Button
    android:id="@+id/btn3"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginStart="60dp"
    android:text="按鈕3"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/btn2"/>

 

展示出來的是:

 

九、WRAP_CONTENT:強制約束(在1.1中新增)

當一個view的寬或高,設定成wrap_content時,如果裡面的內容實在特別寬的時候,他的約束會出現問題.我們來看一個小慄子:

 
<Button
    android:id="@+id/btn1"
    android:layout_width="100dp"
    android:layout_height="wrap_content"
    android:text="Q"/>

<Button
    android:id="@+id/btn2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV"
    app:layout_constraintLeft_toRightOf="@id/btn1"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toBottomOf="@id/btn1"/>

 

 

從右側的圖片可以看出,按鈕2裡面的內容確實是在按鈕1的內容的右側.但是按鈕2整個來說,卻是沒有整個的在按鈕1的右側.

這時需要用到下麵2個屬性

 

app:layout_constrainedWidth=”true|false”
app:layout_constrainedHeight=”true|false

 

給按鈕2加一個app:layout_constrainedWidth=”true”,來看效果:

 

 

哈哈,又看到了我們想要的效果.爽歪歪.

十、MATCH_CONSTRAINT尺寸(在1.1中新增)

當一個view的長寬設定為MATCH_CONSTRAINT(即0dp)時,預設是使該view佔用所有的可用的空間. 這裡有幾個額外的屬性

父級的百分比

 

layout_constraintWidth_min和layout_constraintHeight_min:將設定此維度的最小大小
layout_constraintWidth_max和layout_constraintHeight_max:將設定此維度的最大大小
layout_constraintWidth_percent和layout_constraintHeight_percent:將此維度的大小設定為

 

這裡簡單舉個百分比的例子:居中並且view的寬是父親的一半

 
 <Button
    android:id="@+id/btn1"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:text="Q"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintWidth_percent="0.5"/>

 

 

It’s so easy! 這極大的減少了我們的工作量.

註意

  • 百分比佈局是必須和MATCH_CONSTRAINT(0dp)一起使用

  • layout_constraintWidth_percent 或layout_constraintHeight_percent屬性設定為0到1之間的值

十一、按比例設定寬高(Ratio)

可以設定View的寬高比例,需要將至少一個約束維度設定為0dp(即MATCH_CONSTRAINT),再設定layout_constraintDimensionRatio.

舉例子:

<Button
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:text="按鈕"
    app:layout_constraintDimensionRatio="16:9"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"/>

 

 

該比率可表示為:

  • 浮點值,表示寬度和高度之間的比率

  • “寬度:高度”形式的比率

如果兩個尺寸都設定為MATCH_CONSTRAINT(0dp),也可以使用比率。在這種情況下,系統設定滿足所有約束的最大尺寸並保持指定的縱橫比。要根據另一個特定邊的尺寸限制一個特定邊,可以預先附加W,“或” H,分別約束寬度或高度。例如,如果一個尺寸受兩個標的約束(例如,寬度為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(鏈)

設定屬性layout_constraintHorizontal_chainStyle或layout_constraintVertical_chainStyle鏈的第一個元素時,鏈的行為將根據指定的樣式(預設值CHAIN_SPREAD)更改。

  • CHAIN_SPREAD – 元素將展開(預設樣式)

  • 加權連結CHAIN_SPREAD樣式,如果設定了一些小部件MATCH_CONSTRAINT,它們將分割可用空間

  • CHAIN_SPREAD_INSIDE – 類似,但鏈的端點不會分散

  • CHAIN_PACKED – 鏈條的元素將被包裝在一起。然後,子項的水平或垂直偏差屬性將影響打包元素的定位

 

 

下麵是一個類似LinearLayout的weight的效果,需要用到layout_constraintHorizontal_weight屬性:

 

    android:id="@+id/btn1"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:text="A"
    app:layout_constraintEnd_toStartOf="@id/btn2"
    app:layout_constraintHorizontal_chainStyle="spread"
    app:layout_constraintHorizontal_weight="1"
    app:layout_constraintStart_toStartOf="parent"/>


    android:id="@+id/btn2"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:text="按鈕2"
    app:layout_constraintEnd_toStartOf="@id/btn3"
    app:layout_constraintHorizontal_weight="2"
    app:layout_constraintStart_toEndOf="@id/btn1"/>

    android:id="@+id/btn3"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:text="問問"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_weight="3"
    app:layout_constraintStart_toEndOf="@id/btn2"/>

例子的效果圖如下:

 

十三、Guideline

這是一個虛擬檢視

Guideline可以建立相對於ConstraintLayout的水平或者垂直準線. 這根輔助線,有時候可以幫助我們定位.

 

layout_constraintGuide_begin   距離父親起始位置的距離(左側或頂部)
layout_constraintGuide_end    距離父親結束位置的距離(右側或底部)
layout_constraintGuide_percent    距離父親寬度或高度的百分比(取值範圍0-1)

 


我們拿輔助線幹嘛??? 比如有時候,可能會有這樣的需求,有兩個按鈕,在螢幕中央一左一右.  如果是以前的話,我會搞一個LinearLayout,.然後將LinearLayout居中,然後按鈕一左一右.

效果圖如下:

 

 

現在我們使用Guideline的話,就超級方便了,看程式碼:


<android.support.constraint.Guideline
    android:id="@+id/gl_center"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    app:layout_constraintGuide_percent="0.5"/>

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="按鈕1"
    app:layout_constraintEnd_toStartOf="@id/gl_center"/>

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="按鈕2"
    app:layout_constraintLeft_toRightOf="@id/gl_center"/>

十四、Barrier

虛擬檢視

Barrier是一個類似於屏障的東西.它和Guideline比起來更加靈活.它可以用來約束多個view.

比如下麵的姓名和聯絡方式,右側的EditText是肯定需要左側對齊的,左側的2個TextView可以看成一個整體,Barrier會在最寬的那個TextView的右邊,然後右側的EditText在Barrier的右側.

 

 

Barrier有2個屬性

  • barrierDirection,取值有top、bottom、left、right、start、end,用於控制 Barrier 相對於給定的 View 的位置。比如在上面的慄子中,Barrier 應該在 姓名TextView 的右側,因此這裡取值right(也可end,可隨意使用.這個right和end的問題,其實在RelativeLayout中就有體現,在RelativeLayout中寫left或者right時會給你一個警告,讓你換成start和end)。

  • constraint_referenced_ids,取值是要依賴的控制元件的id(不需要@+id/)。Barrier 將會使用ids中最大的一個的寬(高)作為自己的位置。

ps:這個東西有一個小坑,如果你寫完程式碼,發現沒什麼問題,但是預覽出來的效果卻不是你想要的.這時,執行一下程式即可.然後預覽就正常了,在手機上展示的也是正常的.

例子的程式碼如下(如果預覽不正確,那麼一定要執行一下,不要懷疑是自己程式碼寫錯了):

 

<TextView
    android:id="@+id/tv_name"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="姓名:"
    app:layout_constraintBottom_toBottomOf="@id/tvTitleText"/>

<TextView
    android:id="@+id/tv_phone"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="聯絡方式:"
    app:layout_constraintBottom_toBottomOf="@id/tvContentText"
    app:layout_constraintTop_toBottomOf="@+id/tv_name"/>

<EditText
    android:id="@+id/tvTitleText"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="null"
    android:text="張三"
    android:textSize="14sp"
    app:layout_constraintStart_toEndOf="@+id/barrier2"/>

<EditText
    android:id="@+id/tvContentText"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="null"
    android:text="xxxxxxxxxxxxxxx"
    android:textSize="14sp"
    app:layout_constraintStart_toEndOf="@+id/barrier2"
    app:layout_constraintTop_toBottomOf="@+id/tvTitleText"/>

<android.support.constraint.Barrier
    android:id="@+id/barrier2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:barrierDirection="right"
    app:constraint_referenced_ids="tv_name,tv_phone"/

十五、Group

固定思議,這是一個組.  這也是一個虛擬檢視.

可以把View放到裡面,然後Group可以同時控制這些view的隱藏.

 

<android.support.constraint.Group
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:visibility="gone"
    app:constraint_referenced_ids="btn1,btn2"/>

<Button
    android:id="@+id/btn1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="按鈕1"/>

<Button
    android:id="@+id/btn2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="按鈕2"
    app:layout_constraintTop_toBottomOf="@id/btn1"/>

  1. Group有一個屬性constraint_referenced_ids,可以將那些需要同時隱藏的view丟進去.

  2. 別將view放Group包起來.這樣會報錯,因為Group只是一個不執行onDraw()的View.

  3. 使用多個 Group 時,儘量不要將某個View重覆的放在 多個 Group 中,實測可能會導致隱藏失效.

十六、何為虛擬檢視

上面我們列舉的虛擬檢視一共有:

  1. Guideline

  2. Barrier

  3. Group

來我們看看原始碼

//Guideline

public class Guideline extends View {
public Guideline(Context context) {
    super(context);
    //這個8是什麼呢?  
    //public static final int GONE = 0x00000008;
    //其實是View.GONE的值
    super.setVisibility(8);
}

public Guideline(Context context, AttributeSet attrs) {
    super(context, attrs);
    super.setVisibility(8);
}

public Guideline(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    super.setVisibility(8);
}

public Guideline(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr);
    super.setVisibility(8);
}

//可見性永遠為GONE
public void setVisibility(int visibility) {
}

//沒有繪畫
public void draw(Canvas canvas) {
}

//大小永遠為0
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    this.setMeasuredDimension(00);
}

 

我們看到Guideline其實是一個普通的View,然後在建構式裡將自己設定為GONE

  • 並且setVisibility()為空方法,該View就永遠為GONE了.

  • draw()方法為空,意思是不用去繪畫.

  • onMeasure()中將自己長寬設定成0.

綜上所述,我覺得這個Guideline就是一個不可見的且不用測量,不用繪製,那麼我們就可以忽略其繪製消耗.

然後Barrier和Group都是繼承自ConstraintHelper的,ConstraintHelper是一個View.ConstraintHelper的onDraw()和onMeasure()如下:

 

public void onDraw(Canvas canvas) {
}

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //mUseViewMeasure一直是false,在Group中用到了,但是還是將它置為false了.
    if (this.mUseViewMeasure) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    } else {
        this.setMeasuredDimension(00);
    }

}

 

哈哈,其實是和Guideline一樣的嘛,還是可以忽略其帶來的效能消耗嘛.上面的mUseViewMeasure一直是false,所以長寬一直為0.

所以我們可以將Guideline,Barrier,Group視為虛擬試圖,因為它們幾乎不會帶來多的繪製效能損耗.我是這樣理解的.

十七、Optimizer最佳化(add in 1.1)

可以透過將標簽app:layout_optimizationLevel元素新增到ConstraintLayout來決定應用哪些最佳化。這個我感覺還處於實驗性的階段,暫時先別用..哈哈

使用方式如下:

    app:layout_optimizationLevel="standard|dimensions|chains"

 

  • none:不最佳化

  • standard:預設,僅最佳化直接和障礙約束

  • direct:最佳化直接約束

  • barrier:最佳化障礙約束

  • chain:最佳化鏈條約束

  • dimensions: 最佳化維度測量,減少匹配約束元素的度量數量

總結

我把一些常用的屬性和怎麼用都列舉出來,方便大家查閱.如有不對的地方,歡迎指正.

已同步到看一看
贊(1)

分享創造快樂