
4.1 屏幕组件介绍
在第3章中已经阐述了Android应用的基本单位是Activity,它会显示应用的UI。Activity可以包含的部件有按钮、标签、文本框等。通常,你会使用XML文件定义UI(例如,在项目中res/layout文件夹里面的activity_main.xml文件),类似于以下代码:
<? xml version="1.0" encoding="utf-8"? > <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:context="com.jfdimarzio.helloworld.MainActivity"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="? attr/actionBarSize" android:background="? attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay" /> </android.support.design.widget.AppBarLayout> <include layout="@layout/content_main" /> <android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="@dimen/fab_margin" android:src="@android:drawable/ic_dialog_email" /> </android.support.design.widget.CoordinatorLayout>
在运行过程中,需要在Activity类的onCreate()方法处理程序中调用setContentView()方法加载XML定义的UI:
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); }
在编译过程中,在XML文件中的每一个元素会被编译成对应的AndroidGUI(图形用户接口)类,方法代表相应的属性。然后当Activity被加载时Android系统会创建Activity的UI。
注意:虽然使用XML文件创建UI非常简单,但有时还是需要在运行时动态地创建UI(例如,编写游戏时)。因此,使用代码的方式创建整个UI也是有可能的。在这一章的后面会有一个示例展示如何使用代码创建UI。
4.1.1 视图和ViewGroup
一个Activity对象包含视图和ViewGroup。视图是一个能在屏幕上显示外形的部件。它包括按钮、标签和文本框。视图是由基类android.view.View派生而来的。
注意:在第5章和第6章中会讨论Android中的各种常用的视图。
一个或多个视图可以组织在一个ViewGroup中。ViewGroup(它本身也是一个特殊类型的视图)提供布局的功能,可以在ViewGroup中控制视图的显示样式和顺序。它包括RadioGroup和ScrollView。ViewGroup是由基类android.view.ViewGroup派生而来的。
另外一种ViewGroup的类型是Layout。Layout是由基类android.view.ViewGroup派生而来的另一种容器,用来包含其他视图。然而,ViewGroup的目的是逻辑性地把视图组织在一起——比如一组具有相同功能的按钮——Layout是用来在屏幕上视觉化地组合和安排视图的。在Android中有以下Layout类型:
● FrameLayout
● LinearLayout(水平的)
● LinearLayout(垂直的)
● TableLayout
● TableRow
● GridLayout
● RelativeLayout
接下来会详细地讲解每一个Layout。
注意:Android开发中一个非常有用的特性是,可以自由地混合和匹配多个Layout来为你的应用创建独一无二的控制布局。
4.1.2 FrameLayout
FrameLayout是Android中非常基础的layout。设计它用来包含一个视图。对于开发而言,没有硬性规定FrameLayout不能包含多个视图。但这里有一个原因可以说明为什么FrameLayout被这样设计。
现在有无数的屏幕尺寸和分辨率,你无法控制安装应用的设备的硬件配置。因此,当你的应用为了适应各种不同的设备而调整大小和重新布局时,应该尽量确保它和原本的设计相类似。
当屏幕尺寸发生变化时,FrameLayout用来帮助你控制单个视图的布局。在以下的“试一试”中,将会在HelloWorld应用中向FrameLayout添加一个TextView。
试一试:在FrameLayout中添加TextView
使用在第1章和第2章中创建的HelloWorld项目,在项目中创建一个新的布局资源文件,在布局文件中添加一个FrameLayout,最后在FrmeLayout中添加一个TextView。
(1) 在Android Studio中打开HelloWorld项目。
(2) 右击res/layout文件夹并且添加一个布局资源文件。命名为framelayout_example.xml。
(3) 使用design panel,把FrameLayout拖曳到设备屏幕中。
(4) 使用design panel,把Plain TextView拖曳到FrameLayout中。
(5) 在Plain TextView中输入一些文本。
示例说明
FrameLayout以自由浮动的方式显示Plain TextView。FrameLayout原本的目的是包含一个元素。但是,它也可以包含多个元素。
4.1.3 LinearLayout(水平)和LinearLayout(垂直)
LinearLayout把视图排列在单列或者单行中。子视图被水平排列或者垂直排列,这也是为什么需要两个不同的layout——一个用来把视图水平地排列成行,一个用来把视图垂直地排列成列。
注意:LinearLayout (水平)和LinearLayout(垂直)实际上是具有不同属性的同一个layout。在这一小节的后面,当应用中有一个水平或者垂直的布局时,你会发现LinearLayout是如何控制android:orientation属性的。
想要知道LinearLayout的工作原理,请思考以下通常在activity_main.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:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> </LinearLayout>
在activity_main.xml文件中,通过观察可以发现,根元素是<LinearLayout>并且它包含一个<TextView>元素。<LinearLayout>元素控制它所包含的视图的显示顺序。
每一个视图和ViewGroup都有一组常用属性,表4-1列出了部分属性。
表4-1 视图和ViewGroup中的常用属性

注意:其中一些属性只有当视图在特定的ViewGroup中才有效。例如,只有当视图在LinearLayout或TabletLayout中时,layout_weight和layout_gravity属性才有效。
例如,<TextView>元素的宽度设置为fill_parent常量,它的宽度占用了整个父容器的宽度(本示例中为整个屏幕宽度)。它的高度设置为wrap_content常量,意思是它的高度等于它内容的高度(本示例中为它内部的文本高度)。如果不希望<TextView>视图占用整个行的尺寸,可以将layout_width属性的值设为wrap_content,代码如下:
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/hello" />
以上代码将视图的宽度设置为其内部文本的宽度。思考下列代码中的布局,其中两个视图的宽度都显式地设置为一个数值,并且它们的高度都设置为它们内容的高度。
<?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:layout_width="100dp" android:layout_height="wrap_content" android:text="@string/hello" /> <Button android:layout_width="160dp" android:layout_height="wrap_content" android:text="Button" android:onClick="onClick" /> </LinearLayout>
测量单位:
当为Android UI中的元素指定尺寸时,应该了解以下测量单位:
● dp——与密度无关的像素。1dp在一个160dpi的屏幕上等于1像素。当为布局中的视图设置尺寸时推荐使用这个测量单位。
● sp——与缩放无关的像素。该单位与dp相似,并且建议设置字体尺寸时使用。
● pt——点。一个点被定义为1/72英寸,与物理屏幕的尺寸有关。
● px——像素。与屏幕上的实际像素一一对应。不建议使用这个测量单位,因为在不同屏幕分辨率的设备上,UI的显示会发生错误。
将TextView和Button这两个视图的宽度都设置为一个绝对值。这种情况下,TextView的宽度被设置为100密度无关像素,Button的宽度为160密度无关像素。在观察视图在具有不同像素密度的屏幕上如何显示之前,了解Android如何识别屏幕的尺寸和像素密度是非常重要的。
图4-1展示了Nexus 5的屏幕(来自于模拟器的截图)。它有一个五英寸的屏幕(对角线尺寸),屏幕的宽度为2.72英寸。它的分辨率为1080(宽)×1920(高)像素。像素密度根据屏幕的尺寸和分辨率变化。
要测试在XML文件中定义的视图在不同密度的屏幕上如何显示,先创建两个Android虚拟设备(AVD)使用不同的屏幕分辨率和抽象的LCD密度。图4-2展示的是分辨率为1080×1920、LCD密度为480的AVD。
图4-3展示的是分辨率为768×1280、LCD密度为320的AVD。

图4-1

图4-2

图4-3
如何换算dp与px:
从dp换算到px的公式如下:
实际像素px = dp * (dpi / 160),其中dpi的值可以为120、160、240或者320。
因此,在这个示例中Button在235dpi的屏幕上,它的实际宽度为160 * (240/160) = 240 px。当运行在180dpi的模拟器上(当作160dpi的设备),它的实际像素尺寸是160 * (160/160) =160px。这时,1dp相当于1px。
要证明这个公式的正确性,可以调用View对象的getWidth()方法得到它的像素宽度:
public void onClick(View view) { Toast.makeText(this, String.valueOf(view.getWidth()), Toast.LENGTH_LONG).show(); }
如果不使用dp而使用像素(px),指定尺寸会怎么样?
<TextView android:layout_width="100px" android:layout_height="wrap_content" android:text="@string/hello" /> <Button android:layout_width="160px" android:layout_height="wrap_content" android:text="Click Me" android:onClick="onClick"/>
图4-4展示了Label和Button在480dpi屏幕上的显示效果。图4-5展示了同样的视图在320dpi屏幕上的效果。在这种情况下,Android没有进行任何转换操作,因为所有的尺寸都是使用像素指定的。如果使用像素指定视图尺寸,视图在高dpi的屏幕上会比它在低dpi的屏幕上显示得小一些(假设屏幕的尺寸相同)。

图4-4

图4-5
上一个示例同时也指定了布局的方向为垂直方向:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" >
默认的布局方向为水平方向,所以如果你省略了android:orientation属性,视图就会如图4-6那样显示。

图4-6
在LinearLayout中,可以设置它所包含的视图的layout_weight和layout_gravity属性,修改后的activity_main.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" > <Button android:layout_width="160dp" android:layout_height="0dp" android:text="Button" android:layout_gravity="left" android:layout_weight="1" /> <Button android:layout_width="160dp" android:layout_height="0dp" android:text="Button" android:layout_gravity="center" android:layout_weight="2" /> <Button android:layout_width="160dp" android:layout_height="0dp" android:text="Button" android:layout_gravity="right" android:layout_weight="3" /> </LinearLayout>
图4-7展示了视图的位置和高度。layout_gravity属性标明视图应该如何放置,而layout_weight属性指定了如何分配可使用空间。在上一个示例中,三个按钮分别占用了16.6%(1/(1+2+3)*100)、33.3%(2/(1+2+3)*100)和50%(3/(1+2+3)*100)的可使用高度。
注意:每一个按钮的高度是设置为0dp,因为布局的方向是垂直方向。
如果将LinearLayout的方向修改为水平方向(如以下代码所示),则需要把每一个视图的宽度都改为0dp。视图就会如图4-8那样显示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal" > <Button android:layout_width="0dp" android:layout_height="wrap_content" android:text="Button" android:layout_gravity="left" android:layout_weight="1" /> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:text="Button" android:layout_gravity="center_horizontal" android:layout_weight="2" /> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:text="Button" android:layout_gravity="right" android:layout_weight="3" /> </LinearLayout>

图4-7

图4-8
在下面的“试一试”中,将会组合多个LinearLayout并创建一个L型配置的视图。
试一试:组合两个不同方向的LinearLayout
在配置文件中使用两个LinearLayout,一个设置为垂直方向并在里面放置三个TextView,另一个设置为水平方向并在里面放置三个Button,如图4-9所示。

图4-9
(1) 创建一个新的布局资源文件,命名为linearlayouts_example.xml。默认情况下,这个新文件将会创建一个LinearLayout(垂直)。
(2) 在LinearLayout中放置三个层叠的TextView组成一列。
(3) 在第三个TextView下面放置一个LinearLayout(水平)。
(4) 在LinearLayout(水平)中放置三个Button组成一行。
示例说明
Layout可以使用各种不同的配置组合在一起,产生出各种能想象到的外观。在这个示例中,使用了一个垂直的和一个水平的LinearLayout创建一个L型的布局。完成以后的.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:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Text 1" android:id="@+id/textView" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Text 2" android:id="@+id/textView2" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Text 3" android:id="@+id/textView3" /> <LinearLayout android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button 1" android:id="@+id/button" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button 2" android:id="@+id/button2" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button 3" android:id="@+id/button3" /> </LinearLayout> </LinearLayout>
4.1.4 TableLayout
TableLayout布局把视图组织成行和列。可以使用<TableRow>元素指定表格中的行。每一行可以包含一个或者多个视图。在行中的每个视图构成一个单元格。每一列的宽度由这一列中最大宽度的单元格确定。
思考以下activity_main.xml文件中的代码:
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="fill_parent" android:layout_width="fill_parent" > <TableRow> <TextView android:text="User Name:" android:width ="120dp" /> <EditText android:id="@+id/txtUserName" android:width="200dp" /> </TableRow> <TableRow> <TextView android:text="Password:" /> <EditText android:id="@+id/txtPassword" android:inputType="textPassword" /> </TableRow> <TableRow> <TextView /> <CheckBox android:id="@+id/chkRememberPassword" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Remember Password" /> </TableRow> <TableRow> <Button android:id="@+id/buttonSignIn" android:text="Log In" /> </TableRow> </TableLayout>
图4-10展示了以上代码在Android模拟器中的显示效果。
注意:在以上示例中,TableLayout有两列四行。显示Password的TextView正下方的单元格由一个空的<TextView/>元素创建。如果你不这么做,Remember Password复选框就会出现在Password的TextView下方,如图4-11所示。

图4-10

图4-11
4.1.5 RelativeLayout
RelativeLayout布局使你能够使用相对属性来指定其子视图的位置。思考以下activity_main.xml文件中的代码:
<? xml version="1.0" encoding="utf-8"? > <RelativeLayout android:id="@+id/RLayout" android:layout_width="fill_parent" android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android" > <TextView android:id="@+id/lblComments" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Comments" android:layout_alignParentTop="true" android:layout_alignParentStart="true" /> <EditText android:id="@+id/txtComments" android:layout_width="fill_parent" android:layout_height="170dp" android:textSize="18sp" android:layout_alignStart="@+id/lblComments" android:layout_below="@+id/lblComments" android:layout_centerHorizontal="true" /> <Button android:id="@+id/btnSave" android:layout_width="125dp" android:layout_height="wrap_content" android:text="Save" android:layout_below="@+id/txtComments" android:layout_alignEnd="@+id/txtComments" /> <Button android:id="@+id/btnCancel" android:layout_width="124dp" android:layout_height="wrap_content" android:text="Cancel" android:layout_below="@+id/txtComments" android:layout_alignStart="@+id/txtComments" /> </RelativeLayout>
注意嵌入在RelativeLayout中的每一个视图都有使它们与其他视图对齐的属性。这些属性包括:
● layout_alignParentTop
● layout_alignParentStart
● layout_alignStart
● layout_alignEnd
● layout_below
● layout_centerHorizontal
这些属性的值都是你想要对齐的视图的ID。以上XML UI创建的屏幕外观如图4-12所示。

图4-12
4.1.6 FrameLayout
FrameLayout布局如同屏幕中的占位符,可以在其中嵌入一个单独的视图。在FrameLayout中添加的视图总是被定在布局的左上角。请思考以下main.xml代码:
<? xml version="1.0" encoding="utf-8"? > <RelativeLayout android:id="@+id/RLayout" android:layout_width="fill_parent" android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android" > <TextView android:id="@+id/lblComments" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello, Android! " android:layout_alignParentTop="true" android:layout_alignParentStart="true" /> <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignStart="@+id/lblComments" android:layout_below="@+id/lblComments" android:layout_centerHorizontal="true" > <ImageView android:src="@mipmap/butterfly" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </FrameLayout> </RelativeLayout>
这里的FrameLayout被嵌入在RelativeLayout中。在FrameLayout中又嵌入了一个ImageView。UI如图4-13所示。

图4-13
注意:这个示例假设在res/mipmap-hdpi文件夹中有一张名为butterfly.png的图片。
如果在FrameLayout中添加另一个视图(比如一个Button视图),这个视图会与之前的视图重叠(如图4-14所示)。
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout android:id="@+id/RLayout" android:layout_width="fill_parent" android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android" > <TextView android:id="@+id/lblComments" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello, Android! " android:layout_alignParentTop="true" android:layout_alignParentStart="true" /> <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignStart="@+id/lblComments" android:layout_below="@+id/lblComments" android:layout_centerHorizontal="true" > <ImageView android:src="@mipmap/butterfly" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:layout_width="124dp" android:layout_height="wrap_content" android:text="Print Picture" /> </FrameLayout>

图4-14
注意:可以在FrameLayout中添加多个视图,但是每一个视图都会堆叠到前一个视图上。当想要使用一系列的图片做一个动画,一次只能显示一张图片时,可以使用这种方法。
4.1.7 ScrollView
ScrollView是一种特殊的FrameLayout,当其中的视图所占的空间大于物理显示尺寸时,它允许用户在其中滚动显示。ScrollView只能包含一个子视图或者一个ViewGroup,通常是一个LinearLayout。
注意:不要将ListView(将在第5章中讨论)和ScrollView一起使用。ListView被设计用来显示一个相关信息列表,它对于处理大型列表做过特殊优化。
以下main.xml内容显示一个ScrollView包含了一个LinearLayout,在LinearLayout中包含了一些Button和EditText视图:
<ScrollView android:layout_width="fill_parent" android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android" > <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" > <Button android:id="@+id/button1" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 1" /> <Button android:id="@+id/button2" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 2" /> <Button android:id="@+id/button3" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 3" /> <EditText android:id="@+id/txt" android:layout_width="fill_parent" android:layout_height="600dp" /> <Button android:id="@+id/button4" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 4" /> <Button android:id="@+id/button5" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 5" /> </LinearLayout> </ScrollView>
如果在Android模拟器中加载以上代码,会看到如图4-15所示的界面。

图4-15
由于EditText会自动得到焦点,它占据了整个Activity(因为高度被设置为600dp)。为了阻止它得到焦点,将下列代码中的两个粗体属性添加到<LinearLayout>元素中:
<LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" android:focusable="true" android:focusableInTouchMode="true" >
现在你可以看到按钮了,并且能够在view列表中滚动显示(如图4-16所示)。

图4-16
有时你也许希望EditText能够自动得到焦点,但是不希望软输入面板(键盘)自动出现(这种情况会在真实设备上发生)。为了避免软键盘出现,在AndroidManifest.xml文件的<activity>元素中添加以下粗体属性:
<activity
android:label="@string/app_name"
android:name=".LayoutsActivity"
android:windowSoftInputMode="stateHidden" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>