常見的約束布局 ConstraintLayout 的實踐使用場景:
- 場景1:固定比例視圖
- 場景2:N等分布局
- 場景3:百分比對齊
- 場景4:角度布局
- 場景5:超長限制強制約束
- 場景6:多組件協(xié)同約束
- 場景7:容器約束下的邊界約束
場景1:固定比例視圖
考慮下面這個場景辐烂,組件寬度撐滿屏幕半哟,高度按「寬度x固定比例」計算。
這樣的布局熬甫,在以往的布局方式下谒获,都需要通過動態(tài)計算后修改高度來實現(xiàn)桃纯,但是通過 ConstraintLayout睦裳,則可以直接在 XML 中實現(xiàn)弄砍。
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="match_parent"
android:layout_height="0dp"
android:src="@drawable/wb_agent_card_bg"
app:layout_constraintDimensionRatio="1:0.34"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
通過 layout_constraintDimensionRatio仙畦,可以很方便的實現(xiàn)比例視圖的控制,同時音婶,比例可以設(shè)置的很靈活慨畸,滿足各種條件的需要。
場景2:N等分布局
常見的N等分布局桃熄,例如三等分布局先口,通常都需要進(jìn)行動態(tài)計算,根據(jù)屏幕寬度瞳收,減去間距后得到每部分的寬度碉京,再動態(tài)設(shè)置給每個元素,而通過ConstraintLayout螟深,則可以直接實現(xiàn)這樣的效果谐宙。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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:layout_marginStart="16dp"
android:layout_marginEnd="16dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/bookCover1"
android:layout_width="0dp"
android:layout_height="0dp"
android:src="@tools:sample/avatars"
app:layout_constraintDimensionRatio="0.74:1"
app:layout_constraintEnd_toStartOf="@+id/bookCover2"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.30"
tools:srcCompat="@tools:sample/avatars" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/bookCover2"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="0.74:1"
app:layout_constraintEnd_toStartOf="@+id/bookCover3"
app:layout_constraintStart_toEndOf="@+id/bookCover1"
app:layout_constraintTop_toTopOf="@+id/bookCover1"
app:layout_constraintWidth_percent="0.30"
tools:srcCompat="@tools:sample/avatars" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/bookCover3"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="0.74:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/bookCover2"
app:layout_constraintTop_toTopOf="@+id/bookCover2"
app:layout_constraintWidth_percent="0.30"
tools:srcCompat="@tools:sample/avatars" />
</androidx.constraintlayout.widget.ConstraintLayout>
效果如下圖所示:
這其中的間距,主要是通過layout_constraintWidth_percent
來設(shè)置在當(dāng)前容器尺寸下所占百分比來進(jìn)一步約束大小界弧。
如果去掉這個屬性凡蜻,那么會直接等分父容器尺寸。
另外垢箕,還可以通過layout_constraintHorizontal_weight屬性來控制類似LinearLayout的weight屬性的效果划栓,實現(xiàn)按權(quán)重進(jìn)行分配。
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/bookCover2"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="centerCrop"
app:layout_constraintDimensionRatio="0.74:1"
app:layout_constraintEnd_toStartOf="@+id/bookCover3"
app:layout_constraintHorizontal_weight="0.5"
app:layout_constraintStart_toEndOf="@+id/bookCover1"
app:layout_constraintTop_toTopOf="@+id/bookCover1"
tools:srcCompat="@tools:sample/avatars" />
場景3:百分比對齊
在ConstraintLayout中,雖然不能使用 -margin 的方式來完成傳統(tǒng)布局中的一些錯位的效果,但是可以借助Space來實現(xiàn)類似的功能畸悬,例如借助Space來實現(xiàn)左邊TextView在右邊TextView某一百分比(或者是dp)對齊的場景汰蓉。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/textView2"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_marginEnd="32dp"
android:background="#bebebe"
android:text="TextView111"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Space
android:id="@+id/space"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="22dp"
app:layout_constraintEnd_toEndOf="@+id/textView2"
app:layout_constraintHorizontal_bias="0.2"
app:layout_constraintStart_toStartOf="@+id/textView2"
app:layout_constraintTop_toBottomOf="@+id/textView2" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView222"
app:layout_constraintEnd_toStartOf="@+id/space"
app:layout_constraintTop_toBottomOf="@+id/space" />
</androidx.constraintlayout.widget.ConstraintLayout>
app:layout_constraintHorizontal_bias="0.2"
水平方向偏移量
由于ConstraintLayout不支持-Margin委煤,所以很多場景下堂油,我們都可以借助Space等輔助元素來實現(xiàn)中轉(zhuǎn),完成傳統(tǒng)布局下通過-Margin實現(xiàn)的效果碧绞。
場景4:角度布局
通過角度的方式來對元素進(jìn)行排列府框,在傳統(tǒng)布局中,只能通過 FrameLayout讥邻,并通過動態(tài)計算的方式迫靖,將角度換算為邊距的方式來布局,但通過ConstraintLayout计维,則變的非常簡單袜香。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_menu"
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_margin="16dp"
android:src="@android:drawable/ic_dialog_email"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:ignore="ContentDescription" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab1"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@android:drawable/ic_dialog_info"
android:tint="#fff"
app:elevation="@null"
app:layout_constraintCircle="@+id/fab_menu"
app:layout_constraintCircleAngle="0"
app:layout_constraintCircleRadius="100dp"
tools:ignore="ContentDescription,MissingConstraints" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab2"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@android:drawable/ic_dialog_info"
android:tint="#fff"
app:elevation="@null"
app:layout_constraintCircle="@+id/fab_menu"
app:layout_constraintCircleAngle="315"
app:layout_constraintCircleRadius="100dp"
tools:ignore="ContentDescription,MissingConstraints" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab3"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@android:drawable/ic_dialog_info"
android:tint="#fff"
app:elevation="@null"
app:layout_constraintCircle="@+id/fab_menu"
app:layout_constraintCircleAngle="270"
app:layout_constraintCircleRadius="100dp"
tools:ignore="ContentDescription,MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>
- layout_constraintCircle:引用另一個控件的 id。
- layout_constraintCircleRadius:到另一個控件中心的距離鲫惶。
- layout_constraintCircleAngle:控件的角度(順時針蜈首,0 - 360 度)
場景5:超長限制強制約束
下面這個場景,最下面的TextView最大不會超過第一個TextView的寬度欠母。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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="300dp"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:ellipsize="end"
android:singleLine="true"
android:text="TextViewTextViewTextViewTextViewTextViewTextViewTextViewTextViewTextViewTextView"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="@+id/textView2"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/textView2"
app:layout_constraintTop_toBottomOf="@+id/textView2" />
</androidx.constraintlayout.widget.ConstraintLayout>
效果如下所示欢策。
這時候就需要通過使用app:layout_constrainedWidth
來使其寬度約束強制生效。
再考慮下面這個場景:
當(dāng)?shù)诙€TextView文字超長的時候赏淌,希望它截斷踩寇,而不會影響左右的TextView。這個場景非常常用六水,在很多業(yè)務(wù)場景下都會使用到這樣的功能俺孙,傳統(tǒng)布局下,只能在布局時動態(tài)計算文字寬度來進(jìn)行動態(tài)修改掷贾,但通過ConstraintLayout睛榄,則可以非常方便的實現(xiàn)。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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/textView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="32dp"
android:text="TextView"
app:layout_constraintEnd_toStartOf="@+id/textView5"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:singleLine="true"
android:text="TextView11111111111"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@+id/textView6"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/textView4"
app:layout_constraintTop_toTopOf="@+id/textView4" />
<TextView
android:id="@+id/textView6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="32dp"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/textView5"
app:layout_constraintTop_toTopOf="@+id/textView5" />
</androidx.constraintlayout.widget.ConstraintLayout>
場景6:多組件協(xié)同約束
下面這個場景想帅,多個組件的寬度不定场靴,需要取最大寬度的組件在布局中展示,例如下面這個例子港准。
Email和Password兩個TextView的寬度可能因為文字的不一樣而不同旨剥,需要他們整體取最大寬度后,與右邊元素進(jìn)行對齊浅缸,如下所示轨帜。
這時候,就需要使用Barrier衩椒。Barrier 是用多個 View 作為限制源來決定自身位置的一種輔助線.阵谚,Barrier和Group一樣蚕礼,通過constraint_referenced_ids來組合需要作用的組件烟具,代碼如下梢什。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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/email"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:textSize="24sp"
app:layout_constraintBottom_toTopOf="@+id/password"
app:layout_constraintStart_toStartOf="@+id/password"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
android:text="E-mail Address"
tools:text="E-mail Address" />
<EditText
android:id="@+id/emailInput"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ems="10"
android:inputType="textEmailAddress"
android:text="xys@gmail.com"
app:layout_constraintBaseline_toBaselineOf="@+id/email"
app:layout_constraintStart_toEndOf="@+id/barrier" />
<TextView
android:id="@+id/password"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Password"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/email"
tools:layout_editor_absoluteX="11dp"
tools:ignore="MissingConstraints" />
<EditText
android:id="@+id/passwordInput"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ems="10"
android:inputType="textPassword"
android:text="666666"
app:layout_constraintBaseline_toBaselineOf="@+id/password"
app:layout_constraintStart_toEndOf="@+id/barrier" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="right"
app:constraint_referenced_ids="email,password" />
</androidx.constraintlayout.widget.ConstraintLayout>
其中app:barrierDirection
設(shè)置為right,即右側(cè)不超過Barrier朝聋,再讓剩余組件與Barrier進(jìn)行約束即可嗡午。
場景7:容器約束下的邊界約束
面這個場景,中間的TextView被約束在兩邊的組件中冀痕,如下所示荔睹。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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/textView4"
android:layout_width="wrap_content"
android:layout_height="100dp"
android:layout_marginStart="32dp"
android:layout_marginTop="32dp"
android:background="#bebebe"
android:text="TextView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView5"
android:layout_width="wrap_content"
android:layout_height="100dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="32dp"
android:background="#bebebe"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView6"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="TextViewTextViewTextViewTextViewTextViewTextView"
app:layout_constraintBottom_toBottomOf="@+id/textView4"
app:layout_constraintEnd_toStartOf="@+id/textView5"
app:layout_constraintStart_toEndOf="@+id/textView4"
app:layout_constraintTop_toTopOf="@+id/textView4"
tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>
在個例子的重點是將layout_width設(shè)置為0dp,即MATCH_CONSTRAINT言蛇,即可實現(xiàn)這樣的效果僻他。
下面進(jìn)一步思考下這個場景。
當(dāng)TextView文字較少時腊尚,可以發(fā)現(xiàn)其尺寸是默認(rèn)占據(jù)了整個約束空間吨拗,這時候,如果要求TextView只顯示文字大小婿斥,類似設(shè)置wrap_content的效果劝篷,但是在文字長的時候,又必須被邊緣約束民宿,所以又不能設(shè)置wrap_content娇妓,這種場景下,可以通過layout_constraintWidth_default屬性來解決活鹰,它提供了邊緣約束下默認(rèn)的尺寸設(shè)置方式哈恰。
前面說的類似wrap_content的效果,就可以使用wrap來設(shè)置志群。
app:layout_constraintWidth_default="wrap"
當(dāng)然着绷,不設(shè)置這個屬性,將TextView的寬度設(shè)置為wrap_content赖舟,也是可以實現(xiàn)這個效果的蓬戚,這就需要使用到前面講的constrainedWidth屬性了。
layout_constraintWidth_default
的默認(rèn)值為spread宾抓,即占據(jù)邊緣約束下的所有空間子漩。