Android用户界面布局(layouts)
备注:view理解为视图
一个布局定义了用户界面的可视结构,比如activity的UI或是APP widget的UI,我们可以用下面两种方式来声明布局:
(1) 在XML文件中声明UI元素,Android提供一种直观的XML词汇(vocabulary,应该是指属性表)来对应View类及其子类,比如那些用于部件(widget)和布局的词汇。
(2) 在运行时实例化(初始化)布局元素,我们的应用能够以编程的方式创建View和ViewGroup对象(和操纵它们的属性)。
Android应用框架(framework)使我们能够很灵活使用这两种方式来声明和管理我们应用的UI,比如,我们可在XML中声明我们应用默认的布局,包括将在它们和它们属性中显示的屏幕元素。然后我们可以在应用中增加代码来修改屏幕对象的状态,包括那些在XML和运行时声明的。
在XML中声明UI的好处在于使我们可以更好地把控制行为的代码和应用程序的外观分开。我们UI描述在应用代码之外,这意味着我们能够在不修改代码和重新编译的情况下修改UI。比如,我们能够为不同屏幕方向(横向或是纵向)、不同的屏幕大小和多国语言创建XML布局。另外,在XML中声明布局使得我们的UI更直观,所以更容易调试问题。就这点而言,本文的重点在告诉我们如何在XML中声明布局,如果我们对在运行时初始化View对象,可参考ViewGroup和View类参考文献。
总的来说,定义UI元素的XML词汇紧密遵循类和方法的结构与命名,UI元素名对应类型,而属性名对应方法名。确切地说,由于它们之间的一致性,我们可以从类方法名猜出XML属性,或者可以通过xml元素猜出类方法。然而,请注意并不是所有的词汇都是相同的,比如,EditText元素有一个text属性对应的是
EditText.setText()这个方法。
1. 写XML(Write the XML)
使用Android的XML词汇,采用一系列嵌套的元素,我们能够快速设计出UI布局和它们包含的屏幕元素,我们也可以采用相同的方式在HTML中创建网页网站(web pages)。
每个布局文件必须明确的包含一个根元素,该元素必须是一个View或是ViewGroup对象。一旦我们定义了根元素后,就可以增加额外的布局对象或是widget作为子元素来逐步构建了我们布局的视图层级。比如,下面是使用一个垂直LinearLayout来包含一个TextView和一个button的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="vertical">
<TextView android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello, I am a TextView"/>
<Button android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello, I am a Button"/>
</LinearLayout>
我们在XML中声明了布局后,保存为xml扩展名的文件,次文件保存在我们Android工程的res/layout/文件夹下,这样它将正确编译。
2. 加载XML资源(Load the XML Resource)
在编译我们APP时,每个布局文件被编译成一个View资源,我们就能在代码中加载这些布局资源,这在Activity.onCreate()回调函数中实现。调用setContentView()函数,并将布局资源的引用R.layout.layout_file_name传递给它。比如,如果我们的XML布局保存在main_layout.xml文件中,我们可以像下面这样在activity中加载资源:
publicvoid onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
}
当activity被启动的时候,Android framework调用我们这个activity的onCreate回调函数。
3. 属性(Attributes)
每个View和ViewGroup对象支持它们各自的一系列XML属性,一些属性针对于具体的View对象(比如,TextView支持textSize属性),这些属性也被哪些扩展自这些类的View对象继承(也就是View和ViewGroup的属性,对继承了它们的子类也有效)。有些属性对于所有的View对象时公共的,因为它们是继承之根View类(如id属性)。另外,一些参数被作为“布局参数(layout parameters)”,它们描述View对象的某一布局方向(certain layout orientations),布局参数由对象的父对象ViewGroup定义。
4. 唯一标识符(ID)
任何View对象都有一个与它关联的整数ID,这可以在View树中唯一地标识每个View对象。当APP编译时,这个ID被引用作为一个整数,但ID通常被分配在布局XML文件中作为一个字符串(string),放在id属性中。这是所有View对象公共的XML属性(被View类定义)和我们将经常使用它,一个ID的语法,在XML标记如下:
android:id="@+id/my_button"
以@符号开始的字符串告诉XML解析器应解析和展开ID字符串的剩余部分并识别它作为一个ID资源。+符号表示这是一个新资源名称,必须被创建和增加到我们的资源中(在R.java文件中)。Android framework还提供其他的ID资源,当引用一个Android资源ID,我们不需要这个+符号,当时必须增加android包命名空间(package namespace),像下面这样:
android:id="@android:id/empty"
在适当的地方使用android包命名空间,我们现在引用的ID是来之android.R资源类,而不是本地资源类(本地的资源类是<包名>.R,是自动生成的)。
为了创建视图和在应用中引用它们,一个常用的模式如下:
(1) 在布局文件中定义一个view/widget和为其分配一个唯一的ID:
<Buttonandroid:id="@+id/my_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/my_button_text"/>
(2) 然后创建view对象的实例并从布局中获取它(一般在onCreate()方法中)
Button myButton= (Button) findViewById(R.id.my_button);
当创建一个相对布局(RelativeLayout)时,定义view对象的ID是非常重要的。在相对布局中,兄弟view能够通过唯一的ID引用来定义它们之间布局的相对位置。
一个ID在整个视图数中不需要是唯一的,但必须是我们要搜索的数部分是唯一的(通常是整棵树,所以可能的话最好是整棵树)。
5. 布局参数(Layout Parameters)
名为layout_something的XML布局属性定义了view的布局参数,这些参数适用于view所在的ViewGroup。
每个ViewGroup类实现一个扩展自ViewGroup.LayoutParams的嵌套类,该子类包括定义了每个子view大小和位置的属性类型,适用于视图组(view group)。正如我们在下图看到的,父视图组定义了每个子视图(包括子视图组,比如这里的子视图有下图的View,子视图组有下图的RelativeLayout)的布局参数。
图1
注意每个LayoutParams子类有它自己设置值的语法,每个子元素必须定义适用于其父元素的LayoutParams,虽然父元素也可以为它的子元素定义不同的LayoutParams。
所有的视图组都包含一个宽度和高度(layout_width
和layout_height
),且每个视图都要定义它们。很多LayoutParams页可以包括可选的margins(页边的空白)和borders(边距)。
我们可以使用精确的度量指定宽度和高度,尽管我们可能很少这么使用。更常见的是我们将使用下面的常数设置宽度或是高度:
(1) wrap_content 告诉我们view调整自身内容所需要的尺寸来调整自己的大小。
(2) fill_parent (从API 8版本开始重命名为match_parent )告诉我们的视图和它的父视图组一样大小就可以。
通常,不推荐使用比如是像素这样的绝对单位来指定布局宽度和高度,相反,使用比如是和密度无关的像素单元(density-independent pixel units,dp)相对度量。使用wrap_content或fill_parent是更好的办法,因为这样它有助于确保我们的应用程序在不同屏幕大小的设备上正常显示,Android系统能够接受的单位类型在Available Resources文档中定义
(http://developer.android.com/guide/topics/resources/available-resources.html#dimension)
6. 布局位置(Layout Position)
View的几何形状是矩形,一个view的位置采用一对left和top坐标来表示,view的二维(可理解为尺寸)用width和height来表示,位置和尺寸的单位是像素。
可调用方法getLeft()和getTop()来获取view的位置,前者返回代表view的矩形的左或是X坐标,后者返回代表view的矩形的上或是Y坐标。这两个方法都返回相对于其父对象的位置。比如,当getLeft()返回20,意味着view位于其父对象左边缘以右的20像素处。
另外,提供了一些避免没必要计算的方法getRight()和getBottom(),这些方法返回代表view的矩形的右边和下边边缘的坐标。比如调用gerRight()相当于这样的计算:getLeft() + getWidth()。
7. 大小、内边距和外边距(Size,Padding and Margins)
View的大小用一个宽度和高度来描述,实际上,view有两组宽度和高度的值。
第1对被称为测量宽度和测量高度(measured width and measured height),这些长度定义一个view在其父view中的大小。测量尺寸(measured dimensions)能够调用getMeasuredWidth()和getMeasuredHeight()。
第2对被简称为宽度和高度(width and height),或是有时候被称为绘制宽度和高度(drawing width and drawing height)。这些尺寸定义了view在屏幕上绘制和布局时的实际大小,这些值可能,但不一定和测量宽度和测量高度相同,这个宽度和高度通过调用getWidth()和getHeight()获得。
为了测量view的尺寸,一个view必须把它的padding考虑进来,padding用像素来表示,一个view的上下左右部分都有padding。通过一个指定数量像素的padding来位移view的内容。比如,包含2个像素点的一个左padding将使view的内容相对于左边界向右推移2个像素。Padding通过调用setPadding(int, int, int, int)方法来设置和调用getPaddingLeft()、getPaddingTop()、getPaddingRight()和getPaddingBottom()来查询。
虽然一个view能够定义一个padding,但它不提供任何对margins(外边距)的支持。但是view groups提供这样的支持,参考ViewGroup和 ViewGroup.MarginLayoutParams以获取更多的信息。
为获取更多关于尺寸的信息,请看Dimension Values
http://developer.android.com/guide/topics/resources/more-resources.html#Dimension
8. 常见布局(Common Layouts)
ViewGroup类的子类提供独特的方式来显示嵌入到ViewGroup中view。下面是一些常见的布局类型内置在Android平台中。
注意:虽然我们可以在另一个布局中嵌入一个或是多个布局来完成我们的UI设计,我们应尽可能保持我们的布局层次结构尽可能浅。有更少嵌套的布局在绘制时更快(宽视图层次结构比深度视图层次结构更好,a wide view hierarchy is better than a deepview hierarchy)。
图2
(1) 线性布局(Linear Layout)
线性布局以一个单一的水平或是垂直的排列方式来组织里面的view,如果布局窗口的长度超过屏幕的长度,它就创建一个滚动条。
(2) 相对布局(Relative Layout)
使我们能够指定每个子view之间的相对位置(比如子view A在子view B左边的相对位置),或是相对于父view的位置(和父view的顶部对齐)。
(3) 网页视图(Web View)
显示网页。
9. 建立适配器的布局(Building Layouts with an Adapter)
当我们布局的内容是动态的或是不是预设的,我们可以使用继承自AdapterView的布局来在运行时填充包含有views的布局。一个AdapterView的子类使用一个Adapter来绑定数据到它的布局中。这个Adapter表现为数据源和AdapterView布局的中间人,这个Adapter获取数据(比如从一个数据或是一个数据库查询)和将每个条目转换为一个能被添加到AdapterView布局中的view。
依靠一个adapter支持的常见布局包括:
图3
(1) 列表视图(List View)
显示一个滚动的单一列清单。
(2) 网格视图(Grid View)
显示一个滚动列和行的网格。
10. 向一个适配器view填充数据(Filling an adapter view with data)
我们能够通过绑定AdapterView实例到一个Adapter来填充像ListView或是GridView这样的AdapterView,Adapter从一个外部数据源获取数据和创建一个代表了每个输入条目的view。
Android提供了多个Adapter子类,这些子类有助于获取不同类型的数据和为一个AdapterView建立view,两个最常见的adapters如下:
(1) ArrayAdapter(数组适配器)
当数据源是一个数组时使用这个适配器,默认情况,ArrayAdapter通过在每个数组项上调用toString()来为每个数组项创建一个view和将这些内容保存在TextView。
比如,如果我们想要在ListView中显示一个字符串数组,使用构造函数初始化一个新的ArrayAdapter来指定每个字符串和这个字符串数据的布局:
ArrayAdapter adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, myStringArray);
这个构造函数的参数如下:
This:我们APP的上下文(context)。
android.R.layout.simple_list_item_1:布局,包含一个TexView(文本框)中数组的每个字符串。
myStringArray:字符串数据。
然后简单的调用我们ListView的setAdapter(),如下:
ListView listView = (ListView) findViewById(R.id.listview);
listView.setAdapter(adapter);
如果要自定义每项的外观,我们可以重写(override)我们保存在数组中的对象的toString()方法,或者,为不同于TextView的对象的每一项创建view,徐璈扩展ArrayAdapter类和重写getView()来获得我们想要的每一项的view类型。
(2) SimpleCursorAdapter
当我们从光标处获取数据时使用这个adapter,在使用SimpleCursorAdapter时,我们必须在光标处为每行指定要使用布局,在光标处的列应该插入这个布局的views。比如,如果我们要创建一个人的名字和电话号码列表,我们能够执行一个返回一个光标的查询,这个查询的结果包括每个人的一行和名字与电话号码的列。然后我们可以创建一个字符串数组来指定光标处开始的哪些列,这些列对应于每个结果中布局的内容,创建一个整数数组来指定每一列重要放置的views。
String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER};
int[] toViews = {R.id.display_name, R.id.phone_number};
当我们实例化SimpleCursorAdapter,传递布局用于每个结果,cursor参数包含了结果,另外两个参数是两个数组:
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
R.layout.person_name_and_number, cursor, fromColumns, toViews, 0);
ListView listView = getListView();
listView.setAdapter(adapter);
SimpleCursorAdapter然后在光标处创建一个view,使用提供的布局R.layout.person_name_and_number将每个fromColums项插入到对应的toView视图中。
如果在我们APP声明周期之内,我们改变了由adapter读取的底层数据,我们应该调用notifyDataSetChanged(),这将通知这个视图数据已经改变,它应该刷新自己。
11. 处理点击事件(Handling click events)
通过实现AdapterView.OnItemClickListener接口,我们可以响应一个AdapterView每一项的点击事件,比如:
// Create a message handling object as an anonymous class.
private OnItemClickListener mMessageClickedHandler = new OnItemClickListener() {
public void onItemClick(AdapterView parent, View v, int position, long id) {
// Do something in response to the click
}
};
listView.setOnItemClickListener(mMessageClickedHandler);
参考链接:
Layouts
http://developer.android.com/guide/topics/ui/declaring-layout.html
XML布局
http://weimingtom.iteye.com/blog/1276406
android2.2官方开发手册(02-01)-DeclaringLayout-设计布局
http://blog.csdn.net/forlong401/article/details/6295024