Android開發之基本控制元件和詳解四種佈局方式
Android中的控制元件的使用方式和iOS中控制元件的使用方式基本相同,都是事件驅動。給控制元件新增事件也有介面回撥和委託代理的方式。今天這篇部落格就總結一下Android中常用的基本控制元件以及佈局方式。說到佈局方式Android和iOS還是區別挺大的,在iOS中有Frame絕對佈局和AutoLayout相對佈局。而在Android中的佈局方式就比較豐富了,今天部落格中會介紹四種常用的佈局方式。先總結一下控制元件,然後再搞一搞基本方式,開發環境還是用的Mac下的Android Studio。開始今天的正題, 雖然Android的控制元件和佈局方式都可以拖拽實現,今天為了更詳細的瞭解控制元件和佈局,我們就用純程式碼的形式來進行實現和介紹。
一、常用基本控制元件
1.TextView
看到Android中的TextView, 我不禁的想到了iOS開發中的UILabel。從字面意思上看,TextView就是文字檢視,只是用來顯示文字的。在iOS中就叫做標籤,即為UILabel。要想在Activity中顯示TextView, 我們需要在相應的佈局檔案,也就是Activity對應的layout.xml檔案去新增相應的控制元件標籤。這些xml標籤可以確定控制元件的位置,大小,顏色等屬性。下方是在Activity中顯示一個TextView。佈局程式碼如下:
1 <TextView 2 android:id="@+id/name_text_view" 3 android:layout_width="match_parent" 4 android:layout_height="wrap_content" 5 android:gravity="center" 6 android:textSize="30sp" 7 android:textColor="#be0e0a" 8 android:text="My name is ZeluLi"/>
標籤<TextView/>代表著我們要在Activity中新增一個個TextView, 標籤中可以設定一些屬性。
(1).android:id屬性代表著TextView的Id,也就是TextView的唯一標示,在java程式碼中我們可以通過findViewById()方法來通過Id獲取控制元件。上述控制元件的唯一id為name_text_view。
(2).android:layout_width屬性代表著控制元件的寬度,該屬性的值是match_parent, 表示該控制元件的寬度與父檢視的寬度相同。
(3).android:layout_height屬性代表著控制元件的高度,該屬性的值是wrap_content,表示控制元件的高度根據內容的高度進行改變。
(4).android:gravity屬性代表著TextView中文字對齊方式,有多種方式,我們在此選的是center,居中顯示。
(5).android:textSize屬性代表著TextView中文字的型號,也就是文字的大小。
(6).android:textColor屬性設定的是TextView中文字的顏色,屬性值是16進位制的色值。
(7).android:text屬性就是用來設定TextView顯示的值的。
我們如何在Java類,也就是Activity中獲取上述控制元件呢,下方的程式碼就是使用findViewById()方法通過id獲取上述控制元件,並獲取TextView中的值以及設定TextView中的值。具體程式碼如下。
TextView myTextView = (TextView) findViewById(R.id.name_text_view); String myText = myTextView.getText().toString(); myTextView.setText(myText+" Add");
經過上面的屬性的設定,執行工程,你會在Activity中看到如下效果:
2.Button
在Android中的按鈕就叫Button, 而在iOS中則叫UIButton。其兩者的用法極為相似。還是和上面類似,我們需要在Activity對應的佈局檔案layout.xml中新增一個Button, 具體的xml程式碼如下所示。<Button/>標籤就是代表著Button, 其中的屬性和屬性值就不做過多的贅述了,上面已經提到了。
<Button android:id="@+id/click_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="點我"/>
在Activity的類中也是使用findViewById來通過Id獲取該按鈕,獲取按鈕後我們需要給按鈕繫結點選事件。也就是點選按鈕要做的事情,下方給出了兩中方式,一種是塊的形式,一種是委託代理的形式。
(1).介面回撥的形式繫結點選事件
1 Button button = (Button) findViewById(R.id.click_button); 2 button.setOnClickListener(new View.OnClickListener() { 3 @Override 4 public void onClick(View v) { 5 //點選按鈕要做的事情 6 } 7 });
(2)委託代理
1 button.setOnClickListener(this); 2 3 //重寫委託回撥的方法 4 /** 5 * Called when a view has been clicked. 6 * 7 * @param v The view that was clicked. 8 */ 9 @Override 10 public void onClick(View v) { 11 switch (v.getId()){ 12 case R.id.click_button: 13 //點選按鈕後要做的事情 14 break; 15 default: 16 break; 17 } 18 }
經過上面的步驟就會在TextView下面添加了一個按鈕,執行效果如下所示
3.EditText
接下來要為Activity新增一個輸入框,在Android中輸入框的型別和標籤都是EditText。iOS中的輸入框就是UITextField了,其實兩者用法類似,其功能都是接收使用者輸入的資料的。下方是其xml佈局方式.
1 <EditText 2 android:id="@+id/edit_text" 3 android:layout_width="match_parent" 4 android:layout_height="wrap_content" 5 android:hint="placeHoder: type something here" 6 android:maxLines="3"/>
上方EditText標籤中比之前多了兩個屬性:
(1).android:hint屬性後邊是一個字串,其實就是用來佔位用的字串,功能是提示使用者該輸入框是幹嘛的,在iOS開發中叫做Placeholder。
(2).android:macLines 用來設定輸入框的最大行數。
在Activity中獲取EditText物件,也是通過Id方式,下方程式碼是獲取通過id例項化EditText物件,並獲取其中的文字在Toast上顯示。
EditText myEditText = (EditText) findViewById(R.id.edit_text); String inputText = myEditText.getText().toString(); Toast.makeText(MainActivity.this, inputText, Toast.LENGTH_SHORT).show();
輸入框如下所示:
4.AlterDialog(警告框)
Toast用來顯示提示內容,而AlterDialog是警告框,上面可以有一些控制元件,比如按鈕等。AlterDialog其實就是iOS中的AlterView(在iOS8後有增加了UIAlterController)。下面的程式碼是初始化AlterDialog並且進行顯示的程式碼,下方的程式碼是放在點選按鈕所觸發的方法當中。
(1)AlterDialog通過AlterDialog的Builder進行建立,在建立的時候會指定該AlterDialog在那個Activity上進行顯示。
(2)通過setTitle方法給AlterDialog設定標題,通過setMessage給AlterDialog設定內容。
(3)setCancelable()方法,我們在這兒設定的時false,表示彈出的AlterDialog在使用者點選返回鍵是不消失,該值預設是true。
(4)setPositiveButton()方法是設定點選“確定”按鈕時的事件, setNegativeButton是設定點選“取消”按鈕的事件。通過Toast來展示事件的點選。
1 AlertDialog.Builder alterDialog = new AlertDialog.Builder(MainActivity.this); 2 alterDialog.setTitle("提示框"); 3 alterDialog.setMessage("提示內容"); 4 alterDialog.setCancelable(false); 5 alterDialog.setPositiveButton("好的", new DialogInterface.OnClickListener() { 6 @Override 7 public void onClick(DialogInterface dialog, int which) { 8 Toast.makeText(MainActivity.this, "好的", Toast.LENGTH_SHORT).show(); 9 } 10 }); 11 alterDialog.setNegativeButton("取消", new DialogInterface.OnClickListener() { 12 @Override 13 public void onClick(DialogInterface dialog, int which) { 14 Toast.makeText(MainActivity.this, "取消", Toast.LENGTH_SHORT).show(); 15 } 16 }); 17 alterDialog.show();
下面就是上面AlterDialog顯示後的效果。
5.ProgressBar(進度條)
進度條,就是平時下載東西常見到表示下載進度的控制元件。ProgressBar和iOS中的UIProgressView類似,用法也是非常類似的。首先需要在Activity對應的Xml檔案中對ProgressBar進行佈局和樣式的設定。下方是ProgressBar的佈局和樣式。
1 <ProgressBar 2 android:id="@+id/my_progress_bar" 3 android:layout_width="match_parent" 4 android:layout_height="wrap_content" 5 style="?android:attr/progressBarStyleHorizontal" 6 android:max="200"/>
我們可以通過android:max來設定ProgressBar的進度最大值,而style可以給ProgressBar設定不同的樣式。ProgressBar有多種樣式,可以根據不同的場景來選擇不同的樣式,下方是可選樣式。
在xml中配置好ProgressBar之後就可以在程式碼中通過ID獲取,對ProgressBar進行一系列的操作了。下方的程式碼也是放在按鈕的點選事件中,每點選一次進度條的進度就增加10,直到增到最大值時ProgressBar就會變成不可見。變為不可見後,接著就會把進度設定成0。
1 ProgressBar myProgressBar = (ProgressBar) findViewById(R.id.my_progress_bar); 2 myProgressBar.setProgress(myProgressBar.getProgress()+10); 3 4 if (myProgressBar.getProgress() == myProgressBar.getMax()) { 5 myProgressBar.setVisibility(View.GONE); 6 myProgressBar.setProgress(0); 7 } else { 8 myProgressBar.setVisibility(View.VISIBLE); 9 }
6.ProgressDialog(進度提示框)
ProgressDialog說白了就是在AlterDialog上新增Progress, ProgressDialog不需要在xml中進行配置,直接在程式碼中進行生成即可。下方是在按鈕點選的委託代理方法中新增的ProgressDialog,點選按鈕時就顯示ProgressDialog。
1 /** 2 * Called when a view has been clicked. 3 * 4 * @param v The view that was clicked. 5 */ 6 @Override 7 public void onClick(View v) { 8 switch (v.getId()){ 9 case R.id.click_button: 10 11 ProgressDialog myProgressDialog = new ProgressDialog(MainActivity.this); 12 myProgressDialog.setTitle("ProgressDialog"); 13 myProgressDialog.setMessage("Loading……"); 14 myProgressDialog.setCancelable(true); 15 myProgressDialog.show(); 16 17 break; 18 default: 19 break; 20 } 21 }
執行效果如下:
二、四大布局方式
有的地方介紹的是五大布局,因為還有一種是絕對佈局(AbsoluteLayout)就是通過座標和寬高來控制控制元件的位置,此佈局方式在Android開發中已經被棄用了,所以不再今天的討論範圍之內。今天要介紹的佈局方式有線性佈局(LinearLayout)、相對佈局(RelativeLayout)、幀佈局(FrameLayout)、表格佈局(TableLayout)。前兩者是常用的,所以今天就著重的討論一下LinearLayout。
說到Android中的佈局方式我想對比一下iOS開發中的佈局方式。可以說iOS佈局中基本的有兩種方式,一個是絕對佈局,另一種就是相對佈局。絕對佈局就是通過Frame(x, y, width, height), 也就是給控制元件設定座標原點以及寬高來確定控制元件的位置和大小。因為這種佈局方式一旦設定Frame後,控制元件的位置和大小就固定了,所以被成為絕對佈局。
另一種iOS中的佈局方式就是相對佈局了,在iOS開發中可以使用Autolayout + SizeClass來確定控制元件的位置和大小。我們可以給控制元件新增不同的約束(寬,高,上下左右邊距,上下左右居中,垂直水平居中)等方式來控制控制元件的大小和位置。這種方式在螢幕適配時更為靈活,在iOS開發中也常常被使用到。關於響度佈局iOS開發中你可以通過VFL(Visual format language)給控制元件新增約束,你也可以通過Storyboard以視覺化的方式來進行約束的新增。
iOS的佈局方式就先聊到這兒,接下來回到安卓的佈局方式當中。在Android開發的幾種佈局方式當中,你不許指定控制元件的座標點,也就是說你不許指定控制元件的位置,因為特定的佈局方式有其特定計算控制元件座標點的方法。但是在不同的佈局方式中你需要為控制元件指定寬高。接下來具體的介紹一下Android開發中的佈局方式。
1. LinearLayout (線性佈局)
說到LinearLayout, 我想說一下流式佈局。其實LinearLayout就是流式佈局,流式佈局有個特點,就是下一個控制元件的座標原點由上一個控制元件來決定,你可以沿水平方向或者垂直方向上來排列你的控制元件。 如果你的控制元件是垂直排列的,那麼你可以給控制元件指定水平的居中方式(這一點可能說起來抽象,下方會通過例項來進行介紹)。接下來將通過一系列的例項來介紹一下LinearLayout。
(1) 下方有張效果圖,我們想實現下方佈局方式,如果使用LinearLayout來實現該如何去做呢。
(2) 首先我們先分析佈局方式,把每個塊進行拆分,分析,然後通過LinearLayout進行組合在一塊即可。我們對上述佈局方式進行拆分,並且對使用的LinearLayout進行命名,並且指定子檢視的佈局方式(V-垂直,H-水平),具體的請看下圖。最下方我們使用了一個水平佈局的LinearLayout1, 在LinearLayout01上又有兩個高度等於父檢視高度的LinearLayout11和LinearLayout12,兩者子控制元件的佈局方式都設定為垂直排列。在LinearLayout12中又有兩個子線性佈局LinearLayout121和LinearLayout122, 這兩個子佈局沿垂直方向排列於父佈局之上,並且寬度與父佈局相等。
(3) 上方說了這麼多了,那麼接下來看一下上面佈局的具體實現方式吧,其佈局層次結構圖如下所示
具體實現xml如下,在實現中你可以通過android:orientation屬性來設定是水平(horizontal)線性排列還是垂直(vertical)線性排列。關於pt等這種單位,下篇部落格會給大家詳細的介紹一下。
1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" 3 android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" 4 android:paddingRight="@dimen/activity_horizontal_margin" 5 android:paddingTop="@dimen/activity_vertical_margin" 6 android:paddingBottom="@dimen/activity_vertical_margin" 7 tools:context="com.example.lizelu.userinterfacedemo.Main2Activity"> 8 <LinearLayout 9 android:layout_width="match_parent" 10 android:layout_height="match_parent"> 11 12 <!--垂直線性佈局方式--> 13 <LinearLayout 14 android:layout_width="60pt" 15 android:layout_height="match_parent" 16 android:background="#ff0000" 17 android:orientation="vertical"> 18 </LinearLayout> 19 20 <LinearLayout 21 android:layout_width="match_parent" 22 android:layout_height="match_parent" 23 android:orientation="vertical"> 24 25 <LinearLayout 26 android:layout_width="match_parent" 27 android:layout_height="50pt" 28 android:background="#0000ff" 29 android:orientation="horizontal"> 30 </LinearLayout> 31 32 <LinearLayout 33 android:layout_width="match_parent" 34 android:layout_height="match_parent" 35 android:background="#00ff00" 36 android:orientation="horizontal"> 37 </LinearLayout> 38 </LinearLayout> 39 </LinearLayout> 40 </RelativeLayout>
(4) 垂直佈局控制元件的對齊方式(Left, Center, Right)。垂直佈局的控制元件,我們可以對其指定水平方向的對對齊方式。為了說明這個問題我還是想畫個圖來解釋一下這個看似簡單的問題。我們可以通過控制元件的android:layout_gravity屬性來指定對其方式。在垂直佈局中,垂直方向的對齊方式(top, center, bottom)是不起作用的,因為垂直方向的位置已經有垂直線性佈局所決定了,所以layout_gravity就不起作用了。
原理就先到這兒,接下來就是實現了,我們將在LinearLayout11佈局中新增下方的子控制元件。每個子控制元件都指定了水平的對齊方式,具體程式碼如下所示:
1 <Button 2 android:layout_width="wrap_content" 3 android:layout_height="wrap_content" 4 android:layout_gravity="left" 5 android:text="aa"/> 6 <Button 7 android:layout_width="wrap_content" 8 android:layout_height="wrap_content" 9 android:layout_gravity="center" 10 android:text="bb"/> 11 <Button 12 android:layout_width="wrap_content" 13 android:layout_height="wrap_content" 14 android:text="cc" 15 android:layout_gravity="right"/>
新增程式碼後執行效果如下:
(5) 水平佈局控制元件的對齊方式(Top, Center, Bottom)。如果控制元件是以水平的方式進行排列的,那麼我們就可以對其指定垂直方向的對齊方式,即Top, Center和Bottom。也是通過android:layout_gravity屬性來指定的。為了說明一下原理呢,我還是想用一張圖來表達一下:
原理看完了,接下來按照上面的套路,我們以上面的佈局和對齊方式,在LinearLayout121上新增三個上述佈局的Button. 具體程式碼如下所示:
1 <Button 2 android:layout_width="wrap_content" 3 android:layout_height="wrap_content" 4 android:layout_gravity="top" 5 android:text="aa"/> 6 <Button 7 android:layout_width="wrap_content" 8 android:layout_height="wrap_content" 9 android:text="bb" 10 android:layout_gravity="center" /> 11 <Button 12 android:layout_width="wrap_content" 13 android:layout_height="wrap_content" 14 android:layout_gravity="bottom" 15 android:text="cc"/>
接下來就該運行了,下方是執行出來的結果:
(6)線上性佈局中有一個不得不提的屬性就是android:layout_weight, 該屬性允許你以比例的形式來指定控制元件的大小。接下來我們要做的就是在LinearLayout122中新增三個水平方向上等分的按鈕。使用android:layout_weight屬性,很容易就可以實現,因為原理比較簡單,就不畫原理圖了,下方是具體的xml實現:
1 <Button 2 android:layout_width="0pt" 3 android:layout_height="wrap_content" 4 android:layout_weight="1" 5 android:text="你好"/> 6 7 <Button 8 android:layout_width="0pt" 9 android:layout_height="wrap_content" 10 android:layout_weight="1" 11 android:text="Android"/> 12 13 <Button 14 android:layout_width="0pt" 15 android:layout_height="wrap_content" 16 android:layout_weight="1" 17 android:text="iOS"/>
具體執行效果如下所示:
線性佈局就先到這兒,因為線性佈局方式在Android開發中經常使用到,所以介紹的會多一些。線性佈局還有好多其他的用法,等後邊部落格中用到的時候會詳細的介紹。
2.RelativeLayout (相對佈局)
上面也說了一下相對佈局, 相對佈局的本質就是以不變應萬變。也就是說相對佈局可以根據已經固定的控制元件來確定其他新加控制元件的位置。相對佈局用的還是蠻多的,接下來我們將通過一個例項來介紹一下RelativeLayout。
首先我們先來看一下我們要實現的效果,實現思路是我們先根據父檢視的中心位置來確定center_button的位置,然後再由Center和Parent的位置來確定出其他按鈕的位置,這就是相對佈局。
在相對佈局中,你可以設定的屬性如下所示,還是蠻多的。在本篇部落格中就不做一一介紹了,其用法都差不多。如下圖所示:
實現上述效果的xml程式碼如下所示,相對佈局使用起來和理解起來還是比較簡單的。
1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 tools:context="com.example.lizelu.userinterfacedemo.Main3Activity"> 6 7 <Button 8 android:id="@+id/button_center" 9 android:layout_width="wrap_content" 10 android:layout_height="wrap_content" 11 android:layout_centerInParent="true" 12 android:text="center"/> 13 14 <Button 15 android:id="@+id/button_above" 16 android:layout_width="wrap_content" 17 android:layout_height="wrap_content" 18 android:layout_above="@+id/button_center" 19 android:layout_centerInParent="true" 20 android:text="above"/> 21 22 <Button 23 android:id="@+id/button_below" 24 android:layout_width="wrap_content" 25 android:layout_height="wrap_content" 26 android:layout_below="@+id/button_center" 27 android:layout_centerInParent="true" 28 android:text="below"/> 29 30 <Button 31 android:id="@+id/button_left" 32 android:layout_width="wrap_content" 33 android:layout_height="wrap_content" 34 android:layout_toLeftOf="@+id/button_center" 35 android:layout_centerVertical="true" 36 android:text="left"/> 37 38 <Button 39 android:id="@+id/button_right" 40 android:layout_width="wrap_content" 41 android:layout_height="wrap_content" 42 android:layout_toRightOf="@+id/button_center" 43 android:layout_centerVertical="true" 44 android:text="right"/> 45 46 </RelativeLayout>
3.幀佈局 (FrameLayout)
說到幀佈局, 就比較簡單了,而且比較好理解,並且幀佈局的用處不是很多,但他的存在還是有他的必要性的。FrameLayout中的Frame和iOS中的Frame不是一個概念,在iOS中的Frame你可以指定任意的座標,而這個座標點時相對於父檢視的。FrameLayout中的Frame的座標原點是螢幕的左上角,位置固定,你只需為控制元件指定大小即可。接下來將通過一個例項來搞一下這個FrameLayout。
下面是使用FrameLayout做的一個效果,可以看出每塊區域中除了大小顏色不一樣外,他們的座標點都是左上角的位置。這也是FrameLayout的特點,下面是執行效果截圖:
實現上方佈局的xml如下:
1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" 3 android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" 4 android:paddingRight="@dimen/activity_horizontal_margin" 5 android:paddingTop="@dimen/activity_vertical_margin" 6 android:paddingBottom="@dimen/activity_vertical_margin" 7 tools:context="com.example.lizelu.userinterfacedemo.Main4Activity"> 8 9 <FrameLayout 10 android:layout_width="120pt" 11 android:layout_height="120pt" 12 android:background="#00ff00"> 13 <FrameLayout 14 android:layout_width="100pt" 15 android:layout_height="100pt" 16 android:background="#00f0f0"> 17 </FrameLayout> 18 19 <FrameLayout 20 android:layout_width="80pt" 21 android:layout_height="80pt" 22 android:background="#0000ff"> 23 </FrameLayout> 24 25 <FrameLayout 26 android:layout_width="60pt" 27 android:layout_height="60pt" 28 android:background="#00ffff"> 29 </FrameLayout> 30 31 <FrameLayout 32 android:layout_width="40pt" 33 android:layout_height="40pt" 34 android:background="#000000"> 35 </FrameLayout> 36 37 </FrameLayout> 38 39 </RelativeLayout>
4、表格佈局(TableLayout)
如果你接觸過Web前端的東西的話,雖然常用的時div + css , 但是Web前端也是有表格佈局的。在安卓開發中的表格佈局和Web前端中的表格佈局的概念類似,也就是通過畫表表格的方式來實現佈局。 在表格佈局中,整個頁面就相當於一張大的表格,控制元件就放在每個Cell中。接下來我們就使用表格佈局來畫一個表格,感受一下表格佈局。接下來我們將會使用表格佈局來實現一個比較經典的“登入”頁面,下方是簡單畫的要實現效果圖:
由上圖我們容易看出,上面就是一個表格結構。Table中有3行兩列,登入按鈕佔了一個整行,其餘控制元件都佔了一列。上面的佈局還是蠻簡單的,說白了,再複雜的佈局也是從簡單做起的。下方是實現上面佈局的XML程式碼。
1 <TableLayout 2 android:layout_width="match_parent" 3 android:layout_height="match_parent" 4 android:stretchColumns="1"> 5 <TableRow> 6 <TextView 7 android:layout_width="wrap_content" 8 android:layout_height="wrap_content" 9 android:text="使用者名稱:"/> 10 <EditText 11 android:layout_width="match_parent" 12 android:layout_height="wrap_content" 13 android:hint="請輸入使用者名稱"/> 14 </TableRow> 15 16 <TableRow> 17 <TextView 18 android:layout_width="wrap_content" 19 android:layout_height="wrap_content" 20 android:text="密 碼:"/> 21 <EditText 22 android:layout_width="match_parent" 23 android:layout_height="wrap_content" 24 android:hint="請輸入密碼" 25 android:inputType="textPassword"/> 26 </TableRow> 27 28 <TableRow> 29 <Button 30 android:layout_height="wrap_content" 31 android:layout_width="wrap_content" 32 android:text="登入" 33 android:layout_span="2"/> 34 </TableRow> 35 36 </TableLayout>
其中android:stretchColumns="1"屬性,表示讓第一列(列數從零開始算起)拉伸,以達到視訊螢幕的目的。所以你看到的輸入框是充滿後邊整個螢幕的。登入按鈕中這個屬性android:layout_span="2" ,表明登入按鈕跨兩列。上述佈局xml執行後的效果如下:
到此4種佈局方式已介紹完畢,其實再複雜的佈局也是從簡單的開始。複雜的佈局頁面有可能上述四種佈局方式都會用到。由簡單到複雜這需要一個過程的,基礎的會了之後,接下來就是如何去運用基礎來構造更為複雜的佈局方式。