Android中的4.0新布局控件:Space和GridLayout

时间:2020-12-16 05:28:09

Android4.0 Ice Cream Sandwich ICS) 提供了两种新的控件,也就是SpaceGridLayout,是专门为大屏幕设备提供更丰富的用户交互体验而设计。

在这之前,Android中最常用的布局类是LinearLayout,它能将它的子元素们水平排列或垂直排列。当界面布局比较复杂的时候,也可以利用它嵌套一系列分割出来的LinearLayout子布局来实现,嵌套的层数通常不宜太深,只适合于许多简单布局的情形。

嵌套布局有很多显著的缺点(参考 Android Layout Tricks #1Flattening The Stack 这些文章),总结起来有这三个方面:

  • 1.无法同时在水平和竖直方向对齐;
  • 2.嵌套太深影响性能;
  • 3.不适用于那些支持*编辑的设计工具;

以下就是关于上述第一个问题的简单的例子:

Android中的4.0新布局控件:Space和GridLayout

当文字字体和“Email address”标签文字本身改变的时候,我们会希望标签与它右边的组件的底部基线对齐,同时让它的右边缘与它下方的标签的右边缘对齐。但如果用嵌套的线性布局做这个会很困难,因为标签本身会去和其他组件在水平和竖直方向上自动对齐。

诸如此类的问题并不是第一次出现在Android或者更大范围的UI工具上,但我们需要用它们丰富的平台支持,来推动我们的其他工作。

GridLayout

为了提供更好的布局支持,我们已经在4.0框架中增加了一种新的布局类即GridLayout,它通过将容器自身的真实区域切割成行列单元来解决上述问题。

Android中的4.0新布局控件:Space和GridLayout

正如上图所示在使用GridLayout之后,“Email address”标签就可以同时属于那底部基线对齐的一行和那右边缘对齐的一列。

GridLayout用一组无限细的直线将它的绘图区域分割成行、列、单元。它支持行、列拼接合并,这就使得一个子元素控件能够排布在一系列连续单元格组成的矩形区域。在下文中,我们将直接使用“行”、“列”、“单元格”这些术语来分别代表“行集合”、“列集合”、“单元格集合”,这里集合的意思是指那些一个或多个连续元素。

LinearLayout的相似性

GridLayout的所有XML APILinearLayout有着一致的语法规则,所以如果你已经使用过LinearLayout的话,上手GridLayout也是应该很容易的。事实上,它们之间是非常相似的,相似到直接将XML文件中的标签名从LinearLayout改到GridLayout而无需做其他改变,就可以实现与LinearLayout中相似的UI布局。即使不是这样,你也仍然能借此开启使用这种二维布局之路。

开始使用GridLayout

Android 4.0 SDK的程序示例中,有两个示例分别演示了如何在Java代码和XML中使用GridLayout

samples/ApiDemos/src/com/example/android/apis/view/GridLayout0.java

samples/ApiDemos/res/layout/grid_layout_1.xml

这里有一个较上述XML布局中稍微简化版的XML代码:

<?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:useDefaultMargins=”true”
android:alignmentMode=”alignBounds”
android:columnOrderPreserved=”false”
android:columnCount=”4″
>

<TextView
android:text=”Email setup”
android:textSize=”32dip”
android:layout_columnSpan=”4″
android:layout_gravity=”center_horizontal”
/>

<TextView
android:text=”You can configure email in just a few steps:”
android:textSize=”16dip”
android:layout_columnSpan=”4″
android:layout_gravity=”left”
/>

<TextView
android:text=”Email address:”
android:layout_gravity=”right”
/>

<EditText
android:ems=”10″
/>

<TextView
android:text=”Password:”
android:layout_column=”0″
android:layout_gravity=”right”
/>

<EditText
android:ems=”8″
/>

<Space
android:layout_row=”4″
android:layout_column=”0″
android:layout_columnSpan=”3″
android:layout_gravity=”fill”
/>

<Button
android:text=”Next”
android:layout_row=”5″
android:layout_column=”3″
/>
</GridLayout>

在上面的代码中,要注意的是在GridLayout 布局中,已经不需要使用WRAP_CONTENT和MATCH_PARENT等属性了。要注意的是,在上面的代码中,并没有显式地说明哪个控件摆放在什么单元格,每一个控件其实是使用了layout_columnSpan及rowSpan或columnSpan去指定其相关的位置和宽度。如在上述代码中,首先用

  android:columnCount="4"指定了有4列,列的编号注意从左到右,第一列是0,如此类推。行的编号从上到下,序号也是从0开始。比如上图界面中的最下方的按钮,使用

  android;layout_row=”5”及android:layout_column=’3”指定了其按钮的位置位于第6行第4列。

  而space是Android 4.0中新增的一个控件,它实际上可以用来分隔不同的控件,其中形成一个空白的区域。这个例子中,同样通过android:layout_row及android:layout_column指定了其起始位置。

  下面再看一个例子,代码如下:

 <?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="wrap_content"
android:columnCount="4"
android:rowCount="4" >
<TextView
android:text="1,1" />
<TextView
android:text="1,2" />
<TextView
android:text="1,3" />
<TextView
android:text="1,4" />
<TextView
android:text="2,1" />
<TextView
android:text="2,2" />
<TextView
android:text="2,3" />
<TextView
android:text="2,4" />
<TextView
android:text="3,1" />
<TextView
android:text="3,2" />
<TextView
android:text="3,3 longer" />
<TextView
android:text="3,4" />
<TextView
android:text="4,1" />
<TextView
android:text="4,2" />
<TextView
android:text="4,3" />
<TextView
android:text="4,4" />
</GridLayout>

运行结果如下图所示:


Android中的4.0新布局控件:Space和GridLayout

可以看到,在gridlayout中,默认的布局是水平方向的,即将控件从左到右,从上到下进行排列,比如上图中的文本“1,2”即放置在第1行第2列中,如此类推。可以看到,其中的文本“3,3”的位置是拉伸了没跟其他控件对齐,要实现控件间的对齐,可以通过  设置android:layout_gravity=”fill_horizontal”,代码如下:

  <?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="wrap_content"
android:columnCount="4"
android:rowCount="4" >
.
.
.
<TextView
android:layout_gravity="fill_horizontal"
android:text="1,3" />
.
.
.
<TextView
android:layout_gravity="fill_horizontal"
android:text="2,3" />
.
.
.
<TextView
android:layout_gravity="fill_horizontal"
android:text="3,3 longer" />
.
.
.
<TextView
android:layout_gravity="fill_horizontal"
android:text="4,3" />
</GridLayout>
运行结果如下图:

Android中的4.0新布局控件:Space和GridLayout

如果需要使用垂直的布局,可以设定android:orientation="vertical",如下代码:

<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:columnCount="4"
android:rowCount="4"
android:orientation="vertical" >

运行结果如下图:

Android中的4.0新布局控件:Space和GridLayout

可以看到,由于文本控件“1,3”被设置成 android:layout_row="1",所以该控件被移动到第2行(注意layout_row的序号从0开始),而其他各控件的位置也相对偏移了。  接下来,我们看下其中的layout_gravity属性和gravity属性。例子如下代码:

  <GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:columnCount="4"
android:rowCount="4">

<TextView
android:text="1,1" />

<TextView
android:text="1,2" />

<TextView
android:layout_gravity="fill_horizontal"
android:layout_rowSpan="2"
android:text="1,3"/>

<TextView
android:layout_row="0"
android:text="1,4" />

<TextView
android:text="2,1" />

<TextView
android:text="2,2" />

<TextView
android:layout_row="1"
android:text="2,4" />

<TextView
android:text="3,1" />

<TextView
android:text="3,2" />

<TextView
android:layout_gravity="fill_horizontal"
android:text="3,3 longer" />

<TextView
android:text="3,4" />

<TextView
android:text="4,1" />

<TextView
android:text="4,2" />

<TextView
android:layout_gravity="fill_horizontal"
android:text="4,3" />

<TextView
android:text="4,4" />
</GridLayout>

再运行,可以看到运行结果如下:

Android中的4.0新布局控件:Space和GridLayout

可以看到,这次文本控件通过使用android:layout_gravity=”fill”,成功地伸展充满了被包含的父控件。如果要让文字“1,3”放置在单元格的正*的话,可以再通过如下代码设置;

  android:layout_gravity="fill"

  android:gravity="center"

  android:layout_rowSpan="2"

  android:text="1,3"/>

  运行后如下图:

Android中的4.0新布局控件:Space和GridLayout

可以看到,要让文字居中,只需要设置 android:gravity="center"属性即可。  最后,我们来看下一个例子,在这个例子中,分别用LinearLayout和GridLayout布局,实现了同样的功能,代码如下:

 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal" >

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Line 1" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Line 2" />
</LinearLayout>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=">" />
</LinearLayout>

<GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="3"
android:rowCount="2" >

<ImageView
android:layout_gravity="fill"
android:layout_rowSpan="2"
android:src="@drawable/ic_launcher" />

<TextView
android:layout_gravity="fill"
android:text="Line 1" />

<TextView
android:layout_gravity="fill_vertical"
android:layout_rowSpan="2"
android:gravity="fill"
android:text=">" />

<TextView
android:layout_gravity="fill"
android:text="Line 2" />
</GridLayout>

运行后,结果如下图:

Android中的4.0新布局控件:Space和GridLayout

在这个例子中,可以看到,gridlayout比较简单方便,效率也高,性能比普通的Linearlayout要好,因为 Linearlayout有时要涉及到多层的嵌套,影响渲染性能。

  小结

  在本文中,简要介绍了在Android 4.0中新增的Gridviewlayout布局的基本使用知识和技巧。Gridviewlayout布局由于其简单易用的特点,因此建议开发者在设计布局时,优先考虑使用,避免使用多层嵌套的布局。目前该布局只能在4.0中使用,据说不久的将来将会在补充支持包中能增加让低版本的Android SDK支持gridviewlayout布局的功能。












在这里你能注意到的第一个区别就是在以上例子中,子元素控件没有使用WRAP_CONTENTMATCH_PARENT这种通常出现在Android布局资源中的属性常量。但在GridLayout中,你通常不需要使用这些属性,这样做的原因在GridLayout.LayoutParams的API文档中有所解释。

行与列的索引

第二个你会注意到的事是在上述XML资源中,子元素控件并不总是明确指定它们自身摆放在哪些单元格内。事实上GridLayout中每个子元素控件的布局参数属性中有行与列的索引,它们一起定义了这个控件该摆放在哪个位置,但当其中一个参数或这两个参数都没有指定的时候,GridLayout会提供默认值而不抛出异常。

自动索引的分配

当子元素控件被加入一个GridLayout的时候,GridLayout会维护这一个位置指针,以及一个所谓的“高水位标志”,用来将控件摆放到那些还闲置着的单元格里去。

Android中的4.0新布局控件:Space和GridLayout

当GridLayout的方向属性orientation被设置成水平(horizontal),且列数属性columnCount 也设置过时(在上图例子中,该值为8),那么高水位标志(上图中红色标记的线)将会为每一列维护一个独立的高度值。当索引值需要创建时,GridLayout首先会通过检查新控件的rowSpancolumnSpan属性来计算所需单元格集的区域大小,然后从上图中位置指针所指的位置开始,以从左到右,从上往下的顺序遍历可用空间,找到的第一个可用空间的行、列索引值。

假如GridLayout的方向属性是竖直(vertical)的,所有的算法原则都与水平时一样,只是将水平轴和竖直轴所扮演的角色互换一下。

如果你希望将多个视图控件摆放在同一单元格内,你就必须要明确指定它的索引值了,因为默认的分配过程正如上文解释的那样,是将控件摆在独立的单元格内的。

大小、外边距以及对齐方式

GridLayout中,定义控件大小、外边距的做法与在LinearLayout中是一样的。对齐、位置属性(gravity)的使用方式也与LinearLayout中一样,同样使用lefttoprightbottomcenter_horizontalcenter_verticalcenterfill_horizontalfill_verticalfill这些常量值。

自适应性

与其他UI工具包着的网格布局不同的是,GridLayout不会将数据绑定到行列中。相反的,所有与对齐和布局自适应相关的东西都只跟组件自身相关。GridLayout将那些行为模式从中剥离开来,提供了一个更加通用的系统来让那些处于传统深层次嵌套布局中的但只有轻微联系的子元素们自适应到一个单一的布局配置文件中。

那些单元列的自适应性可以从该列中组件的gravity属性推断得到,如果每个组件都定义了一个gravity属性,那么这个列就可以认为是自适应的,否则只能认为是不自适应的。更多关于这方面的细节可参考GridLayoutAPI文档

模拟其他布局

GridLayout并没有加入Android平台中其他布局的所有特性,但它也拥有一系列非常丰富的特性,使得其他布局中常用的特性也能被正常地在单一GridLayout中模拟出来。

尽管LinearLayout可以被当作是GridLayout的一个特例,但当一组视图控件的布局简单到就是单一行或单一列对齐时,选择用LinearLayout会更好,因为它自身就能说明容器中子元素的排版表现目的,而且还有一些性能上的优势(当然是非常小的)。

TableLayout布局的模拟也是非常直接的,因为GridLayout支持行、列合并。同时,TableRowsGridLayout中就不需要了。对于相同的UI,使用GridLayout完成起来往往更快而且消耗更少的内存空间。

简单的RelativeLayout布局通常就是将相互关联的视图控件组合成行与列,以此来实现网格。与标准的网格不同的是,GridLayout由系统来完成那些繁重的排版工作。通过设置GridLayoutrowOrderPreservedcolumnOrderPreserved属性,就可以把GridLayout从传统的网格排版中解放出来,完成大部分RelativeLayout所能完成的布局——甚至是那种当子元素尺寸变化时要求网格线能相互交错的效果。

简单的FrameLayout排版也能够在GridLayout的单元格内实现,因为GridLayout的一个单元格可以放置多个视图。要在两个视图之间切换,只需要将它们放在同一单元格内,在代码中设置可见性属性的值来切换。与LinearLayout提到的情形一样,如果你所需要的功能就是如上描述的那样,选择使用FrameLayout会更好,因为那样会有少量性能上的优势。

GridLayout缺少的一个重要特性就是按指定比例在行、列给子元素中分配额外空间,这个特性在LinearLayout中是能通过weight这个属性实现的。此项遗漏以及实现它的可能性在GridLayoutAPI文档中也有所提及。

视图控件布局操作的阶段

将上文中讨论的单元格索引分配阶段与视图控件布局阶段区分开是很有必要的。正常来讲,单元格索引的分配阶段只发生一次,也就是在UI初始化之后。索引分配只会在索引本身未指定时才发生,同时这个阶段要确保所有视图在布局阶段都已经有一系列已定义好的、将它摆放在哪里的单元格。

而视图控件布局阶段通常在索引分配阶段之后进行,并且每当视图的尺寸改变时都会重新计算其位置、尺寸。GridLayout会在布局过程中测量每个子元素控件的尺寸,这样就能计算它们在网格中所需要的宽和高值。当GridLayout使用gravity属性最终摆放每个组件在单元格中的位置后,布局阶段才算完成。

尽管索引的自动分配只发生一次,但从技术上来讲GridLayout是一个动态布局,这意味着如果你在组件布局完成之后改变了它的方向,或者增加或者删除某些子元素,GridLayout都会重复上述过程,重新分配索引,让布局重新以一合理的方式呈现。

站在性能的角度来看,了解到GridLayout的实现已经为一些通用场景比如初始化一次但经常更新布局这种场景做了优化是十分有必要的。因此,初始化步骤通常是设置一下内部数据结构,这样布局阶段会快速完成而且不消耗任何内存。换句话说,也就是无论是改变GridLayout的方向还是改变它子元素的数量,都比其他常用布局操作要更消耗性能。

结论

GridLayout吸纳了很多Android框架中已有的通用目的的布局的功能:也就是LinearLayoutFrameLayoutTableLayoutRelativeLayout的综合功能。比如说它提供了将高度嵌套的视图结构替换成一个单一的高度优化过的布局的解决方案。

如果你刚开始使用Android UI,还不熟悉Android的布局类型,那么可以考虑使用GridLayout——它提供了其他布局所拥有的大部分特性,并且拥有比TableLayoutRelativeLayout更通用的API

我们希望FrameLayoutLinearLayout以及GridLayout的结合能提供一系列足够丰富的特性来完成大解决布局工作而无需手写代码。当然花一些时间来决定优先使用哪一种布局是非常有必要的,一个最佳的选择可以减少中间容器的使用并且提供一个更快、消耗内存更少的用户界面。