to top

Layouts

翻译:Mark(武汉大学)QQ:765731535

在一个activity中,你的layout是支撑用户界面的整体架构。它定义了布局结构,容纳所有的用户能直接接触的元素。你可以使用以下两种方式声明你的layout:

  • Android提供了一个明确的对应于View类以及其子类的词汇(vocabulary),比如widgets和layout。
  • 在运行时实例化layout元素。你的应用可以用编程的方式创建View和ViewGroup对象(以及操作它们的属性(properties))。

Android Framework赋予你一定的灵活性来使用他们当中的一种或所有的方法去声明和管理你的应用UI。 举个例子,你可以在XML中声明你的应用的默认布局,包括屏幕元素以及他们的属性。你也可以在运行期添加代码去修改屏幕对象的状态,包括在XML中声明的对象。

在XML中声明UI的优点是你可以尽可能地把应用的表现与控制行为的代码分隔开。你的UI描述脱离于在你的应用程序的代码,这就意味着你可以修改或调整它(UI)而不用修改源代码或者说重新编译。举个例子,你可以为不同的屏幕方向,不同的设备屏幕尺寸甚至是不同的语言创建相应的XML布局。另外,在XML中声明layout会让你的UI结构可视化变得更容易,同时受益的还有调试问题。本篇文档聚焦于教你怎样在XML中声明你的layout。如果你更倾向于在运行时实例化View对象,请参看ViewGroupView类的文档

一般说来,声明UI元素所用到的XML词汇(vocabulary)跟类和方法的命名以及结构对应得很紧密,体现在元素的名字对应于类名而属性名对应于方法名。事实上,这种对应很直接以致于你可以猜到某一个类方法对应的XML属性,或者是根据XML元素猜到对应的类名。 然而,请记住,并不是所有的词汇都是像这样的。在一些情形中,有一些细微的命名差异。举例来说 ,EditText元素有一个text属性对应于EditText.setText()方法。

小提示:想要学习更多有关不同layout类型的内容,参看Common Layout Objects。还有一系列的有关创建各种layouts教程,参看 Hello Views指导。

Write the XML


使用Android的XML词汇。你可以快速地设计出UI layouts和他们包含的元素.你可以用同样的方法创建web页面-使用一系列的嵌套元素。

每一个layout文件都必须明确地包含一个根元素,它必须是View或ViewGroup类的对象。只要你定义了一个根元素,你就可以添加额外的layout对象或者窗口部件(widget)作为子元素逐渐地建立一个View层次来定义你的layout。 举例来说,下面是一个使用了垂直here's an XML layout that uses a vertical LinearLayout 来容纳TextView和一个Button的XML layout:

<?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中声明了你的layout之后,以.xml扩展名保存在你的Android工程的res/layout/目录下,然后才能顺利编译。

稍后我们将会讨论这里展示的每一个属性(attribute)

Load the XML Resource


当编译你的应用程序的时候,每一个XML layout文件被编译成一个 View源。你应该在你的 Activity.onCreate()回调函数的实现当中载入来自程序代码的XML资源。 依靠调用setContentView(), 把你的layout资源的引用以类似R.layout.layout_file_name的格式传递给它就能做到:

举例来说,如果你的XML layout被保存为main_layout.xml,你就可以像下面这样载入到你的Activity中:

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_layout);
}

当你的Activity被启动的时候,你的Activity中的onCreate()回调方法就会被Android Framework调用。(在Activities文档中查看有关Activities的讨论)

Attributes


每一个View和ViewGroup对象都支持它们自己的XML属性,一些attributes对于View类来说是特定的(举个例子:TextView支持textSize属性),但是这些属性也会被扩展自View类的对象所继承。 一些属性对于所有的View对象是相通的,因为它们都是从根View类中继承来的(就像id属性)。还有一些属性被认为是“layout Parameters”,意思就是它们用来描述特定的View对象的layout方向,就像在他们的父ViewGroup中定义的一样。

ID

任意一个View对象都可能伴随着一个整数类型的ID用来唯一地标识这个View。当应用被编译的时候,ID是作为一个整数来引用的, 但是这个ID通常在layout XML文件的id属性中被指定为一个字符串。这是一个和所有View对象(在View 类中定义)都一样的属性,你将会非常频繁地使用它。 在XML标签中使用ID的语法如下:

android:id="@+id/my_button"

字符串开头的(@)标记表明XML解析器应该解析和扩展ID字符串的剩余部分,并且把它定义为标识为一个ID资源。(+)标记意味着这是一个应该被创建并且加入到我们的资源(R.java文件)当中的新的资源名称。 提供了大量的其他的ID资源。当你引用一个Android 资源ID的时候,你没有必要使用“+”,但是你必须加上android的包命名空间。像这样:

android:id="@android:id/empty"

合适地使用android包命名空间,我们就可以引用来自android.R的资源ID,而不是本地的资源类。

为了创建views并且在应用中引用他们,通常的模式是:

  1. 在layout中定义一个view或者widget,赋予它们唯一的ID:
    <Button android:id="@+id/my_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/my_button_text"/>
  2. 创建一个view对象的实例,然后从layout当中捕获它(通常是在onCreate() 方法中)。
    Button myButton = (Button) findViewById(R.id.my_button);

当你创建一个RelativeLayout时,为view对象定义ID是很重要的。 在一个relative layout中,同胞的view可以定义它们的layout相对于另外一个唯一ID标识的同胞view的位置。

在整个的layout结构中一个ID没有必要是唯一的,但是在你正在搜索的那一部分结构中它应该是唯一的。(很有可能是你整个结构,因此你最好尽可能地让他完全唯一化)。

Layout Parameters

被命名为layout_something的XML的layout属性在自己所处的View中为适合ViewGroup的View定义了layout parameters。

每一个ViewGroup类都实现了一个继承了ViewGroup.LayoutParams的嵌套类。 这个子类包含了为每一个对于view group适当的child view 定义尺寸和位置的属性类型。 就像你在figure 1中看到的一样,父view为每一个子view 定义了layout parameters(包括子view group)。

Figure 1. Visualization of a view hierarchy with layout parameters associated with each view.

请注意:每一个LayoutParams子类都有它自己的设定值的语法。每一个子元素都必须定义适应它的父元素的LayoutParams,尽管它也可能为它自己的子元素定义不同的LayoutParams。

所有的view group都包含一个宽度和高度属性(layout_widthlayout_height),每一个view都要定义他们。很多LayoutParams还会包含可选的margin以及border属性。

你可以用精确的数字指定width与height,虽然你将很可能不会经常这样做。更多地,你将会使用下面这些常量之一去设置你的height和width:

  • wrap_content告诉你的view调整自己到内容所要求的尺寸。
  • fill_parent(修改自API 8中的match_parent)告诉你的view变得跟它的parent view group所能允许的最大尺寸一样。

一般来说,使用类似像素的绝对单位来指定一个layout的height和width是不被推荐的,取而代之地, 使用例如密度无关像素单位(dp),wrap_contentfill_parent等相对量度是一种更好的解决办法, 因为这样能帮助你确定你的应用能恰当地显示在大量的不同尺寸的设备屏幕上。这个可接受的衡量方式定义在 Available Resources文档中。

Layout Position


View的几何形状是一个矩形,每一个view有一个用lefttop的坐标来确定的定位, 另外还有两个量度,height和weight来确定view的高度和宽度。定位的尺寸的单位都是pix(像素)。

用调用getLeft()getTop()的方法来获得view的定位是可行的。前者返回代表view的矩形的left即X坐标, 后者返回top即Y坐标。这两个方法返回的都是view相对于它的父view的定位值。例如,当getLeft()返回20的时候,就意味着这个view位于距离它的直接父view的右边缘的20像素的位置。

另外,这里还给了几个简便的方法来避免不必要的计算,例如getRight()getBottom()方法。这两个方法返回的是代表view的矩形的右边缘和下边缘的坐标。 举个例子,调用getRight()就相当于以下的运算:getLeft() + getWidth()

Size, Padding and Margins


View的大小以width和height显示出来,事实上一个view拥有两对height和width值。

第一对被叫做measured widthmeasured height。它们定义了一个view在它的parent中显示为多大。 实际的尺寸可通过调用 getMeasuredWidth()getMeasuredWidth()和 getMeasuredHeight()getMeasuredHeight()来得到.

第二对被简单地称为widthheight,有的时候也被叫做drawing widthdrawing height。它们在绘制时间以及布局之后定义view在屏幕上的实际尺寸。 这些值可能不必与measured height 和measured width不同。Height和width可以通过调用getWidth()

为了测量尺寸,view把它的padding计算在内。Padding包括view的上下左右四个部分,单位是像素。Padding可以用特定的像素数来抵消view的内容部分。 举例来说,值为2的left padding将会把view的内容从左边缘向右推2pixels。Padding可以用 setPadding(int, int, int, int)方法来设置, 靠调用getPaddingTop(), getPaddingRight(), getPaddingBottom()getPaddingLeft()来查询。

尽管view可以定义padding,但是它没有提供任何对margins的支持。然而view groups提供了这样的支持。 更多信息请查询ViewGroup,以及 ViewGroup.MarginLayoutParams

更多有关dimensions的信息,请参看 Dimension Values.

Common Layouts


ViewGroup类的每一个子类都有一个独一无二的方法来显示你在其中嵌套的view。 下面是一些构建在Android Platform上的更常见的layout类型。

请注意:尽管你可以在layout中嵌套使用多个layout来实现你的UI设计,但是你还是应该力求保持你的layout层级尽量地浅显易懂。 你的layout嵌套地越少就会绘制地越快(一个宽泛的view层级比一个深度的要好)。 Although you can nest one or more layouts within another layout to acheive your UI design, you should strive to keep your layout hierarchy as shallow as possible. Your layout draws faster if it has fewer nested layouts (a wide view hierarchy is better than a deep view hierarchy).

Linear Layout

把它的子元素组织在一个单独的水平或竖直的行列里边的layout。如果窗口的长度超过了屏幕的尺寸,那么它会自动建立一个scrollbar(滚动条)。

Relative Layout

它使你能够制定子对象相对于彼此(child A to the left of child B)或者相对于父元素的定位(aligned to the top of the parent)。

Web View

显示web页面

Building Layouts with an Adapter


当你的layout内容是动态的或者不是事先确定的,你就可以在使用AdapterView的子类在运行时去充实layout。 AdapterView的一个子类可以使用一个Adapter来把数据捆绑到layout上。 这个Adapter发挥着数据源和 AdapterView Layout之间的“中间人”的作用-- Adapter获取数据(从一个比如数组或者数据库查询之类的来源),然后把每一个入口转变成能被添加到 AdapterViewLayout里的view。

常见的被Adapter支持的layout如下:

List View

显示一个可滚动的单列列表。

Grid View

显示一个可滚动的行列组成的网格。

Filling an adapter view with data

你可以在Adapter绑定AdapterView 以此来填充ListView或者 GridView,这个 Adapter从外部资源中获取数据,创建一个代表着每一个数据入口的View

Android提供了一些对提取不同类型数据以及为AdapterView创建view有用的 Adapter的子类。如下是两个最常用的Adapter:

ArrayAdapter
当你的数据源是数组的时候使用这个Adapter。默认情况下, ArrayAdapter靠在每一个数组项调用 toString()和把内容放置在 TextView的方式为每一个数组项创建一个view。

举个例子:如果你有一个想要展示在ListView的字符串数组,用构造器初始化一个新的 ArrayAdapter来为每一个字符串和字符串数组指定layout:

ArrayAdapter adapter = new ArrayAdapter<String>(this, 
        android.R.layout.simple_list_item_1, myStringArray);

这个构造器的参数是:

  • 你的app的上下文。
  • 包含了为数组中每个字符串指定内容的TextView的layout。
  • 字符串数组。

然后简单地在你的ListView中调用 setAdapter()

ListView listView = (ListView) findViewById(R.id.listview);
listView.setAdapter(adapter);

为了定制每一个条目的外观,你可以为数组中每个对象重写toString()方法。或者,为了给每一个不是 TextView的条目创建view, (举例来说,如果你想为每一个数组项建立ImageView)你要为每个item继承 ArrayAdapter类并重写getView()方法来返回你想要view类型。

SimpleCursorAdapter
当你的数据来自于一个Cursor的时候使用这个Adapter。 当使用SimpleCursorAdapter的时候,你必须为cursor中每一行指定一个Layout以及cursor中的哪一列应该被插入到layout的哪一个view中去。 举例来说,如果你想创建人们名字和电话号码的列表,你就可以执行一个返回包含了一个 Cursor的查询,在这个Cursor中,每行为一个人,列包含人名,电话号码之类的信息。 然后你就可以创建一个在layout中指定你想要的Cursor的哪些列的字符串数组,以及指定对应的每一列应当被放置的所在的view 的整形数组:

String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME, 
                        ContactsContract.CommonDataKinds.Phone.NUMBER};
int[] toViews = {R.id.display_name, R.id.phone_number};

当你实例化SimpleCursorAdapter类的时候, 传递layout给Cursor包含的每一个result使用。然后这两个数组:

SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, 
        R.layout.person_name_and_number, cursor, fromColumns, toViews, 0);
ListView listView = getListView();
listView.setAdapter(adapter);

然后SimpleCursorAdapter就会使用提供的layout将每一个fromColumns插入到对应的toViews view中,并以此在 Cursor中为每一行创建view。

.

如果在你的应用的生命周期当中,你改变了底层可以被Adapter读取的数据,你应该调用notifyDataSetChanged()。 这样能够通知哪些相关的view:数据已经改变了,是时候刷新了。

Handling click events

你可以实现AdapterView.OnItemClickListener接口在 AdapterView的每一个item上回应点击事件。

// 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);