Android用户界面设计:表格布局
Chinese (Simplified) (中文(简体)) translation by Xiaxie (you can also view the original English article)
表格布局可用于显示表格数据或以类似于网页上的HTML表格的方式整齐地对齐屏幕内容。了解如何使用布局XML文件和代码创建它们。
了解布局对于良好的Android应用程序设计非常重要。在本教程中,您将学习有关表格布局的所有信息,这些布局在整齐定义的行和列中在屏幕上组织用户界面控件或窗口小部件。 如果使用得当,表格布局可以是强大的布局范例,Android应用程序可以在其上设计屏幕或显示表格数据。
什么是表格布局?
表格布局正是您所期望的:由行和列组成的网格,其中单元格可以显示视图控件。 从用户界面设计的角度来看,TableLayout由TableRow控件组成 - 表中每行一个。TableRow的内容只是将进入表格网格的每个“单元格”的视图控件。
TableLayout的外观由几个附加规则控制。 首先,整个表的列数与列数最多的行数相匹配。 其次,每列的宽度定义为列中最宽内容的宽度。 TableLayout的子行和单元格layout_width属性始终为MATCH_PARENT - 尽管它们可以放在XML文件中,但实际值不能被覆盖。 可以定义TableLayout的单元格layout_height,但layout_height的TableRow属性始终为WRAP_CONTENT。单元格可以跨越列,但不能跨越行。 这是通过TableRow的子视图的layout_span属性完成的。
单元格是TableRow中的单个子视图。如果您想要一个具有多个视图的更复杂的单元格,请使用布局视图来封装其他视图。 也就是说,可以修改一些规则。列可以标记为可伸缩, 这意味着宽度可以扩展到父容器的大小。
有关表格布局的完整文档,请参阅TableLayout类的Android SDK文档。XML资源中使用的关联XML属性也在文档中定义。
设计简单的表格布局
最好通过示例解释布局,并且表格布局也不例外。假设我们想要设计一个显示扩展天气预报的屏幕。表格布局可能是组织这些信息的好选择:
- 在第一个TableRow中,我们可以显示屏幕的标题。
- 在第二个TableRow中,我们可以以熟悉的类似日历的格式显示日期。
- 在第三个TableRow中,我们可以显示每日高温信息。
- 在第四个TableRow中,我们可以显示每日低温信息。
- 在第五个TableRow中,我们可以显示图形来识别天气状况,例如下雨,下雪,太阳或阴天,并有可能吃肉丸。
第一个图显示了布局编辑器中的表的早期外观:



使用表布局定义XML布局资源
设计应用程序用户界面的最方便和可维护的方式是创建XML布局资源。此方法极大地简化了UI设计过程,将大部分静态创建和用户界面控件布局以及控件属性定义移动到XML,而不是乱丢代码。
XML布局资源必须存储在/ res / layout项目目录层次结构中。我们来看看上一节介绍的表格布局。这个布局资源文件,恰当地命名为/ res / layout / table.xml,在XML中定义如下:
<?xml version="1.0" encoding="utf-8"?> <TableLayout xmlns:android="https://schemas.android.com/apk/res/android" android:id="@+id/tableLayout1" android:layout_width="match_parent" android:layout_height="match_parent" android:shrinkColumns="*" android:stretchColumns="*"> <TableRow android:id="@+id/tableRow4" android:layout_height="wrap_content" android:layout_width="match_parent" android:gravity="center_horizontal"> <TextView android:id="@+id/textView9" android:layout_width="match_parent" android:layout_height="wrap_content" android:textStyle="bold" android:typeface="serif" android:textSize="18dp" android:text="Weather Table" android:gravity="center" android:layout_span="6"></TextView> </TableRow> <TableRow android:id="@+id/tableRow1" android:layout_height="wrap_content" android:layout_width="match_parent"> <TextView android:id="@+id/TextView04" android:text=""></TextView> <TextView android:id="@+id/TextView04" android:text="Feb 7" android:textStyle="bold" android:typeface="serif"></TextView> <TextView android:id="@+id/TextView03" android:text="Feb 8" android:textStyle="bold" android:typeface="serif"></TextView> <TextView android:id="@+id/TextView02" android:text="Feb 9" android:textStyle="bold" android:typeface="serif"></TextView> <TextView android:id="@+id/TextView01" android:text="Feb 10" android:textStyle="bold" android:typeface="serif"></TextView> <TextView android:text="Feb 11" android:id="@+id/textView1" android:textStyle="bold" android:typeface="serif"></TextView> </TableRow> <TableRow android:layout_height="wrap_content" android:id="@+id/tableRow2" android:layout_width="match_parent"> <TextView android:text="Day High" android:id="@+id/textView2" android:textStyle="bold"></TextView> <TextView android:id="@+id/textView3" android:text="28°F" android:gravity="center_horizontal"></TextView> <TextView android:text="26°F" android:id="@+id/textView4" android:gravity="center_horizontal"></TextView> <TextView android:text="23°F" android:id="@+id/textView5" android:gravity="center_horizontal"></TextView> <TextView android:text="17°F" android:id="@+id/textView6" android:gravity="center_horizontal"></TextView> <TextView android:text="19°F" android:id="@+id/textView7" android:gravity="center_horizontal"></TextView> </TableRow> <TableRow android:layout_height="wrap_content" android:id="@+id/tableRow2" android:layout_width="match_parent"> <TextView android:text="Day Low" android:id="@+id/textView2" android:textStyle="bold"></TextView> <TextView android:text="15°F" android:id="@+id/textView3" android:gravity="center_horizontal"></TextView> <TextView android:text="14°F" android:id="@+id/textView4" android:gravity="center_horizontal"></TextView> <TextView android:text="3°F" android:id="@+id/textView5" android:gravity="center_horizontal"></TextView> <TextView android:text="5°F" android:id="@+id/textView6" android:gravity="center_horizontal"></TextView> <TextView android:text="6°F" android:id="@+id/textView7" android:gravity="center_horizontal"></TextView> </TableRow> <TableRow android:id="@+id/tableRow3" android:layout_height="wrap_content" android:layout_width="match_parent" android:gravity="center"> <TextView android:id="@+id/textView8" android:text="Conditions" android:textStyle="bold"></TextView> <ImageView android:id="@+id/imageView1" android:src="@drawable/hot"></ImageView> <ImageView android:id="@+id/imageView2" android:src="@drawable/pt_cloud"></ImageView> <ImageView android:id="@+id/imageView3" android:src="@drawable/snow"></ImageView> <ImageView android:id="@+id/imageView4" android:src="@drawable/lt_snow"></ImageView> <ImageView android:id="@+id/imageView5" android:src="@drawable/pt_sun"></ImageView> </TableRow> </TableLayout>
回想一下,在Activity中,只需要onCreate()方法中的一行代码就可以在屏幕上加载和显示布局资源。如果布局资源存储在/ res / layout / table.xml文件中,那么代码行将是:
setContentView(R.layout.table);
此表布局通过在值中使用“*”将其所有列设置为收缩和拉伸。如果只是某些列应该缩小或拉伸,则值将是逗号分隔列表(对列使用基于0的索引)。
在纵向和横向模式下,表格现在看起来如下截图。






以编程方式定义表布局
您还可以以编程方式在Java中创建和配置表布局。这是使用TableLayout和TableRow类(android.widget.TableLayout和android.widget.TableRow)完成的。您将在TableLayout.LayoutParams和TableRow.LayoutParams类中为每个控件找到唯一的显示参数。 此外,典型的布局参数(android.view.ViewGroup.LayoutParams),如layout_height和layout_width,以及边距参数(ViewGroup.MarginLayoutParams),仍然适用于TableLayout和TableRow对象,但不一定适用于表格单元格。 对于表格单元格(TableRow内的任何View),宽度始终为MATCH_PARENT。可以定义高度,但默认为WRAP_CONTENT,无需指定。
如前所示,如果使用setContentView()方法直接加载布局资源,如果以编程方式创建布局,则必须使用Java构建屏幕内容,然后提供包含要显示为的所有控件内容的父布局对象。子视图到setContentView()方法。 在这种情况下,您使用的父布局将是创建的表布局。
例如,以下代码说明如何以编程方式让Activity实例化TableLayout布局参数并重现以前在XML中显示的示例:
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TableLayout table = new TableLayout(this); table.setStretchAllColumns(true); table.setShrinkAllColumns(true); TableRow rowTitle = new TableRow(this); rowTitle.setGravity(Gravity.CENTER_HORIZONTAL); TableRow rowDayLabels = new TableRow(this); TableRow rowHighs = new TableRow(this); TableRow rowLows = new TableRow(this); TableRow rowConditions = new TableRow(this); rowConditions.setGravity(Gravity.CENTER); TextView empty = new TextView(this); // title column/row TextView title = new TextView(this); title.setText("Java Weather Table"); title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); title.setGravity(Gravity.CENTER); title.setTypeface(Typeface.SERIF, Typeface.BOLD); TableRow.LayoutParams params = new TableRow.LayoutParams(); params.span = 6; rowTitle.addView(title, params); // labels column TextView highsLabel = new TextView(this); highsLabel.setText("Day High"); highsLabel.setTypeface(Typeface.DEFAULT_BOLD); TextView lowsLabel = new TextView(this); lowsLabel.setText("Day Low"); lowsLabel.setTypeface(Typeface.DEFAULT_BOLD); TextView conditionsLabel = new TextView(this); conditionsLabel.setText("Conditions"); conditionsLabel.setTypeface(Typeface.DEFAULT_BOLD); rowDayLabels.addView(empty); rowHighs.addView(highsLabel); rowLows.addView(lowsLabel); rowConditions.addView(conditionsLabel); // day 1 column TextView day1Label = new TextView(this); day1Label.setText("Feb 7"); day1Label.setTypeface(Typeface.SERIF, Typeface.BOLD); TextView day1High = new TextView(this); day1High.setText("28°F"); day1High.setGravity(Gravity.CENTER_HORIZONTAL); TextView day1Low = new TextView(this); day1Low.setText("15°F"); day1Low.setGravity(Gravity.CENTER_HORIZONTAL); ImageView day1Conditions = new ImageView(this); day1Conditions.setImageResource(R.drawable.hot); rowDayLabels.addView(day1Label); rowHighs.addView(day1High); rowLows.addView(day1Low); rowConditions.addView(day1Conditions); // day2 column TextView day2Label = new TextView(this); day2Label.setText("Feb 8"); day2Label.setTypeface(Typeface.SERIF, Typeface.BOLD); TextView day2High = new TextView(this); day2High.setText("26°F"); day2High.setGravity(Gravity.CENTER_HORIZONTAL); TextView day2Low = new TextView(this); day2Low.setText("14°F"); day2Low.setGravity(Gravity.CENTER_HORIZONTAL); ImageView day2Conditions = new ImageView(this); day2Conditions.setImageResource(R.drawable.pt_cloud); rowDayLabels.addView(day2Label); rowHighs.addView(day2High); rowLows.addView(day2Low); rowConditions.addView(day2Conditions); // day3 column TextView day3Label = new TextView(this); day3Label.setText("Feb 9"); day3Label.setTypeface(Typeface.SERIF, Typeface.BOLD); TextView day3High = new TextView(this); day3High.setText("23°F"); day3High.setGravity(Gravity.CENTER_HORIZONTAL); TextView day3Low = new TextView(this); day3Low.setText("3°F"); day3Low.setGravity(Gravity.CENTER_HORIZONTAL); ImageView day3Conditions = new ImageView(this); day3Conditions.setImageResource(R.drawable.snow); rowDayLabels.addView(day3Label); rowHighs.addView(day3High); rowLows.addView(day3Low); rowConditions.addView(day3Conditions); // day4 column TextView day4Label = new TextView(this); day4Label.setText("Feb 10"); day4Label.setTypeface(Typeface.SERIF, Typeface.BOLD); TextView day4High = new TextView(this); day4High.setText("17°F"); day4High.setGravity(Gravity.CENTER_HORIZONTAL); TextView day4Low = new TextView(this); day4Low.setText("5°F"); day4Low.setGravity(Gravity.CENTER_HORIZONTAL); ImageView day4Conditions = new ImageView(this); day4Conditions.setImageResource(R.drawable.lt_snow); rowDayLabels.addView(day4Label); rowHighs.addView(day4High); rowLows.addView(day4Low); rowConditions.addView(day4Conditions); // day5 column TextView day5Label = new TextView(this); day5Label.setText("Feb 11"); day5Label.setTypeface(Typeface.SERIF, Typeface.BOLD); TextView day5High = new TextView(this); day5High.setText("19°F"); day5High.setGravity(Gravity.CENTER_HORIZONTAL); TextView day5Low = new TextView(this); day5Low.setText("6°F"); day5Low.setGravity(Gravity.CENTER_HORIZONTAL); ImageView day5Conditions = new ImageView(this); day5Conditions.setImageResource(R.drawable.pt_sun); rowDayLabels.addView(day5Label); rowHighs.addView(day5High); rowLows.addView(day5Low); rowConditions.addView(day5Conditions); table.addView(rowTitle); table.addView(rowDayLabels); table.addView(rowHighs); table.addView(rowLows); table.addView(rowConditions); setContentView(table); }
让我们仔细看一下上面的Java代码。 首先,我们使用setStretchAllColumns()和setShrinkAllColumns()方法创建TableLayout控件并将所有列的shrinkable和stretchchable属性设置为true。 接下来,我们系统地创建五个TableRow。每个TableRow将包含视图控件(用于标题,日期,高和低数据的TextView控件以及用于天气条件图形的ImageView控件)。 您将看到如何使用第一个TableRow处理列跨度。使用addView()方法按顺序创建,设置和添加特定的视图列到相应的TableRow。 每个TableRow使用TableLayout的addView()方法按顺序添加到TableLayout控件中。最后,我们加载TableLayout并使用setContentView()方法将其显示在屏幕上。
正如您所看到的,随着更多控件添加到屏幕上,代码的大小可能会迅速增加。对于组织和可维护性,以编程方式定义和使用布局最好留给奇数情况而不是常规。 此外,在这种情况下,数据通常来自我们输入的字符串之外的其他来源,因此循环可能更适合于许多应用程序。
结果如下图所示。如您所见,它们与之前的结果相同 - 正如预期的那样。



TableLayout关注
尽管表格布局可用于设计整个用户界面,但它们通常不是这样做的最佳工具,因为它们来自LinearLayout而不是最有效的布局控件。如果你考虑一下,TableLayout只不过是一组有组织的嵌套LinearLayouts,而且对于性能问题,通常不鼓励嵌套布局太深。 但是,对于已经采用适合表格格式的数据(例如电子表格数据),表格布局可能是一个合理的选择。
此外,表格布局数据可能会根据屏幕尺寸和分辨率而有所不同。确保在显示大量数据时启用滚动通常是一种很好的设计实践。 例如,如果之前使用的天气示例还包括条件的“记录”,则该文本可能是一个句子或二十个句子,因此启用垂直和/或水平滚动将是谨慎的。
结论
Android应用程序用户界面是使用布局定义的,表格布局非常方便用于在行和列中显示视图数据或控件。使用适当的表格布局可以使许多屏幕设计更简单,更快捷。 但是,请记住,TableLayout派生自LinearLayout,并且具有许多相同的性能限制。
关于作者
移动开发者Lauren Darcey和Shane Conder共同撰写了几本有关Android开发的书籍:一本深入的编程书籍,标题为Android无线应用程序开发和Sams TeachYourself Android应用程序开发24小时。 不写时,他们花时间在公司开发移动软件并提供咨询服务。他们可以通过电子邮件到androidwirelessdev+mt@gmail.com,通过他们的博客在androidbook.blogspot.com和Twitter @androidwireless上联系。