Android開發 之 view的幾種佈局方式及實踐
引言
通過前面兩篇:
我們對Android應用程式執行原理及佈局檔案可謂有了比較深刻的認識和理解,並且用“Hello World!”程式來實踐證明了。在繼續深入Android開發之旅之前,有必要解決前兩篇中沒有介紹的遺留問題:View的幾種佈局顯示方法,以後就不會在針對佈局方面做過多的介紹。View的佈局顯示方式有下面幾種:線性佈局(Linear Layout)、相對佈局(Relative Layout)、表格佈局(Table Layout)、網格檢視(Grid View)、標籤佈局(Tab Layout)、列表檢視(List View)、絕對佈局(AbsoluteLayout)。本文雖然是介紹View的佈局方式,但不僅僅是這樣,其中涉及了很多小的知識點,絕對能給你帶來Android大餐!
本文的主要內容就是分別介紹以上檢視的七種佈局顯示方式效果及實現,大綱如下:
- 1、View佈局概述
- 2、線性佈局(Linear Layout)
- 2.1、Tips:android:layout_weight="1"
- 3、相對佈局(Relative Layout)
- 4、表格佈局(Table Layout)
- 5、列表檢視(List View)
- 5.1、一個小的改進
- 5.2、補充說明
- 6、網格檢視(Grid View)
- 7 、絕對佈局()
- 8、標籤佈局(Tab Layout)
1、view的佈局顯示概述
通過前面的學習我們知道:在一個Android應用程式中,使用者介面通過View和ViewGroup物件構建。Android中有很多種View和ViewGroup,他們都繼承自View
View的佈局顯示方式直接影響使用者介面,View的佈局方式是指一組View元素如何佈局,準確的說是一個ViewGroup中包含的一些View怎麼樣佈局。ViewGroup類是佈局(layout)和檢視容器(View
container)的基類,此類也定義了ViewGroup.LayoutParams類,它作為佈局引數的基類,此類告訴父檢視其中的子檢視想如何顯示。例如,XML佈局檔案中名為layout_something的屬性(參加上篇的4.2節)。我們要介紹的View的佈局方式的類,都是直接或間接繼承自ViewGroup類,如下圖所示:
圖1、繼承自ViewGroup的一些佈局類
其實,所有的佈局方式都可以歸類為ViewGroup的5個類別,即ViewGroup的5個直接子類。其它的一些佈局都擴充套件自這5個類。下面分小節分別介紹View的七種佈局顯示方式。
2、線性佈局(Linear Layout)
線性佈局:是一個ViewGroup以線性方向顯示它的子檢視(view)元素,即垂直地或水平地。之前我們的Hello World!程式中view的佈局方式就是線性佈局的,一定不陌生!如下所示res/layour/main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal"><!--
have an eye on ! -->
<Button android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello, I am a Button1"
android:layout_weight="1"
/>
<Button android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello, I am a Button2"
android:layout_weight="1"
/>
<Button android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello, I am a Button3"
android:layout_weight="1"
/>
<Button android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello, I am a Button4"
android:layout_weight="1"
/>
<Button android:id="@+id/button5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello, I am a Button5"
android:layout_weight="1"
/>
</LinearLayout>
從上面可以看出根LinearLayout檢視組(ViewGroup)包含5個Button,它的子元素是以線性方式(horizontal,水平的)佈局,執行效果如下圖所示:
圖2、線性佈局(水平或者說是橫向)
如果你在android:orientation="horizontal"設定為vertical,則是是垂直或者說是縱向的,如下圖所示:
圖3、線性佈局(垂直或者說是縱向)
2.1、Tips:android:layout_weight="1"
這個屬性很關鍵,如果你沒有顯示設定它,它預設為0。把上面佈局檔案(水平顯示的那個)中的這個屬性都去掉,執行會得出如下結果:
圖4、layout_weight屬性
沒有了這個屬性,我們本來定義的5個Button執行後卻只顯示了2個Button,為什麼呢??
"weight"顧名思義是權重的意思,layout_weight 用於給一個線性佈局中的諸多檢視的重要程度賦值。所有的檢視都有一個layout_weight值,預設為零,意思是需要顯示多大的檢視就佔據多大的螢幕空間。這就不難解釋為什麼會造成上面的情況了:Button1~Button5都設定了layout_height和layout_width屬性為wrap_content即包住文字內容,他們都沒有設定layout_weight 屬性,即預設為0.,這樣Button1和Button2根據需要的內容佔據了整個螢幕,別的就顯示不了啦!
若賦一個高於零的值,則將父檢視中的可用空間分割,分割大小具體取決於每一個檢視的layout_weight值以及該值在當前屏幕布局的整體layout_weight值和在其它檢視屏幕布局的layout_weight值中所佔的比率而定。舉個例子:比如說我們在 水平方向上有一個文字標籤和兩個文字編輯元素。該文字標籤並無指定layout_weight值,所以它將佔據需要提供的最少空間。如果兩個文字編輯元素每一個的layout_weight值都設定為1,則兩者平分在父檢視佈局剩餘的寬度(因為我們宣告這兩者的重要度相等)。如果兩個文字編輯元素其中第一個的layout_weight值設定為1,而第二個的設定為2,則剩餘空間的三分之二分給第一個,三分之一分給第二個(數值越小,重要度越高)。
3、相對佈局(Relative Layout)
相對佈局:是一個ViewGroup以相對位置顯示它的子檢視(view)元素,一個檢視可以指定相對於它的兄弟檢視的位置(例如在給定檢視的左邊或者下面)或相對於RelativeLayout的特定區域的位置(例如底部對齊,或中間偏左)。
相對佈局是設計使用者介面的有力工具,因為它消除了巢狀檢視組。如果你發現你使用了多個巢狀的檢視組後,你可以考慮使用一個檢視組了。看下面的res/layour/main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:id="@+id/label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Type here:"/>
<EditText
android:id="@+id/entry"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@android:drawable/editbox_background"
android:layout_below="@id/label"/><!--
have an eye on ! -->
<Button
android:id="@+id/ok"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/entry" <!-- have an eye on ! -->
android:layout_alignParentRight="true" <!-- have an eye on ! -->
android:layout_marginLeft="10dip"
android:text="OK" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/ok" <!-- have an eye on ! -->
android:layout_alignTop="@id/ok" <!-- have an eye on ! -->
android:text="Cancel" />
</RelativeLayout>
從上面的佈局檔案我們知道,RelativeLayout檢視組包含一個TextView、一個EditView、兩個Button,注意標記了<!--
have an eye on ! -->(請注意執行程式碼的時候,請把這些註釋去掉,否則會執行出錯,上面加上是為了更加醒目!)的屬性,在使用相對佈局方式中就是使用這些類似的屬性來定位檢視到你想要的位置,它們的值是你參照的檢視的id。這些屬性的意思很簡單,就是英文單詞的直譯,就不多做介紹了。執行之後,得如下結果:
圖5、相對佈局
4、 表格佈局(Table Layout)
表格佈局:是一個ViewGroup以表格顯示它的子檢視(view)元素,即行和列標識一個檢視的位置。其實Android的表格佈局跟HTML中的表格佈局非常類似, 就像HTML表格的<tr>標記。
用表格佈局需要知道以下幾點:
看下面的res/layour/main.xml:
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:shrinkColumns="0,1,2"><!--
have an eye on ! -->
<TableRow><!-- row1 -->
<Button android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello, I am a Button1"
android:layout_column="0"
/>
<Button android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello, I am a Button2"
android:layout_column="1"
/>
</TableRow>
<TableRow><!-- row2 -->
<Button android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello, I am a Button3"
android:layout_column="1"
/>
<Button android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello, I am a Button4"
android:layout_column="1"
/>
</TableRow>
<TableRow>
<Button android:id="@+id/button5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello, I am a Button5"
android:layout_column="2"
/>
</TableRow>
</TableLayout>
執行之後可以得出下面的結果:
圖6、表格佈局
5、列表檢視(List View)
列表佈局:是一個ViewGroup以列表顯示它的子檢視(view)元素,列表是可滾動的列表。列表元素通過
:擴充套件自,它是和資料列表之間的橋樑。ListView可以顯示任何包裝在ListAdapter中的資料。該類提供兩個公有型別的抽象方法:
- public abstract boolean areAllItemsEnabled () :表示ListAdapter中的所有元素是否可啟用的?如果返回真,即所有的元素是可選擇的即可點選的。
- public abstract boolean isEnabled (int position) :判斷指定位置的元素是否可啟用的?
下面通過一個例子來,建立一個可滾動的列表,並從一個字串陣列讀取列表元素。當一個元素被選擇時,顯示該元素在列表中的位置的訊息。
1)、首先,將res/layour/main.xml的內容置為如下:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dp"
android:textSize="16sp" >
</TextView>
這樣就定義了元素在列表中的佈局。
2)、src/skynet.com.cnblogs.www/HelloWorld.java檔案的程式碼如下:
package skynet.com.cnblogs.www; import android.app.ListActivity; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import android.widget.AdapterView.OnItemClickListener; public class HelloWorld extends ListActivity {
//注意這裡Helloworld類不是擴充套件自Acitvity,而是擴充套件自ListAcitivty /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setListAdapter(new ArrayAdapter<String>(this, R.layout.main, COUNTRIES)); ListView lv = getListView(); lv.setTextFilterEnabled(true); lv.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // When clicked, show a toast with the TextView text Toast.makeText(getApplicationContext(), ((TextView) view).getText(), Toast.LENGTH_SHORT).show(); } }); } static final String[] COUNTRIES = new String[] { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24" }; }
Note:onCreate()函式中並不像往常一樣通過setContentView()為活動(Activity)載入佈局檔案,替代的是通過setListAdapter(ListAdapter)自動新增一個ListView填充整個螢幕的ListActivity。在此檔案中這個方法以一個ArrayAdapter為引數:setListAdapter(new ArrayAdapter<String>(this,
R.layout.main, COUNTRIES)),這個ArrayAdapter管理填入ListView中的列表元素。ArrayAdapter的建構函式的引數為:this(表示應用程式的上下文context)、表示ListViewde佈局檔案(這裡是R.layout.main)、插入ListView的List物件對陣列(這裡是COUNTRES)。
3)、執行應用程式得如下結果(點選1之後,在下面顯示了1):
圖7、列表佈局
NOTE:如果你改了HelloWorld extends ListActivity 而不是Activity之後,執行程式是提示:“Conversion to Dalvik format failed with error 1”。可以這麼解決:解決辦法是 Project > Clean... > Clean project selected below > Ok
5.1、一個小的改進
上面我們是把要填充到ListView中的元素硬編碼到HelloWorld.java檔案中,這樣就缺乏靈活性!也不符合推薦的應用程式的介面與控制它行為的程式碼更好地分離的準則!
其實我們可以把要填充到ListView的元素寫到res/values/strings.xml
檔案中的<string-array>
元素中,然後再原始碼中動態地讀取。這樣strings.xml的內容類似下面:
<?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="countries_array"> <item>1</item> <item>2</item> <item>3</item> <item>4</item> <item>5</item> <item>6</item> <item>7</item> </string-array> </resources>
然而HelloWorld.java檔案中的onCreate()函式,則這樣動態訪問這個陣列及填充到ListVies:
String[] countries = getResources().getStringArray(R.array.countries_array);
setListAdapter(new ArrayAdapter<String>(this, R.layout.list_item, countries));
5.2、補充說明
首先總結一下列表佈局的關鍵部分:
- 佈局檔案中定義ListView
- Adapter用來將資料填充到ListView
- 要填充到ListView的資料,這些資料可以字串、圖片、控制元件等等
其中Adapter是ListView和資料來源之間的橋樑,根據資料來源的不同Adapter可以分為三類:
- String[]: ArrayAdapter
- List<Map<String,?>>: SimpleAdapter
- 資料庫Cursor: SimpleCursorAdapter
使用ArrayAdapter(陣列介面卡)顧名思義,需要把資料放入一個數組以便顯示,上面的例子就是這樣的;SimpleAdapter能定義各種各樣的佈局出來,可以放上ImageView(圖片),還可以放上Button(按鈕),CheckBox(複選框)等等;SimpleCursorAdapter是和資料庫有關的東西。篇幅有限後面兩種就不舉例實踐了。你可以參考android ListView詳解orArrayAdapter ,SimpleAdapter ,SimpleCursorAdapter 區別。
6、網格檢視(Grid View)
網格佈局:是一個ViewGroup以網格顯示它的子檢視(view)元素,即二維的、滾動的網格。網格元素通過ListAdapter跟上面的列表佈局是一樣的,這裡就不重複累述了。
下面也通過一個例子來,建立一個顯示圖片縮圖的網格。當一個元素被選擇時,顯示該元素在列表中的位置的訊息。
1)、首先,將上面實踐擷取的圖片放入res/drawable/
2)、res/layour/main.xml的內容置為如下:這個GridView填滿整個螢幕,而且它的屬性都很好理解,按英文單詞的意思就對了。
<?xml version="1.0" encoding="utf-8"?> <GridView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/gridview" android:layout_width="fill_parent" android:layout_height="fill_parent" android:columnWidth="90dp" android:numColumns="auto_fit" android:verticalSpacing="10dp" android:horizontalSpacing="10dp" android:stretchMode="columnWidth" android:gravity="center" />
3)、然後,HelloWorld.java檔案中onCreate()函式如下:
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); GridView gridview = (GridView) findViewById(R.id.gridview); gridview.setAdapter(new ImageAdapter(this)); gridview.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View v, int position, long id) { Toast.makeText(HelloWorld.this, "" + position, Toast.LENGTH_SHORT).show(); } }); }
onCreate()函式跟通常一樣,首先呼叫超類的onCreate()函式函式,然後通過setContentView()為活動(Activity)載入佈局檔案。緊接著是,通過GridView的id獲取佈局檔案中的gridview,然後呼叫它的函式填充它,它的引數是一個我們自定義的ImageAdapter。後面的工作跟列表佈局中一樣,為監聽網格中的元素被點選的事件而做的工作。
4)、實現我們自定義ImageAdapter,新新增一個類檔案,它的程式碼如下:
package skynet.com.cnblogs.www; import android.content.Context; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.GridView; import android.widget.ImageView; public class ImageAdapter extends BaseAdapter { private Context mContext; public ImageAdapter(Context c) { mContext = c; } public int getCount() { return mThumbIds.length; } public Object getItem(int position) { return null; } public long getItemId(int position) { return 0; } // create a new ImageView for each item referenced by the Adapter public View getView(int position, View convertView, ViewGroup parent) { ImageView imageView; if (convertView == null) { // if it's not recycled, initialize some attributes imageView = new ImageView(mContext); imageView.setLayoutParams(new GridView.LayoutParams(85, 85)); imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); imageView.setPadding(8, 8, 8, 8); } else { imageView = (ImageView) convertView; } imageView.setImageResource(mThumbIds[position]); return imageView; } // references to our images private Integer[] mThumbIds = { R.drawable.linearlayout1, R.drawable.linearlayout2, R.drawable.linearlayout3, R.drawable.listview, R.drawable.relativelayout, R.drawable.tablelayout }; }
ImageAdapter類擴充套件自BaseAdapter,所以首先得實現它所要求必須實現的方法。建構函式和getcount()函式很好理解,而getItem(int)應該返回實際物件在介面卡中的特定位置,但是這裡我們不需要。類似地,getItemId(int)應該返回元素的行號,但是這裡也不需要。
這裡重點要介紹的是getView()方法,它為每個要新增到ImageAdapter的圖片都建立了一個新的View。當呼叫這個方法時,一個View是迴圈再用的,因此要確認物件是否為空。如果是空的話,一個就被例項化且配置想要的顯示屬性:
如果View傳到getView()不是空的,則本地的ImageView初始化時將迴圈再用View物件。在getView()方法末尾,position整數傳入setImageResource()方法以從mThumbIds陣列中選擇圖片。
執行程式會得到如下結果(點選第一張圖片之後):
圖8、網格佈局
7、絕對佈局(AbsoluteLayout)
絕對佈局:是一個ViewGroup以絕對方式顯示它的子檢視(view)元素,即以座標的方式來定位在螢幕上位置。
這種佈局方式很好理解,在佈局檔案或程式設計地設定View的座標,從而絕對地定位。如下所示佈局檔案:
<AbsoluteLayout xmlns:andr