九、设计安卓应用

到目前为止,我们一直在使用标准的安卓主题和样式。从一致性的角度来看,这是一件非常好的事情,因为应用将与设备的主题(如果有的话)适当融合。但是,有时您需要能够定义自己的样式。这种样式可能只适用于单个小部件,也可能适用于整个应用。在上述任何一种情况下,你都需要知道你可以从安卓获得什么工具,以便决定如何最好地解决手头的问题。

样式不仅仅是让你的应用看起来很好。还有,你觉得好看的,另一个人可能会讨厌。这也是为了让应用对用户更有用。这可能包括确保无论用户选择哪种语言,您的应用看起来都是正确的。它可能需要为一些选定的小部件添加额外的颜色,也可能只需要为一些关键屏幕实现横向布局。

在前一章中,我们研究了在设计应用的某些屏幕时可以做出的总体选择。本章还探讨了将 WebView用作内容和小部件容器的想法。使用WebView的优势之一是你可以随意使用 CSS。正如任何网络开发人员都会告诉你的,使用 CSS 使得高级样式变得非常容易。然而,安卓也有一系列内置的造型工具,能够实现许多与 CSS 相同的效果,在某些情况下还能做得更多。

让屏幕上的一个按钮看起来与众不同会让它在所有其他小部件中脱颖而出。这有助于让人们注意到这样一个事实,即它在屏幕上做的事情不同于其他任何事情;它做一些特别的事情。您可能还希望在两组小部件之间有一条渲染线,以便通知用户它们有一个逻辑分隔。就像试图理解别人的源代码一样,掌握一个新的应用就是理解别人的逻辑。正确地设计你的应用可以帮助用户理解你在构建应用时的想法,同时也为他们提供关于他们应该做什么的提示。如果您需要提供如何使用该应用的说明,那么您在设计和设计该应用时就失败了。

在这一章中,我们将探索安卓如何让你设计它提供的各种小部件,以及如何采用你自己的风格和主题。我们还将通过一些例子来说明如何使用自定义样式来使用户更容易使用应用。我们将涵盖以下主题:

  • 定义样式资源
  • 可用于样式设计的不同类型的图形资源
  • 创建和使用九补丁图像
  • 运行时处理设备配置的变化
  • 定义可跨不同设备和屏幕移植的样式

使用样式资源

在处理安卓风格时,第一个攻击点是理解风格值是如何工作的。应用能够定义任意数量的样式,就像能够将字符串和字符串数组定义为资源一样。样式资源用于为某些用户界面元素定义一系列默认值,就像 CSS 规则可以定义样式属性一样。主要区别在于,在安卓系统中,样式可以覆盖为给定小部件类定义的任何 XML 属性。

下表给出了安卓样式资源和 CSS 样式表的快速比较。它们有许多共同的特征,但行为却大不相同。

|

安卓风格资源

|

CSS 样式表

| | --- | --- | | 可以应用于任何 XML 属性 | 拥有一组他们可以定义或更改的特定属性 | | 可能继承自父样式 | 按照定义的顺序层叠在一起,形成复杂的样式 | | 必须明确应用于ViewActivityApplication | 通过选择器与文档元素相匹配 | | 被定义为普通的 XML | 使用专门的语法定义 |

安卓风格级联的方式类似于 CSS 规则。然而,这种级联的定义更多地归功于 Java 类层次结构。每个样式可以声明一个父样式,它将从该父样式继承参数。继承后,这些参数可能会被新样式选择性地覆盖。有一个父样式总是一个好主意,因为设备制造商可能已经修改了默认值,允许您继续适应安装在用户设备上的第一方软件,同时创建自己的新样式。

样式声明不能简单地覆盖所有可用TextView对象的样式。相反,您必须导入小部件声明中指定小部件的样式,或者将清单文件中的样式作为主题引用,以便应用于单个Activity或整个应用。首先,我们将专注于简单地构建样式并将它们应用到单个小部件上。

像维度、字符串和字符串数组一样,样式也是价值资源。创建样式元素时,您可以将其放在 res/values目录中的任何 XML 文件中(尽管最好将您的资源分开,并将样式放在styles.xml文件中)。像values目录中的所有 XML 资源一样,根元素应该是<resources>,之后您将列出您的<style>元素。以下是一个简单的样式,可用于将任何TextView样式化为标题:

<resources>
    <style name="TitleStyle" parent="@android:style/TextAppearance">
        <item name="android:textSize">25dip</item>
        <item name="android:textColor">#ffffffff</item>
        <item name="android:textStyle">bold</item>
        <item name="android:gravity">center</item>
    </style>
</resources>

上述 <style>元素中的名称属性是必需的,而父属性可选地决定默认项目使用哪种样式(在这种情况下,是TextView对象的默认外观)。下面的代码片段用我们上面声明的TitleStyle声明了一个TextView作为它的样式:

<TextView
    style="@style/TitleStyle"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Header"/>

请注意,在前面的示例中缺少android名称空间前缀。当资源被转换成二进制数据进行打包时,在编译时有效地应用一种风格。应用附加属性时,在<style>元素上声明的、在应用样式的小部件上不可用的任何项目都将被忽略。理论上,这允许您创建更抽象的样式,并将它们应用于许多不同的小部件。

TextView和应用的TitleStyle将呈现如下:

Working with style resources

类型

谁凌驾谁?

将样式应用于小部件、活动或应用时,了解覆盖的顺序非常重要。每个样式覆盖其父样式的样式信息(如果有),而每个小部件将覆盖应用于它的任何样式的任何样式信息。这意味着,虽然您可以将android:text样式项应用于TextView对象,但它通常不是很有用,因为TextView上的任何android:text属性都将覆盖样式中指定的值。

使用形状资源

能够改变小部件中字体的大小和颜色是非常好的,但是从根本上改变小部件的呈现方式呢?我们已经对 XML 可绘制对象做了一些工作,但是可以用它们做更多的事情。

到目前为止,使用 XML 可绘制结构所做的工作仅限于将默认图像放入设计有图像的小部件中。然而,安卓系统中的所有小部件都被设计成有图像。View类的 background属性允许你传入任何drawable资源,结合样式资源。这成为一个非常强大的工具。当一个形状资源在 Java 代码中被加载时,它作为 Drawable对象被返回。

您可用的形状在 android.graphics.drawable.shapes包中,而不是Shape类,后者是包中其他类继承的抽象类。您可以通过res/drawable目录中的 XML 文件来引用这些类。但是,与布局 XML 资源不同,形状更加有限:

  • 您不能直接访问类属性
  • 每个形状文件只能创建一个形状
  • 您不能绘制任意路径(即对角线或贝塞尔曲线)

然而,尽管有这些限制,形状还是非常有用和重要的,因为:

  • 它们会根据所连接的小部件的尺寸进行缩放
  • 这使得它们非常适合创建边框和/或背景结构
  • 它们还区分形状的轮廓和填充

形状如何表现

您可以定义的每个形状结构与其他每个形状结构的行为略有不同,不仅表现在呈现方式上,还表现在应用于它的属性上。由于形状资源的复杂程度相当有限,因此它们的使用也有些受限。

渲染线

Android 中的线条形状始终是一条水平直线,在小部件中垂直居中。之前我们在记忆游戏中使用线条形状作为占位符图像。然而,线条形状更常见的用途是作为垂直分隔符。当与ListView一起使用时,线条形状很常见。线形不允许渐变填充,因此它总是纯色(默认为黑色)。但是,线条形状允许使用 stroke元素中的全套属性。

简单的白线只需几行就可以定义,通常在ListView或类似结构中可以很好地用作分隔符。下面的代码片段就是这样一个行定义:

<shape xmlns:android="http://schemas.android.com/apk/res/android"
       android:shape="line">

    <stroke android:width="1sp" android:color="#ffffffff"/>
</shape>

行动时间——画一条虚线

安卓中定义的所有形状都允许你使用<stroke>元素来定义一个点或虚线结构,但是它最好显示在线元素上。如果我们增加线条的宽度,用两倍于间距的虚线段定义一个虚线图案,我们会得到一条看起来很像打印页面上的“剪切”或“撕裂”线的线条。这是在用户界面上制作更硬的分隔符的好方法。

  1. 在名为line.xmlres/drawable目录中创建新的形状资源 XML 文件,并在编辑器或 IDE 中打开该文件。
  2. 将文件的根元素声明为line shape :

    java <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="line">

  3. 3spwidth、白色、5spdashGap10spdashWidth声明新行的笔画元素:

    java <stroke android:width="3sp" android:color="#ffffffff" android:dashGap="5sp" android:dashWidth="10sp" />

  4. 关闭形状声明:

    java </shape>

刚刚发生了什么?

您刚刚创建的shape资源将显示一条虚线。线中虚线的间距正好是虚线长度的一半。大小是相对于用户的首选字体大小设置的,因此破折号将根据用户的偏好而增大和缩小。

以下是使用默认仿真器设置运行的这一行的屏幕截图:

What just happened?

渲染矩形

矩形是最常用的形状资源,因为View对象占据了屏幕上的矩形空间(即使它们没有使用该空间的每个像素)。矩形形状包括具有圆角的能力,其中每个角可以可选地具有不同的半径。

没有额外的样式信息,一个基本的矩形声明将呈现一个没有可见轮廓的填充黑盒。然而,矩形更适合创建轮廓,可以用来吸引对单个小部件的注意,或者将一组小部件与屏幕上的所有其他部件隔离开来。简单的白色矩形边框可以通过将以下代码片段复制到名为res/drawable/border.xml的文件中来构建:

<shape xmlns:android="http://schemas.android.com/apk/res/android"
       android:shape="rectangle">

    <stroke android:width="2dip" android:color="#ffffffff" />
    <padding android:top="8dip"
             android:left="8dip"
             android:bottom="8dip"
             android:right="8dip" />

</shape>

该形状中的填充元素将导致它所使用的任何View对象将其填充的大小增加8dip。这将阻止小部件的内容与形状资源呈现的边框相交。

行动时间-创建圆形边框

矩形也可以使其角弯曲,以形成圆角矩形。圆角矩形对于设置按钮的样式或创建外观更整洁的边框非常有用。

  1. 在名为rounded_border.xmlres/drawable目录中创建新的形状资源 XML 文件,并在编辑器或 IDE 中打开该文件。
  2. 将文件的根元素声明为rectangle shape :

    java <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">

  3. 将矩形描边设置为2dip宽,颜色为白色:

    java <stroke android:width="2dip" android:color="#ffffffff" />

  4. java <padding android:top="8dip" android:left="8dip" android:bottom="8dip" android:right="8dip" />

    的空白8dip填充矩形 5. 通过4dip :

    java <corners android:radius="4dip"/>

    弯曲拐角 6. 关闭形状声明:

    java </shape>

刚刚发生了什么?

要将刚刚创建的圆角边框应用到View对象,您有几个不同的选项,其中最简单的是将其直接应用为背景。为此,您将引用该形状,就像它是可绘制目录中的任何其他图像文件一样。之前,我们声明了一个TitleStyle并将其应用于一个以Header为内容的TextView。如果您将新的rounded_border应用到这个TextView上,布局资源中的TextView声明看起来会更像这样:

<TextView
        style="@style/TitleStyle"
 android:background="@drawable/rounded_border"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Header"/>

或者,您可以将此边框应用于TitleStyle,然后将新边框应用于分配给TitleStyle的每个小部件,这非常适合标题和标题小部件:

<style name="TitleStyle" parent="@android:style/TextAppearance">
    <item name="android:background">@drawable/rounded_border</item>
    <item name="android:textSize">25dip</item>
    <item name="android:textColor">#ffffffff</item>
    <item name="android:textStyle">bold</item>
    <item name="android:gravity">center</item>
</style>

这两种情况都会导致新小部件呈现完全相同的效果。实施决策实际上是你试图实现什么的问题。样式是保持用于相同目的的不同小部件之间通用性的最佳方式。

TextView上使用上述样式将产生一个漂亮的标题小部件,如下所示:

What just happened?

渲染椭圆

椭圆形正是它的名字所暗示的——椭圆。椭圆形的用途比矩形更受限制,除非在它上面绘制的小部件最好以圆形或椭圆形为边界,例如模拟时钟。也就是说,椭圆形,或者更确切地说,圆形是一个非常有用的形状,可以用作用户界面中的图像。一个完美的例子是一个符号,用来通知用户他们是否连接到互联网,或者一个小部件是否有效。为此目的使用椭圆形与使用位图完全相同。然而,椭圆可以根据用户的偏好进行缩放,而不会有任何质量损失,同时您需要几个不同大小的位图图像来实现类似的效果(即使这样,一些位图也需要缩放)。

如果我们想要一个椭圆形来表示一个无效的小部件(例如,当用户选择密码时,显示两个密码条目不匹配),那么最好将椭圆形涂成红色。在下面的代码片段中,我们将一个椭圆形声明为带有灰色边框和红色填充的 XML:

<shape xmlns:android="http://schemas.android.com/apk/res/android"
       android:shape="oval">

    <solid android:color="#ffff0000"/>
    <stroke android:width="1sp" android:color="#ffaaaaaa"/>
</shape>

在前一种情况下,我们使用<solid>元素将椭圆形填充为纯红色,而使用<stroke>元素将其周围用薄灰色轮廓包围。另外,请注意shape元素缺少尺寸标注。如前所述,它们的尺寸继承自它们放置的宽度,或者作为背景,或者在ImageView的情况下,作为小部件的内容。如果你想把这个椭圆形放入一个ImageView,你可以在src属性中指定它,如下所示:

<ImageView
        android:src="@drawable/oval"
        android:layout_width="8dip"
        android:layout_height="8dip"/>

前面的代码大约是验证图标位于小部件旁边的正确大小,而向上或向下缩放图标就像改变ImageView的宽度和高度一样容易。如果你使用wrap_content作为ImageView的大小,它的大小将被设置为零乘零像素,并且将有效地从屏幕上消失。

以下是同一椭圆的四种不同大小的截图,每种大小都是前一种的两倍(从左边的 8x8 dip 开始):

Rendering ovals

行动时间–对椭圆形应用渐变

上一张截图显示,虽然椭圆形看起来不错,但当被构成默认安卓工具包的渐变小部件包围时,它在视觉上不会很有吸引力。为了让小椭圆很好地适合,它需要看起来更像一个球,这需要应用一个简单的径向梯度。

  1. 在名为ball.xmlres / drawable目录中创建新的形状资源 XML 文件,并在编辑器或 IDE 中打开该文件。
  2. 将文件的根元素声明为oval :

    java <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">

  3. 不是声明一种solid颜色作为填充,而是声明一种gradient填充,以浅灰色开始,以红色结束:

    java <gradient android:type="radial" android:centerX="0.5" android:centerY="0.25" android:startColor="#ffff9999" android:endColor="#ffff0000" android:gradientRadius="8" />

  4. stroke元素中定义椭圆的浅灰色轮廓:

    java <stroke android:width="1sp" android:color="#ffaaaaaa"/>

  5. 关闭形状声明:

    java </shape>

刚刚发生了什么?

不幸的是,径向渐变的受影响半径不会随图像的其余部分缩放,当您将图像缩放到大尺寸时,会留下非常小的渐变区域。这种情况下的效果是,虽然图像的最小版本看起来很棒,但较大的版本看起来很糟糕。在写这本书的时候,没有直接的方法来解决这个限制。相反,如果你想使用径向渐变,你需要将你的椭圆形状的大小与ImageView的大小联系起来。

What just happened?

渲染圆环

环形在渲染时也是圆形的,但与椭圆形的目的截然不同。椭圆形的内容区域是轮廓空间内的所有内容,而环形的内容区域是圆形。

下图说明了这两种形状之间的逻辑差异:

Rendering rings

环形还有两个轮廓,一个在外面,另一个在里面(如上图所示)。将这一点与用渐变填充环的内容区域的能力结合起来,您就有了用于进度微调器的完美形状(默认的安卓不确定进度微调器是用环构建的)。

行动时间-渲染旋转环

默认情况下,一个形状将假设它被用作LevelListDrawable的一部分,并且可能不会出现,除非您禁用此行为。您可以通过在形状元素上将useLevel属性指定为false来实现。如果不禁用此功能,环将无法正确渲染,或者根本无法渲染。

  1. 在名为spinner.xmlres/drawable目录中创建新的形状资源 XML 文件,并在编辑器或 IDE 中打开该文件。
  2. ring shape :

    java <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="ring"

    开始文件的根元素 3. ring形状要求其相对厚度设置在shape声明上:

    java android:innerRadiusRatio="3.2" android:thicknessRatio="5.333"

  3. 关闭useLevel功能完成shape申报:

    java android:useLevel="false">

  4. 声明一个以椭圆为中心的sweep渐变:

    java <gradient android:type="sweep" android:useLevel="false" android:startColor="#ffaaffff" android:centerColor="#ff0000ff" android:centerY="0.50" android:endColor="#ff0000ff"/>

  5. 用白色细边框勾勒出【T0:

    java <stroke android:width="1sp" android:color="#ffffffff"/>

  6. 结束shape申报:

    java </shape>

刚刚发生的事情

扫描梯度是另一种形式的径向梯度。它不是从图像的中心向外延伸,而是像时钟的指针一样在一个圆圈内扫过。

左边的图像是一个填充了sweep渐变的矩形;而右侧的图像是ring形状。如你所见,这两种效果大不相同。右侧的图像是基于 Android 1.6 中用于不确定微调器的图像。

What just happened

定义图层

到目前为止,我们只将形状定义为单元素图像。可以将这些形状组合成更复杂的图像。这些图像分层组合在一起,这是一种常用的图形结构。在安卓系统中,这是通过 layer-list结构完成的。一个layer-list不是一种形状,但它是一个Drawable结构,这意味着它可以用来代替正常的位图图像。

分层图像资源并不局限于与矢量Drawable结构一起使用,例如我们已经讨论过的形状。分层的Drawable对象也可以包括位图图像的一些层,或者任何其他可以定义的Drawable类型。

对于layer-list中的每一层,您需要定义一个<item>元素。item 元素用于声明可选的元信息,如图层的 ID(可用于在 Java 代码中检索该图层的Drawable对象)。还可以在 item 元素中声明层的位置偏移或填充。虽然您可以引用一个图层作为外部Drawable资源,但是您也可以在<item>元素中内嵌Drawable对象,允许您在单个文件中组成各种不同的Drawable结构。

类型

调整图层大小

只有layer-list的第一个<item>会根据它所在的小部件来调整大小。所有其他层的大小将被调整到它们的“自然”大小。对于位图图像,这是其呈现的大小。对于一个<shape>元素,自然大小是 0x0。为了给<shape>指定一个自然大小,你需要给<shape>一个带有android:widthandroid:height属性的<size>子元素。

如果您想要一个两层的图像作为一个大的绿色按钮,您可能会声明一个灰色圆角矩形的层作为背景,另一个绿色椭圆形的层看起来像一个灯,或者在灰色背景上的球。这样的layer-list看起来类似于下面的代码片段:

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="rectangle" android:useLevel="false">
            <stroke android:width="1dip" android:color="#ffffffff" />

            <gradient android:type="linear"
                      android:angle="90"
                      android:startColor="#ffaaaaaa"
                      android:endColor="#ffcdcdcd" />

            <padding android:top="8dip"
                     android:left="8dip"
                     android:bottom="8dip"
                     android:right="8dip" />

            <corners android:radius="4dip" />
        </shape>
    </item>
    <item>
        <shape android:shape="oval" android:useLevel="false">
            <size android:width="32dip" android:height="32dip" />
            <gradient android:type="radial"
              android:centerX="0.45"
              android:centerY="0.25"
              android:startColor="#ff1a4e1a"
              android:endColor="#ff1ad049"
              android:gradientRadius="32" />
        </shape>
    </item>
</layer-list>

在前面的代码片段中,只有shape层,但是您可以通过引用<item>元素上的位图资源轻松地添加位图层,如下面的代码片段所示:

<item android:drawable="@drawable/checkmark"/>

使用九块图像进行拉伸

有时,您想要的边框不仅仅是简单的线条,例如,如果您想要添加阴影。在网页上,你通常会发现各种各样的 HTML 技巧,用来在一个框中插入八到九个图像,这样就可以在保持边框完整的情况下缩放内容。在安卓系统中,这种技术被称为“九补丁”图像,因为它由九个不同的部分组成。安卓系统中的九补丁图像在以大于原始尺寸的尺寸渲染时会被特别处理。为了将这些图像识别为特殊图像,它们有一个.9.png扩展名(并且必须是有效的PNG文件)。

九块图像将边框和背景组合在一张图像中。当内容变得对图像来说太大时,背景区域将会增长,并且图像的边界区域将会放大,从而不会留下“洞”。

从概念上来说,您可以从如下图所示的九补丁图像开始考虑:

Stretching using nine-patch images

图中的箭头表示概念上的“边界”区域,其大小将根据中心“内容”区域的大小而增加。九块图像的角将完全不受任何缩放的影响。

创建九块图像

为了创建一个九补丁图像,你需要一个像样的图像编辑应用。我个人使用 Gimp 应用(在http://www.gimp.org免费提供),尽管你可能更喜欢使用另一个应用。无论你用什么应用,都必须能写出便携网络图形 ( PNG )文件,还应该能缩放到相当极致的水平。九补丁图像中的整个数据实际上被编码到图像文件中,这意味着不需要一个 XML 文件来告诉安卓图像的哪些部分是边界区域,以及哪些部分不能受到缩放的影响。

与网页上出现的 CSS 框不同,在安卓系统中,对九个补丁的图像进行的大小操作是最近邻缩放。最近邻缩放并没有试图以任何方式提高缩放图像的质量,像素只是变成了更大的纯色块。虽然这对于渐变内容背景来说非常有效(前提是它们不会变得太大),但它可能会导致您的图像有一些奇怪的伪像。由于目前在缩放过程中没有执行颜色插值,所以有些效果在缩放时可能看起来相当奇怪。缩放也比简单的图像复制需要更长的时间,所以在调整图像大小时请记住这一点,它可能需要比您想象的大得多。然而,这也意味着九补丁图像比那些你可能从网络上知道的要灵活得多。

以下两幅图像是同一个 32x32 像素九片图像的放大版本:

Creating nine-patch images

左侧的图像是原始的 PNG 文件,可用作九补丁图像。右侧的图像是相同的图像,其中一部分高亮显示,以显示将缩放的区域。顶部、底部左侧和右侧区域将仅水平或垂直缩放,而中心区域将被拉伸以适应内容的大小。下图是用作TextView对象背景的相同图像:

Creating nine-patch images

因此,图像左侧和顶部的黑线告诉安卓要缩放图像的哪些部分,但右侧和底部的线条意味着什么?这两行决定了小部件内容的放置位置,很像<shape>资源中的 <padding>元素。

为了掌握九补丁图像的渲染方式和缩放的可能方式,安卓在安卓软件开发工具包安装的tools目录中为您提供了一个实用程序。draw9patch实用程序将您的九个补丁渲染成各种形状和大小,并允许您在应用中使用它之前有效地调试图像。

在安卓系统中使用位图图像

图像是设计应用的主要部分。它们用于图标、边框、背景、徽标和许多其他用途。安卓尽最大努力确保您用作资源的图像在安卓设备上使用的不同类型的屏幕上尽可能好地呈现。

安卓对图像的自动处理如果远非完美。但是,有时您需要同一图像的几种不同变体,以便您的应用在所有不同的设备上都能正常运行。

处理不同的屏幕尺寸

在安卓系统中处理任何位图图像时,非常重要的一点是要考虑到您的应用将在各种不同的屏幕上运行,包括不同的大小和密度。在非常大的屏幕上工作时(例如笔记本电脑或平板电脑上的屏幕),您会希望使用比在极小的屏幕上使用的图像更大的图像。虽然九补丁图像在保持简单方面有很大的作用,但它们仍然会使用最近邻算法进行缩放,并且这可能会开始以比您预期的更大的字体显示在大屏幕上。

您可以在资源目录中提供不同大小的图像。对于每个屏幕尺寸,可以提供不同的 drawable目录。资源加载工具将自动从与当前设备配置最匹配的目录中挑选文件。您不需要这些目录中每个资源的副本,而只需要您想要提供更合适的替代目录的副本。当资源加载器试图找到一个要加载的资源文件时,它将依赖于更松散的匹配目录。

安卓可以识别与屏幕大小相关的五个重要参数。虽然您可以指定与屏幕上的确切像素数相关的参数,但这不是一个好主意,因为您不容易适应所有不同的屏幕尺寸。相反,最好坚持安卓提供的五个参数:

  • small
  • medium
  • large
  • long
  • notlong

前三个参数与屏幕大小直接相关,后两个与屏幕是“传统”(如 VGA)还是“宽”(如 WVGA)格式相关。这些参数可以各种组合混合在一起,例如:

  • /res/drawable-small/
  • /res/drawable-medium-long/
  • /res/drawable-large-notlong/

前面的例子都是有效的资源目录,可以用来覆盖正常drawable目录中的文件。您不能组合相互矛盾的参数,例如:

  • /res/drawable-small-large/
  • /res/drawable-long-notlong/

在上述情况下,您将从资源打包工具收到一个错误。无论何时使用位图图像,考虑这些尺寸参数都很重要,因为有些设备的屏幕与仿真器默认显示的屏幕非常不同。

处理不同的屏幕密度

屏幕密度一般是指打包到给定物理空间中的像素数量(即每英寸点数或 DPI )。它还与屏幕上像素的大小有关。虽然大多数安卓设备都有中密度或高密度屏幕,但大量更便宜的设备使用相对低密度的屏幕。

为什么这会影响九补丁和位图图像?影响字体渲染的原因是一样的——密度越低,抗锯齿和阴影效果越差。最好的解释是用图像。在下面的图像中,左边的是一个简单的圆角矩形,就像它会出现在高密度屏幕上一样。右侧的图像类似于同一图像在低密度屏幕上的渲染方式:

Handling different screen densities

虽然两者都是以相同的物理尺寸渲染的同一源图像,但可用像素数量的减少会使图像在低密度屏幕上看起来像块一样。

下面两张图片是从右下角拍摄的,为了更详细地说明发生了什么,它们被放大了:

Handling different screen densities

同样,这些图像被配置为占用相同的物理空间。如果图像的大小是以屏幕像素为单位的,那么在低密度屏幕上,它将占用更多的物理空间。这就是为什么建议您在安卓系统中使用“密度无关像素”(dpdip)单位而不是正常像素(px)单位来调整图像大小的原因之一。

与屏幕尺寸一样,安卓提供了一系列配置参数,可用于为不同的屏幕密度提供不同的资源。可用于选择屏幕密度的参数可以与基于屏幕尺寸选择的参数混合使用。以下是安卓根据当前设备的屏幕密度提供的资源参数列表:

  • ldpi:低密度屏幕(~120dpi)
  • mdpi:中密度屏幕(~160dpi)
  • hdpi:高密度屏幕(~260dpi)
  • nodpi:特例

最后一种“特殊情况”可以在您有一个不希望根据设备密度缩放的九补丁图像或位图图像时使用。默认情况下,安卓会重新缩放图像,试图让图像的物理尺寸尽可能接近预期尺寸。nodpi目录中的图像不会被安卓自动缩放,而是逐像素渲染。

类型

不同密度图标

有时大的高分辨率图标不能很好地缩小。在这些情况下,为低密度屏幕设计完全不同的图标通常是个好主意。

处理配置更改

当您为安卓提供与各种可能的硬件配置相关的不同资源目录时,资源加载器将尝试为您的应用运行的设备匹配最佳资源文件。然而,并非所有的配置参数都与硬件直接相关,而是描述设备状态或一些软件配置参数。这些参数类型的示例有设备语言、网络标识和设备方向。这些参数可能会在应用运行时发生变化。最常见的例子是设备方向。安卓有一个内置的机制来为你处理这样的变化,在大多数情况下,你不需要任何特殊的 Java 代码来处理这些变化。然而,强烈希望至少为其中一些参数提供资源文件。

当配置参数改变时,安卓会将你的任何Activity状态存储在一个Bundle对象中,然后关闭Activity。然后,它将使用新的配置参数启动Activity对象的新实例,并从Bundle对象恢复状态。所有默认的安卓小部件都会在你的Activity被系统关闭之前保存它们的当前状态。这意味着您通常不需要对配置更改执行任何特殊处理。

提供景观布局

到目前为止,通过这本书,我们只建立了肖像布局。与桌面或网络系统不同,移动应用的方向默认为纵向(因此配置参数为longnotlong,而不是widenarrow)。拥有安卓平台的好处之一是加速度计是一个必需的硬件,这意味着您的应用可以响应设备的方向。由于安卓的配置处理(如前所述),假设您没有用 Java 构建用户界面的主要部分,作为开发人员,除了提供替代的横向布局资源之外,您不需要做任何事情。为了提供特定于纵向或横向的布局,您可以将布局的 XML 资源的特定版本放在使用以下资源配置参数配置的目录中:

  • port:人像专用布局
  • land:景观专用布局

当屏幕垂直方向比水平方向长(即纵向方向)时,使用简单的垂直方向LinearLayout布局输入表单很有意义。您使用的任何输入小部件都将位于它们的标签下方,因此有更多的水平空间来显示它们的数据。额外的水平空间也允许标签包含更多信息。

下图说明了这两种布局概念之间的区别:

Providing landscape layouts

右边使用的布局方法在 web 或桌面系统中非常常见,如果标签和输入小部件的大小足够小,它将在移动设备上运行良好。

当切换到横向时,水平空间的急剧增加加上垂直空间的巨大损失使得垂直LinearLayout成为一个可怕的选择。如果您使用的是简单的输入表单,那么横向布局应该使用TableLayoutRelativeLayout将标签定位在与输入小部件相关的同一行上。

在横向布局上提供文本输入

构建景观布局时,您需要仔细考虑用户界面的哪些部分最重要。如果您的屏幕用于撰写电子邮件或文档,您的横向布局可能与纵向布局几乎相同。然而,这样的布局有一个最隐蔽的敌人:软件键盘。在纵向布局中,软件键盘会将其自身限制在屏幕底部,并消耗相对较少的空间(约为可用屏幕空间的四分之一至三分之一)。然而,在横向布局中,软件键盘会占用多达一半的垂直屏幕空间,因此很难构建以内容为中心的横向布局。如果你的布局是强输入驱动的,当方向为横向时,移除用户界面的一部分,重新操作用户界面,这样软件键盘就不会碍事,这可能是有意义的。

安卓确实提供了一系列配置参数,这些参数会告诉你运行应用的设备上的键盘。在构建应用时考虑所有的可能性是一个好主意。以下是您的应用可能面临的键盘情况的简短列表:

  • 仅软件键盘
  • 硬件键盘
  • 硬件键盘可用;正在使用的软件键盘

除了这些可能性之外,屏幕较小的设备通常会使用 12 键键盘,而不是完整的 QWERTY 键盘。如果这是一个软件键盘(通常如此),键盘可能会占用多达 80%的可用屏幕空间。如果安卓系统在用户激活文本输入框时打开“文本输入”屏幕,通常会出现这个问题。您可以使用以下配置参数来确定键盘可用性的不同状态以及所使用的键盘类型:

  • nokeys:仅软件键盘
  • qwerty:全硬件键盘可用
  • 12key:有 12 键硬件手机键盘
  • keysexposed:无论是硬件还是软件,用户都有一个可见的键盘
  • keyshidden:当前没有任何键盘可见
  • keyssoft:用户将使用软件键盘(虽然可能看不到)

设计屏幕时,请考虑软键盘可能会占用一半的垂直空间。确保内容区域将滚动,而重要的小部件将始终在屏幕上可见。如果一个聊天应用简单地包装在ScrollView中,当软件键盘可见时,输入的EditView对象可能会变得不可见。重要的不仅仅是要考虑屏幕的外观,还要考虑它将如何应对用户对它的改变。最后,不管有没有软件键盘,测试屏幕的外观和行为都是至关重要的。

改变屏幕内容

安卓 XML 布局格式的一大优势是它提供的解耦。纵向和横向布局通常有很大的不同,用户可能会各自找到使用应用的首选方向。设计新布局时,一个不太常见但有用的技巧是从两个不同的布局中添加或移除“非功能性”元素的能力。

在一个简单的示例中,您可能希望在纵向布局的标签中缩写文本,并包含一些图标作为图形提示,而在横向布局中,您可能希望图标的大小加倍,并包含两行标签,所有这些都与您的输入字段位于同一行。

下图说明了这一概念:

Altering screen content

在上图的横向布局中,您可以为标签上的子文本使用额外的TextView元素。假设您的 Java 代码没有寻找额外的TextView对象,您的应用将完美运行。在为 T2 设计替代布局时,改变用户界面的实际结构而不仅仅是布局的能力是一个非常重要的考虑因素。

总结

应用的外观和感觉至关重要。颜色或字体的一次改变就能决定屏幕的可用性。同时,过度设计应用会让它在用户的设备上感觉不合适。陌生的外观和感觉会让用户远离应用,转向那些看起来更熟悉、感觉更舒适的应用。

安卓通过样式资源结构提供了一套极其强大的功能。当与将图形放入资源文件并覆盖默认值的能力相结合时,您可以有效地重新设计任何小部件的样式。使用样式也有助于应用的维护,因为您只需要在样式资源中更改样式,而不需要在特定样式的每个小部件声明中进行更改。

将您的大部分小部件图形作为<shape>资源保存将确保您的应用具有最一致的外观和感觉。然而,这并不总是切实可行的。当您需要提供位图资源时,为用户可能使用的各种屏幕大小和密度提供不同的图像至关重要。

应用的样式还包括布局以及应用适应其运行设备的能力。拥有一个伟大的想法只是应用吸引力的一半,它的样式和执行对于它在“野外”的生存至关重要。关注细节是一个强大的工具,可以吸引用户使用你的应用。“正常工作”的应用总是比那些需要花费时间和精力的应用更受青睐。

利用安卓模拟器提供给你的各种屏幕尺寸和密度,确保你的应用在尽可能多的设备上看起来都很好。不要忘记,许多设备没有硬件键盘,软件键盘可以占用多达一半的屏幕空间。

在下一章中,我们将把这些造型知识扩展到应用的整体设计和主题中。我们将使用许多提供的布局构建一个样式化的应用,并将执行相当广泛的样式化。