2.4 创建卡片视图

卡片视图是最容易识别的Material Design组件之一。它旨在显示单个主题的几段内容,这些内容通常是图形、文本、动作按钮和图标的组合。卡片是用统一的方式呈现选择的好方法,所以使用卡片来展示三明治原料和相关信息(如价格或热值)是一个不错的选择。我们将使用上一章中的工厂模式来实现。但是在了解如何修改代码之前,让我们先看看如何实现卡片视图。

2.4.1 了解卡片视图的属性

如果你的最低目标SDK是21或更高版本,那么标准小部件中已经包含了CardView。否则,需要添加cardview支持库;通过在build.gradle文件中添加以下代码,可以轻松地添加。

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        testCompile 'junit:junit:4.12'
        compile 'com.android.support:appcompat-v7:23.4.0'
        compile 'com.android.support:cardview-v7:23.4.0'
    }

正如支持库的名称所示,卡片视图只支持API 7以上的级别。

了解如何编辑build.gradle十分有用,但我们不需要手动编辑build.gradle文件,因为它可以通过File|Project Structure...(文件|项目结构...)菜单,选择如图2-7所示的项目来实现。

图2-7

一些开发者使用+号代替支持库的版本号,如com.android.support:cardview-v7:23.+。这是对支持库未来的预期+号是动态依赖,支持库会自动更新。——译者注。这样写通常没什么问题,但是不能保证应用程序不会在以后发生崩溃。在开发过程中使用已编译的SDK版本,然后在发布后定期更新应用程序,这样做虽然更费时,但是更明智。

在将卡片视图添加到布局之前,需要重建项目。首先,需要设置卡片的一些属性。打开res/values/dimens.xml文件并添加以下三个新尺寸:

    <dimen name="card_height">200dp</dimen>
    <dimen name="card_corner_radius">4dp</dimen>
    <dimen name="card_elevation">2dp</dimen>

现在,可以把卡片作为一个小部件加入主活动的XML文件中:

    <android.support.v7.widget.CardView
        xmlns:card_view="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="@dimen/card_height"
        android:layout_gravity="center"
        card_view:cardCornerRadius="@dimen/card_corner_radius"
        card_view:cardElevation="@dimen/card_elevation">
    </android.support.v7.widget.CardView>

使用阴影,不仅让界面拥有一个三维外观,还可以图形化显示布局的层次结构,让用户明白哪些功能可用。

如果你花了一些时间查阅卡片视图的属性,就会注意到translationZ属性,似乎这个属性与elevation属性有同样的效果。但是,elevation属性会设置卡片的绝对高度,而translationZ的设置是相对的,其值将从当前的高度中增加或减少。

我们已经创建了一个卡片视图,可以参考Material Design指南填写属性,使用它显示我们的三明治原料。

2.4.2 应用CardView参数

设计指南对字体、内边距和比例等问题做了非常清晰的指导。一旦开始使用Coordinator-Layout,很多配置将自动设置。但现在,我们最好看一下这些参数是如何使用的。

这里,我们将创建一个视图,包含一幅图像、三个文本项和一个动作按钮。卡片可以被视为容器对象,因此通常包含根布局。根布局可以直接放在卡片视图内部,但是如果将卡片内容创建为单独的XML布局,可以使代码可读性更强且更加灵活。

下一个练习至少需要一幅图像。依据Material Design,拍摄的图像应该清晰、明亮、简单,并呈现出单一、清晰的主题。例如,如果我们想在菜单中添加咖啡,图2-8中左边的图像更合适。

图2-8

卡片的图像宽高比需要为16∶9或1∶1。这里,我们将使用16∶9的图像。理想情况下,我们应该生成适合不同屏幕密度的缩放版本,但由于这只是一个演示,我们可以偷懒,将原图直接放入drawable文件夹即可。这种方法远不是最佳实践,但对于初步测试来说是可以的。

获取并保存图像后,下一步是为卡片创建一个布局。

(1)从项目资源管理器中,选择New |XML |Layout XML File(新建|XML|XML布局文件),布局名为card_content.xml。它的根布局应该是垂直方向的线性布局,代码应该如下所示。

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

(2)使用图形编辑器或文本编辑器,创建的布局结构需要匹配如图2-9所示的组件树。

图2-9

(3)现在,在主活动的布局文件中,把上述布局添加到卡片视图中,如下所示。

    <android.support.v7.widget.CardView
        android:id="@+id/card_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <include
            android:id="@+id/card_content"
            layout="@layout/card_content" />
    </android.support.v7.widget.CardView>

虽然elevation属性值可以编辑,但是建议将卡片视图的elevation属性设置为2dp,除非它被选中或正在移动,这种情况下,建议将elevation属性设置为8dp。

毫无疑问,强烈建议XML资源中的字符串不要使用硬编码。不出意外,使用硬编码会使得应用程序无法被翻译成其他语言。但是,在布局设计的早期阶段,使用一些占位符值有助于了解布局的外观。稍后,我们将使用Java来控制卡片的内容,并根据用户输入修改内容,但现在,我们将选择一些典型的值,以便轻松且快速地查看设置的效果。为了查看如何进行上述操作,请将以下属性或等效属性添加到values目录下的strings.xml文件中。

    <string name="filling">Cheddar Cheese</string>
    <string name="filling_description">A ripe and creamy cheddar from the south
    west</string>
    <string name="calories">237 kcal per slice</string>
    <string name="action">ADD</string>
    <string name="alternative_text">A picture of some cheddar cheese</string>

现在,使用这些占位符来评估我们所有的改动。我们刚刚创建的布局的预览图应如图2-10所示。

图2-10

要将其转换为Material Design形式的组件,只需了解Material Design设计的一些格式和知识。

此布局的参数应该如下所示:

❑ 图像的比例必须是16∶9;

❑ 标题文本应为24 sp;

❑ 描述性文字是16 sp;

❑ 文本右下角和左下角的边距为16dp;

❑ 标题文本上方的边距为24dp;

❑ 动作文本为24 sp,并且颜色从主色中选取。

通过属性面板或者直接编辑XML,可以轻松地设置这些属性。因为有一些事情没有提及,所以单独查看一下每个元素是很值得的。

首先,必须指出的是,这些值不应该在代码中逐字描述(像下面的代码片段中那样)。例如,android:paddingstart="24dp",应该像android:paddingstart="@dimen/text_paddingstart"这样进行编码。在dimens.xml文件中定义text_paddingstart。此处这些值使用硬编码,只是为了简化说明。

顶部的图像视图代码应如下所示。

    <ImageView
        android:id="@+id/image_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:contentDescription="@string/alternative_text"
        android:src="@drawable/cheddar" />

代码非常直观,不过需要注意contentDescription属性的使用。视力受损的用户设置了可访问性选项后,可以通过设备的语音合成器读取图像的描述(contentDescription属性)来欣赏图像。

以下是三个文本视图。

    <TextView
        android:id="@+id/text_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingEnd="24dp"
        android:paddingStart="24dp"
        android:paddingTop="24dp"
        android:text="@string/filling"
        android:textAppearance="? android:attr/textAppearanceLarge"
        android:textSize="24sp" />
    <TextView
        android:id="@+id/text_description"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingEnd="24dp"
        android:paddingStart="24dp"
        android:text="@string/filling_description"
        android:textAppearance="? android:attr/textAppearanceMedium"
        android:textSize="14sp" />
    <TextView
        android:id="@+id/text_calories"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end"
        android:paddingBottom="8dp"
        android:paddingStart="16dp"
        android:paddingEnd="16dp"
        android:paddingTop="16dp"
        android:text="@string/calories"
        android:textAppearance="? android:attr/textAppearanceMedium"
        android:textSize="14sp" />

这些代码也很容易理解。真正需要指出的是,使用Start和End,而不是Left和Right来定义padding和gravity,因为这有助于在把文本翻译成从右到左运行的语言时的布局适配。代码中还包含了textAppearance属性,因为直接设置了文本的大小,所以这个属性可能看起来是多余的。但textAppearanceMedium等属性非常有用,因为它们不仅会根据自定义主题自动应用文本颜色,还会根据用户私人设置的全局文本大小调整其大小。

在底部会有一个动作按钮,因为它使用的是文本视图而不是按钮,所以可能需要做一些解释。XML如下所示。

    <TextView
        android:id="@+id/text_add"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end"
        android:clickable="true"
        android:paddingBottom="16dp"
        android:paddingEnd="40dp"
        android:paddingLeft="16dp"
        android:paddingRight="40dp"
        android:paddingStart="16dp"
        android:paddingTop="16dp"
        android:text="@string/action"
        android:textAppearance="? android:attr/textAppearanceLarge"
        android:textColor="@color/colorAccent"
        android:textSize="24sp" />

这里似乎应该使用按钮(Button)小部件,但出于两个原因我们选择了文本视图。首先,Android建议使用平面按钮,在卡片视图中只有文本视图可见。其次,触发操作的可触摸区域需要大于文本本身,这可以通过像以前一样设置padding属性轻松实现。要使文本视图可以像按钮一样工作,只需要添加一行android:clickable="true"。

现在,我们做好的卡片看起来应该如图2-11所示。

图2-11

卡片视图的设计还有很多内容,不过对于我们所需遵循的原则,这是一个很好的介绍。下面看看如何将这些呈现对象的新方法反映到工厂模式代码上。

2.4.3 更新工厂模式

设计模式的美妙之处在于它们可以轻松地适应我们想要做出的任何改变。如果想的话,我们可以保持工厂代码不变,并使用单个字符串输出将客户端代码直接定向到适宜的数据集。不过,适配略为复杂的原料对象,更符合模式的本质。

现在,上一章中的代码结构思路有了回报。虽然我们需要编辑接口和具体示例,但可以将工厂类保留原样,这很好地证明了模式的其中一个优势。

依照用于创建卡片的四个条件,我们的新接口应该如下所示。

    public interface Bread {
        String image();
        String name();
        String description();
        int calories();
    }

单个对象示例如下所示。

    public class Baguette implements Bread {
        @Override
        public String image() {
            return "R.drawable.baguette";
        }
        @Override
        public String name() {
            return "Baguette";
        }
        @Override
        public String description() {
            return "Fresh and crunchy";
        }
        @Override
        public int calories() {
            return 150;
        }
    }

随着发展,对象将需要更多的属性,比如价格,以及它们是素食还是坚果。随着对象变得越来越复杂,我们将不得不使用更复杂的方式来管理数据。但原则上讲,这里用的方法没有任何问题。虽然它可能有些笨拙,但肯定更易阅读和维护。工厂模式显然非常有用,但它们只创建了单个对象。为了更真实地建模一个三明治,我们需要将ingredient对象组合到一起,并将整个集合视为一个sandwich对象。这时建造者模式就派上用场了。