2.3 排布视图
Android UI基础教程
Android视图层次起始于一个布局容器。这些容器包含子视图并安排它们的相对位置。有几个具有不同特征的容器类型,以在不同情况下获得最优解。
2.3.1 FrameLayout
最简单的布局容器是FrameLayout。这个容器完全不安排子视图。它只是简单地呈现每一个视图,从上到下摆下来。视图的顺序是基于它们在XML文件中的声明:视图在文件的后部声明的会被画在顶部。任何时候当你想创建重叠视图时都可以使用这个布局。
当创建自定义的可点击的元素时FrameLayout尤其适用。你可以使用FrameLayout来配对一个有ImageView的按钮,设置按钮背景为透明。这给你更多的对于按钮图像的填充和缩放的控制,而不仅仅是设置一个背景。
2.3.2 TableLayout
TableLayout展示表格格式的数据(图2.7)。它把子视图放置成行和列,每一行都包含在一个TableRow容器中。一个TableLayout将会有和最多单元的TableRow一样多的列。与大多数视图的子视图不同,TableLayout的子视图不能指定一个layout_width。这个是由TableLayout处理并为你设置。单元格可以被标记为跨越多列并可以扩张或者缩小以填充可用空间。
当要以表格形式展现数据时,你应该使用这个视图。在其他例子中,使用LinearLayout、RelativeLayout或者新的GridLayout。
2.3.3 LinearLayout
在第一章中,你看到过LinearLayout。之后的应用中将会多次使用这个容器。顾名思义,这个容器把它的子视图按照垂直或者水平方向单向排列。Orientation属性为linearLayout子视图设置方向。子视图会指定它们将会占用线性布局多大空间。通过设置layout_weight做到这一点。这个参数确定了对比其他视图的相对权值。默认情况下,所有的视图的权值值都是0。这意味着它们将占用这么大的空间,这个空间刚刚好够放下它们里面的内容。设置权值值高于0将会使得该视图扩展到填充布局中剩余的空间。
权值与其他视图的相对值将会决定一个特定视图所占用的空间大小。
图2.8的按钮被包含在朝向设置为竖直方向的线性布局里。每个按钮都占用所需的能装下其内容的空间。顶部按钮的权值值设为0,只占用了刚好能展示其内容的空间。另外两个按钮的权值值分别设为1和4,除了占用正常所需的空间外,它们还占用了额外的空间以填满剩余的空间。底部的按钮有更高的权值值并占用了更多的可用空间。它实际上占用了五分之四的剩余空间,只给第三个按钮留下了五分之一的空间(1 + 4 = 5)。使用布局的权值允许你创建按比例安排的视图,这大大增加了布局的灵活性。
注意: 由于和layout_height以及layout_width的关系,使用layout_weight有时候会有些迷惑。一般来说weight会覆盖height和width,但也不总是如此。如果你打算使用layout_weight属性,把对应的height或者width设为0dp。这样一来,视图的大小就会只由权值控制,而不会受到别的干扰了。
线性布局非常容易使用,并且非常适合TimeTracker应用的第一版。还记得图2.1吗?下面是创建UI的方法。
1.打开你先前创建的TimeTracker工程。
2.打开自动创建的res/main.xml文件。用下面的XML布局替换它的内容:
`<?xml version="1.0" encoding="utf-8"?>`
`<LinearLayout xmlns:android="http://schemas.android.com/`
`→ apk/res/android"`
` `` `` ``android:layout`_`width="match`_`parent"`
` `` `` ``→ android:layout`_`height="match`_`parent"`
` `` `` ``android:orientation="vertical">`
` `` `` ``<TextView android:id="@+id/counter" android:text="0"`
` `` `` `` `` `` android:layout`_`height="wrap`_`content"`
` `` `` `` `` `` → android:textAppearance="?android:`
` `` `` `` `` `` → attr/textAppearanceLarge"`
` `` `` `` `` `` android:gravity="center" android:padding="10dp"`
` `` `` `` `` `` android:layout`_`width="match`_`parent"`
` `` `` `` `` `` → android:textSize="50dp"></TextView>`
` `` `` ``<LinearLayout android:layout`_`height="wrap`_`content"`
` `` `` `` `` `` android:layout`_`width="match`_`parent"`
` `` `` `` `` `` → android:orientation="horizontal">`
` `` `` `` `` `` <Button android:text="@string/start"`
` `` `` `` `` `` → android:id="@+id/start`_`stop"`
` `` `` `` `` `` android:layout`_`height="wrap`_`content"`
` `` `` `` `` `` → android:layout`_`width="0dp"`
` `` `` `` `` `` android:layout`_`weight="1"></Button>`
` `` `` `` `` `` <Button android:text="@string/reset"`
` `` `` `` `` `` → android:id="@+id/reset"`
` `` `` `` `` `` android:layout`_`height="wrap`_`content"`
` `` `` `` `` `` → android:layout`_`width="0dp"`
` `` `` `` `` `` android:layout`_`weight="1"></Button>`
` `` `` `` `` `` </LinearLayout>`
` `` `` `` `` `` <ListView android:layout`_`weight="1"`
` `` `` `` `` `` → android:layout`_`width="fill`_`parent"`
` `` `` `` `` `` android:layout`_`height="0dp"`
` `` `` `` `` `` → android:id="@+id/time`_`list">`
` `` `` `` `` `` </ListView>`
`</LinearLayout>`
此XML代码使用线性布局来摆放它的3个子视图:一个文本视图来表示当前时间,第二个是包含两个按钮的线性布局,以及展示所有之前时刻的列表视图。按钮被摆放在水平朝向的二级线性布局中。注意到你把两个按钮的layout_width都设置为了0dp,它们的layout_weight值都设为了1。这将会使得按钮扩展到可以填满布局的宽度并且均分该空间。列表视图将会为每一行展示自定义布局的时间列表。在下一节中即将会学到更多关于使用列表视图的知识。
2.3.4 RelativeLayout
另外一个常见的布局容器是RelativeLayout。相对布局比线性布局更加灵活,但是它们同样更加复杂。顾名思义,文本视图基于子视图相对于其他视图的位置以及该文本视图本身来摆放子视图。举个例子,要在一个按钮的左侧放置一个文本视图,你将会创建一个属性toLeftOf="@id/my_button"的文本视图。这种灵活性让你可以在此容器中创建非常复杂的UI。
提示: 如果一个视图在相对布局中引用了其他视图,那么它需要在referenced视图中声明。
图2.9展示了摆放在一个相对布局中的一些按钮。按钮1、2、3、4、5都是相对于父RelativeLayout容器放置。角落按钮的属性分别把它们对齐为相对于该相对布局的顶部、底部、左边以及右边。中央的按钮对齐于相对布局的中心,其余的按钮被相对于中央的按钮放置。表2.1列出了在RelativeLayout中视图可用的属性,以及使用它们的方法。
注意: relative layout的子视图按照它们被声明时的顺序排列。所以如果一个视图被声明放在布局的中央,那么所有随后的视图都将会基于该视图的中央对齐放置。
使用这些属性来相对于另一个视图放置视图。这个属性设置视图的规则,以确保它不会越过目标视图的边界。它的值必须为另一个视图的id
在使用相对布局的时候可能会有些棘手,但是当你使用它创建更复杂的UI的时候会发现这一切都是值得的。记住如果你发现自己在创建多重嵌套的线性布局,你应该考虑使用一个相对布局来优化UI的绘制。
注意: 在相对布局中不能有循环依赖。例如,你不能把RelativeLayout的width设为wrap_content并且在子视图之一上使用alignParentTop。这将会产生错误,不会生成R.java文件。
2.3.5 GridLayout
Android 4带来了一种新的称作GridLayout的布局容器。顾名思义,它把视图安排成一个由行和列组成的网格。这个布局使得创建常见的“仪表盘”风格的UI(如Google+之类的应用)变得更加容易。一般来说你会使用TableLayout创建这样的UI,但是GridLayout允许你创建一个低层次的相同布局。这会减少Android要画的视图的数目,从而提高性能。GridLayout同样也支持使用图形布局编辑器进行拖放来设计UI。开发者们仅仅使用GridLayout和布局编辑器就能够创建复杂并高效的布局。
图2.10显示了一个使用GridLayout容器创建的实例布局。在这个布局中有按照行列排布的4个按钮。创建这个布局的XML是:
图2.10 GridLayout可以不使用嵌套的容器产生复杂布局
`<?xml version="1.0" encoding="utf-8"?>`
`<GridLayout xmlns:android="http://schemas.android.com/`
`→ apk/res/android"`
` `` `` ``android:layout`_`width="match`_`parent"`
` `` `` ``android:layout`_`height="match`_`parent"`
` `` `` ``android:columnCount="3" >`
` `` `` ``<Button`
` `` `` `` `` ``android:id="@+id/button1"`
` `` `` `` `` ``android:layout`_`column="1"`
` `` `` `` `` ``android:layout`_`row="1"`
` `` `` `` `` ``android:text="Button" />`
` `` `` ``<Button`
` `` `` `` `` ``android:id="@+id/button2"`
` `` `` `` `` ``android:layout`_`column="1"`
` `` `` `` `` ``android:layout`_`gravity="bottom"`
` `` `` `` `` ``android:layout`_`row="2"`
` `` `` `` `` ``android:text="Button" />`
` `` `` ``<Button`
` `` `` `` `` ``android:id="@+id/button3"`
` `` `` `` `` ``android:layout`_`column="2"`
` `` `` `` `` ``android:layout`_`row="2"`
` `` `` `` `` ``android:text="Button" />`
` `` `` ``<Button`
` `` `` `` `` ``android:id="@+id/button4"`
` `` `` `` `` ``android:layout`_`column="2"`
` `` `` `` `` ``android:layout`_`row="3"`
` `` `` `` `` ``android:text="Button" />`
与TableLayout不同,一个GridLayout并不需要明确的TableRow元素。按钮们本身就已经声明了它们应该出现的地方。
默认情况下,这个布局将不包括任何按钮之间的空间。要想增加空间,你可以使用传统的margin和padding参数,或者可以使用将在Android 4:Space里面介绍的新view。这个视图只是在布局元素之间增加了间距。当在图形化布局编辑器中使用拖放来创建布局时,会自动插入空间以达到想要的外观。下面是通过布局编辑器为图2.10所示的布局创建的空间:
`<Space`
` `` `` `` `` ``android:layout`_`width="58dp"`
` `` `` `` `` ``android:layout`_`height="1dp"`
` `` `` `` `` ``android:layout`_`column="0"`
` `` `` `` `` `` `` ``android:layout`_`gravity="fill`_`horizontal"`
` `` `` `` `` ``android:layout`_`row="0" />`
` `` `` ``<Space`
` `` `` `` `` ``android:layout`_`width="128dp"`
` `` `` `` `` ``android:layout`_`height="1dp"`
` `` `` `` `` ``android:layout`_`column="1"`
` `` `` `` `` ``android:layout`_`gravity="fill`_`horizontal"`
` `` `` `` `` ``android:layout`_`row="0" />`
` `` `` ``<Space`
` `` `` `` `` ``android:layout`_`width="134dp"`
` `` `` `` `` ``android:layout`_`height="1dp"`
` `` `` `` `` ``android:layout`_`column="2"`
` `` `` `` `` ``android:layout`_`gravity="fill`_`horizontal"`
` `` `` `` `` ``android:layout`_`row="0" />`
` `` `` ``<Space`
` `` `` `` `` ``android:layout`_`width="1dp"`
` `` `` `` `` ``android:layout`_`height="83dp"`
` `` `` `` `` ``android:layout`_`column="0"`
` `` `` `` `` ``android:layout`_`gravity="fill`_`horizontal"`
` `` `` `` `` ``android:layout`_`row="0" />`
` `` `` ``<Space`
` `` `` `` `` ``android:layout`_`width="1dp"`
` `` `` `` `` ``android:layout`_`height="180dp"`
` `` `` `` `` ``android:layout`_`column="0"`
` `` `` `` `` ``android:layout`_`gravity="fill`_`horizontal"`
` `` `` `` `` ``android:layout`_`row="2" />`
` `` `` ``<Space`
` `` `` `` `` ``android:layout`_`width="1dp"`
` `` `` `` `` ``android:layout`_`height="173dp"`
` `` `` `` `` ``android:layout`_`column="0"`
` `` `` `` `` ``android:layout`_`gravity="fill`_`horizontal"`
` `` `` `` `` ``android:layout`_`row="3" />`
`</GridLayout>`