Android 7编程入门经典:使用Android Studio 2(第4版)
上QQ阅读APP看书,第一时间看更新

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>