題外話
關於ConstraintLayout的文章網上一抓一大把,而且ConstraintLayout在16年就已經出來了,但是我一直沒有試著去使用(別問我為什麼不去使用,當然是因為懶啊)。畢竟前面的LinearLayout搭配RelativeLayout用習慣了,但是畢竟能減少佈局的巢狀。還是要抱著多學習的方式去接觸。所以寫下文章作為總結。
前言
大家都知道AS在寫相關佈局的時候,有二種方式:
1. 拖拽方式
就是在這裡進行拖控制元件,各種操作,因為在以前RelativeLayout和LinearLayout的年代,自己拖會自動幫我們新增各種屬性值不說,而且還很不方便,但是對於ConstraintLayout來說新增各種約束在這裡操作反而很方便,而且這裡的功能皮膚也增加了很多新功能,方便了很多。
當然我也不多說,貼上郭霖大神寫得在這裡功能皮膚裡面對ConstraintLayout 各種操作方式: 操作皮膚拖拽方式來使用ConstraintLayout
2.編寫程式碼
這種更為大家使用,而我這裡也更多的是直接寫程式碼的方式。
正文
控制元件如何確定自己的位置
1.直接確定控制元件左上角的座標
在約束佈局中,一個控制元件如何來確定自己的位置呢,有人可能說直接寫死讓它在介面的(XXX,XXX)位置不就好了麼。
比如在拖拽介面,我們把一個TextView拖到了介面中間。
我們發現這個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.告訴控制元件相鄰的二個邊的位置情況
如下圖所示:
我們怎麼來確定它們的位置?比如我們紅色的矩形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
複製程式碼
舉個例子:
比如我們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的left
與id
為buttonA
的控制元件的right
相同位置。所以B的左側就和A的右側貼在了一起。
我們發現上面還有一個layout_constraintBaseline_toBaselineOf
,直接看下圖就可以理解所有相關的屬性:
如果是相對於父佈局,我們也可以不寫入另外一個控制元件的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按鈕中間能空一些距離,如下圖所示:
android:layout_marginStart
android:layout_marginEnd
android:layout_marginLeft
android:layout_marginTop
android:layout_marginRight
android:layout_marginBottom
複製程式碼
這時候就又會有一個問題,如果這時候A的visible
為gone
,這時候B的位置就會自動往左邊了。因為A的所佔的寬度沒有了(但是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/>
複製程式碼
我們讓按鈕的左邊與父佈局的左邊對齊,讓按鈕的右邊與父佈局的右邊對齊。這時候因為不是單純的一邊對齊,而是相同直線上的二個邊都被約束了。所以按鈕無法緊靠著左邊的或者右邊的其中一個邊界,所以這時候,這個按鈕就會居於二個約束邊界的中間位置。如下圖所示:
也許也有人問,我想在這二個約束條件下時候不是處於正中間,而是處於左邊三分之一的位置,這時候你可以使用:layout_constraintHorizontal_bias
layout_constraintVertical_bias
複製程式碼
分別是水平和垂直方向上的所佔比例。
<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>
複製程式碼
圓形佈局
有些需求我們可能需要讓控制元件以某個控制元件為中心,繞著進行佈局,如下圖所示:
ConstarintLayout自帶了這些功能,我們可以使用:layout_constraintCircle : 引用另一個控制元件的id
layout_constraintCircleRadius : 距離另外一個控制元件中心的距離
layout_constraintCircleAngle : 應該在哪個角度(從0到360度)
複製程式碼
例如:
<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_width
和android: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)
鏈在單個軸(水平或垂直)中提供類似組的行為。
- 建立一個鏈: 如果一組小部件通過雙向連線連結在一起,則認為它們是一個鏈,如下圖所示,是一個具有二個控制元件的最小的鏈:
- 鏈頭: 鏈由在鏈的第一個元素(鏈的“頭”)上設定的屬性控制: (頭是水平鏈最左邊的部件,也是垂直鏈最頂端的部件。)
- 鏈樣式: 在鏈的第一個元素上設定屬性
layout_constraintHorizontal_chainStyle
或layout_constraintVertical_chainStyle
時,鏈的行為將根據指定的樣式進行更改(預設為CHAIN_SPREAD)。例如:
<?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>
複製程式碼
效果如下:
屏障 (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的模版:
模版寫好了我們來填充真正的東西。我們把剛才定義的模版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>
複製程式碼
以上就是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結合過渡動畫實現的效果:
而這個Demo也是上面那篇文章作者附上的,Demo地址是PlaceHolder動態替換結語:
還是老話,哪裡不對。可以在留言處寫出來。我會進行更正,哈哈。