安卓可穿戴设备高级教程-四-

237 阅读1小时+

安卓可穿戴设备高级教程(四)

原文:Pro Android Wearables

协议:CC BY-NC-SA 4.0

十三、表盘配置伴侣活动:谷歌移动服务

现在,您已经拥有了可在所有显示模式下工作的基本位图和矢量表盘设计,是时候切换到移动(智能手机)设备并创建表盘“功能选择器”应用了,这将允许您的用户定制自己的表盘设计。这将允许您为您的手表面孔应用收费,因为它们将是定制的手表面孔生成器应用,而不仅仅是简单的手表面孔。

这和表盘设计本身一样复杂,只是方式不同。在本章中,你将了解到 Google API 客户端 Java 接口,它用于将 Google Play 服务集成到你的手表表面可穿戴应用中。

这涉及到使用一系列的 com.google.android.gms 包和它们的类,这是我将在本章介绍的大部分内容,同时创建一个将在用户智能手机上运行的类和另一个将驻留在项目 wear 部分的侦听器类。谷歌移动服务(GMS)就是 Android Wear 云!

您将了解可用于创建 Wear Companion 智能手机应用的 Android GMS 类和接口。它们包括 API,比如 GoogleApiClient,它允许你创建一个 Google Play 服务的客户端。其他穿戴 Api 还包括 DataApiDataItemDataMapDataMapItemComponentNameMessageEventWatchFaceCompanion ,甚至还有 CompanionResult 。您还将了解 Android AlertDialog 类和 Android Uri 类,以及它们如何用于 WatchFace 应用开发。

创建一个 ProWatchFaceCompanionConfigActivity

在本节中,您将为表盘用户手机上的对话创建基础,该对话将允许用户配置表盘设计特征。你对用户的表盘设计控制得越多,你对表盘应用的收费就越高。您将添加一个 < activity > 标签到 AndroidManifest.xml 文件,该文件位于 Android Studio 项目的移动部分。然后你将在你的项目的移动组件中创建一个空的ProWatchFaceCompanionConfigActivity.java结构,然后在你实际编写 Java 代码实现这个类之前学习 Android GoogleApiClient 公共接口,它将为你的手表脸提供一个 UI。

移动应用:将你的活动添加到 AndroidManifest

关闭 Android Studio 项目中所有打开的与 wear 部分相关的编辑选项卡,然后打开项目的 mobile 部分,如图图 13-1 所示。在manifest文件夹中,右键单击 AndroidManifest.xml 并选择跳转到源代码选项在编辑选项卡中打开该文件。

9781430265504_Fig13-01.jpg

图 13-1 。关闭所有选项卡,打开项目的移动部分,右键单击 AndroidManifest,然后选择跳转到源代码

在父 <应用> 标签内添加一个子 <活动> 标签,并用 app_name 标签将其命名为ProWatchFaceCompanionConfigActivity。在<活动>标签内添加一个子 <意图过滤> 标签。在这个里面添加一个 <动作> 子标签,带有一个配置 _ 数字动作常量,以及两个 <类别> 子标签,带有伴侣 _ 配置默认常量。注意,我还在清单顶部添加了一个 < uses-sdk > 标签,带有一个最低 SDK 版本 API 级别 18目标 SDK 版本 API 级别 21 。<使用权限>标签增加了提供 _ 背景唤醒 _ 锁定功能。如图 13-2 所示,你的 XML 标记应该如下所示:

<manifest xmlns:android=http://schemas.android.com/apk/res/android
    package="com.pro.android.wearables.proandroidwearable">
    <uses-sdk android:minSdkVersion="18" android:targetSdkVersion="21" />
    <uses-permission android:name="com.google.android.permission.PROVIDE_BACKGROUND" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <application android:allowBackup="true"
        android:label="@string/app_name" android:icon="@drawable/ic_launcher"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".ProWatchFaceCompanionConfigActivity"
            android:label="@string/app_name" >
            <intent-filter>
               <action android:name="com.pro.android.wearables.proandroidwearable.CONFIG_DIGITAL" />
               <category android:name=
                         "com.google.android.wearable.watchface.category.COMPANION_CONFIGURATION" />
               <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
    </application>
</manifest>

9781430265504_Fig13-02.jpg

图 13-2 。为 ProWatchFaceCompanionConfigActivity 添加一个<活动>父标签和<意图过滤>子标签

您可能已经注意到,IntelliJ 在 android:name 参数值上给出了一个红色文本错误高亮显示。因为您还没有创建那个 Java 文件。你将在本章的下一节纠正这一点,所以这将很快消失!

现在,让我们打开/mobile/res/values/strings.xml 文件,并为该活动指定 Pro WatchFace Options 的 app_name,以便应用的标签描述它的功能。如图 13-3 中的所示。

9781430265504_Fig13-03.jpg

图 13-3 。编辑/res/values/strings.xml 中的 app_name 变量,并将 Activity Pro WatchFace Options 命名为

接下来,您需要创建一个 prowatchfaceconnectionconfigactivity Java 类。

Java 类:创建 WatchFace 同伴活动

让我们通过右击移动 java 文件夹,然后选择新的image Java 类上下文相关菜单序列来创建一个新的 Java 类,如图 13-4 左侧所示(编号为步骤 1)。在选择目标目录对话框中,选择主包(不是 androidTest 选项)并点击 OK 按钮。在新建类对话框中,将您的类命名为ProWatchFaceCompanionConfigActivity,将 Kind 设置为,点击 OK 按钮,创建一个表盘设计配置活动类。

9781430265504_Fig13-04.jpg

图 13-4 。创建 ProWatchFaceCompanionConfigActivity.java 文件以删除红色错误高亮显示

当我输入包名时,我得到一个红色的错误高亮,可以在图 13-5 的顶部看到。当我查看 IntelliJ 项目管理窗格时,我注意到 IntelliJ 没有遵循我的指示,如图图 13-4 中编号 2 所示,并把我的类放在 androidTest 文件夹中,在那里它随后生成了“不正确的包路径”错误。

9781430265504_Fig13-05.jpg

图 13-5 。拖动 ProWatchFaceCompanionConfigActivity 并将其放在 proandroidwearable 包中

我纠正这个错误的方法(在 IntelliJ 方面)是将 Java 类文件从 androidTest 文件夹中拖到普通(非 androidTest)包文件夹中。这导致了“移动类”对话框的出现,在这里我点击了 Refactor 按钮,将类移动到正确的文件夹中,并为所有内容正确地设置了内部“因子”(指针),这样编译器就可以“看到”所有内容是如何组合在一起的。这可以从图 13-5 中看出。

现在你可以添加 Java 扩展关键字 和 Activity 类,你需要扩展它们来使这个类成为一个 Android Activity,它在很大程度上用于保存用户界面设计。

您还需要添加 Java 实现关键字,因为您将指定三个 Java 接口。您的智能手机和智能手表硬件之间的通信将需要这些。

你可以在图 13-6 中看到这两个 Java 关键字,以及 IntelliJ 弹出助手对话框,它显示了两个接口和一个嵌套类,它们是 GoogleApiClient 接口的一部分。在创建了这个空代码基础结构之后,您将在下一节中了解 GoogleApiClient,这样您就可以看到实现一个基本的(即空的)watch face companion 配置活动类、公共接口和被覆盖的(@Override 关键字)方法基础结构需要什么。

9781430265504_Fig13-06.jpg

图 13-6 。使用弹出的助手对话框实现 GoogleApiClient 类的 ConnectionCallbacks 接口

在 implements 关键字后,输入 GoogleApiClient 接口和一个句点,会弹出 helper 对话框,在这里可以选择一个connection callbacks(com . Google . Android . GMS . common . API . Google API client)选项。如果希望 IntelliJ 为您编写代码,请双击此选项。对onconnectionfailed listener接口 也进行同样的操作,这也显示在图 13-6 中弹出的帮助器对话框中。

一旦你实现了 GoogleApiClient 接口,你会想要添加 ResultCallback 公共接口,你可以看到我已经在图 13-7 的中间添加了这个接口。你完成的(空的)Java 类声明看起来像下面的 Java 结构,这也可以在图 13-7 中看到:

public class ProWatchFaceCompanionConfigActivity extends Activity implements
        GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener,
        ResultCallback<DataApi.DataItemResult> {  // Your Java class code will go in here  }

9781430265504_Fig13-07.jpg

图 13-7 。添加 ResultCallback < DataApi。DataItemResult >接口;使用 Alt+Enter 导入 DataApi 类

正如你在图 13-7 中看到的,你需要使用一个 Alt+Enter 组合键来让 IntelliJ 为你编写 DataApi 导入语句。

正如你在图 13-8 和 IntelliJ 中注意到的,在左边有一个错误提示灯泡图标。下拉此 how-to-correct 错误建议菜单,选择实现方法选项,打开选择方法实现对话框,如图 13-8 右侧所示。

9781430265504_Fig13-08.jpg

图 13-8 。下拉错误建议,并选择实现方法选项;实现所有方法

选择所有需要被覆盖的方法并点击 OK 按钮,IntelliJ 将为您编写整个类结构。由此产生的空类结构可以在图 13-9 中看到。

9781430265504_Fig13-09.jpg

图 13-9 。空的 prowatchfacecompancionconfigactivity,有六个导入语句,四个必需的方法

接下来您需要做的是添加标记来支持 CONFIG_DIGITAL 常量值,该常量值由 CompanionConfigurationAction 元数据对象保存。这是在 wear 项目的 AndroidManifest XML 定义文件中完成的,位于现有的父标签内。

Wear 应用:将伴随元数据添加到清单

接下来您需要做的是将第四个标签添加到 wear 应用的父标签中,该标签已经在项目的 wear 应用部分(section)的 Android 清单文件中。元数据条目定义了您将在应用中使用的东西,例如壁纸资源、表盘预览图像资源,或者在本例中,您在 mobile AndroidManifest.xml 中定义的 CONFIG_WATCH_FACE 动作,它将启动 Configuration Companion 活动。

在图 13-10 的底部,可以看到这个标签的 XML 标记突出显示,看起来应该像下面的 XML 标签结构:

<meta-data android:name="com.google.android.wearable.watchface.companionConfigurationAction"
           android:value="com.pro.android.wearables.proandroidwearable.CONFIG_WATCH_FACE" />

9781430265504_Fig13-10.jpg

图 13-10 。在<服务>标签内的 wear/manifest/androidmanifest . XML 中添加一个<元数据>子标签

既然已经设置了 Android Manifest XML 定义文件,并且已经将空的 prowatchfacecompcompanionconfigactivity 类结构放置到位,那么在开始编写所有这些方法的代码之前,让我们快速浏览一下 GoogleApiClient 类并了解一下 Google Play 服务。

Google Play 服务:GoogleApiClient 类

要访问 Google Mobile Services 和 Google Play Services 服务器,您需要为访问这些服务器的任何类创建一个 GoogleApiClient 对象。我喜欢将我的 GoogleApiClient 对象命名为 my GoogleApiClient。

Android 的 GoogleApiClient:使用谷歌移动服务

GoogleApiClient 公共接口是 Google 移动服务 API 的一部分。它位于 com . Google . Android . GMS . common . API . Google API client 包中,是 Google Play 服务集成的主要应用“入口点”。

在执行任何操作之前,GoogleApiClient 必须使用连接到 Google Play 服务。connect( ) 方法。这通常是通过完成的。onStart( ) 方法,您将在本章稍后使用下面的 Java 结构对其进行编码:

@Override
protected void onStart( ) {
    super.onStart( );
    myGoogleApiClient.connect( );
}

在调用**public void on connected(Bundle Bundle)**回调方法之前,您的 Google Play 服务客户端不会被视为已连接。这个空的方法可以在图 13-9 中间看到。

GoogleApiClient 对象可以与许多静态方法一起使用。 这些方法中的一些将需要连接 GoogleApiClient 对象,而其他方法在 GoogleApiClient 连接到客户端活动之前将调用排队。检查每种方法的当前 API 文档,以确定您的客户端是否需要连接。

当您的 watch face 应用使用 GoogleApiClient 对象完成时,您将需要调用。disconnect()方法。 这一般通过来完成。 onStop( ) 方法,在本章稍后的部分,你也将使用下面的 Java 结构对其进行编码:

@Override
protected void onStop( ) {
    if (myGoogleApiClient != null && myGoogleApiClient.isConnected( )) {
        myGoogleApiClient.disconnect( );
    }
    super.onStop( );
}

您应该使用一个 GoogleApiClient 实例化活动onCreate(Bundle saved instancestate)方法中的 GoogleApiClient 对象。构建器嵌套(帮助器)类。你将在本章后面编写的 Java 代码看起来像这样:

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pro_watch_face_config);
        myGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(Wearable.API)
                .build( );
    }

有趣的是,我在这里讨论的所有嵌套类都是在上面列出的 GoogleApiClient 实例化代码中调用的。

  • GoogleApiClient。构建器嵌套类 提供了一个构建器类,用于使用点标记链配置 GoogleApiClient 对象。您可以在上面的代码中看到这一点,尽管方法链是使用句点对齐的,而不是使用句点将方法调用“链接”在一起。
  • GoogleApiClient。ConnectionCallbacks 嵌套接口提供了回调对象 ,当客户端连接到 Google Play 服务或从 Google Play 服务断开时调用这些回调对象。
  • 一个 GoogleApiClient 客户端。OnConnectionFailedListener 嵌套接口将为 Google Play 服务访问导致尝试将 GoogleApiClient 对象连接到此 Google Play 服务失败的场景提供回调对象。

GoogleApiClient 类包含 15 个公共方法。我不能在这里涵盖所有这些,但是我将涵盖您将在 Java 代码中使用的那些,以实现配套的监视界面配置活动。

如果您想快速了解所有 15 个 GoogleApiClient 方法调用规范,可以访问 Android 开发者网站,网址为:

https://developer.android.com/reference/com/google/android/gms/common/api/GoogleApiClient.html
  • 抽象的虚空**。方法 用于将 GoogleApiClient 对象连接到 Google Play 服务服务器。您将在。prowatchfaceconnectionconfigactivity 类中的 onStart()方法。 *** 抽象的虚空。** disconnect( ) 方法用于在您使用完毕后关闭与 Google Play 服务服务器的连接。您将在中使用它。onStop()方法,在本章的后面,当您开始实现您的所有方法时。* 抽象布尔**。** isConnected( ) 方法用于检查 GoogleApiClient 对象是否为当前连接到 Google Play 服务的 。如果是,那么对其他 Google Play 服务方法的请求将会成功。* 抽象布尔**。** isConnecting( ) 方法 检查 GoogleApiClient 对象是否为当前试图将与 Google Play 服务连接的**。****

**接下来,让我们为 ProWatchFaceCompanionConfigActivity 类的 onCreate()方法准备一些 XML 基础结构。创建这些 XML 素材后,您可以在 Java 代码中引用 XML 组件。之后,您将了解 GoogleApiClient。构建器嵌套类,然后构建 GoogleApiClient 对象,这样您就可以在 Pro Watch Face 应用中实现 Google Play 服务,您将在本书中继续改进该应用。有大量的代码要写,有大量的 Android 类要学,所以让我们继续吧!

创建播放客户端:编写您的。onCreate()方法

任何 Android 活动中最重要的方法是 onCreate()方法,因为它总是必须在适当的位置,它创建用户界面布局,在这种情况下,还有 GoogleApiClient 对象及其对等 id。

您需要做的第一件事是通过一个**受保护的 void onCreate(Bundle savedInstanceState)**方法结构覆盖 Activity 超类 onCreate()方法。在该方法中,您将使用 Java super 关键字将 savedInstanceState Bundle 对象向上传递给 Activity 超类,以便超类。onCreate()方法可以处理它。在图 13-11 中间用黄色突出显示的 Java 方法结构应该类似于下面的 Java 代码:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
}

9781430265504_Fig13-11.jpg

图 13-11 。向该类添加一个受保护的 void onCreate()方法和 super.onCreate()超类方法调用

才能调用标准活动**。** setContentView( ) 方法,您需要创建布局 XML 目录,并将素材放置在项目的这个移动部分中。右击 /mobile/res 文件夹,选择新建image Android 资源目录菜单序列。使用资源类型下拉框选择布局;这也将设置文件夹名称,如图图 13-12 所示。

9781430265504_Fig13-12.jpg

图 13-12 。右击/res 文件夹,选择新建image Android 资源目录菜单选项和对话框

点击 OK 按钮创建 /mobile/res/layout 文件夹,然后在项目导航窗格中右键点击这个新文件夹,选择新建image Android 资源文件菜单序列。

这个菜单序列也可以在图 13-12 中看到,在新建子菜单的顶部,所以我这里只显示新建资源文件对话框,你可以在图 13-13 中看到。您将使用 Android Activity 用户界面布局文件命名约定来命名 XML 资源文件(Activity 优先并使用下划线字符),因此使用名称 activity_pro_watch_face_config 并选择一个 LinearLayout 根元素,保留其他两个字段的默认设置(main 和 layout)。点击 OK 按钮,创建一个新的activity _ pro _ watch _ face _ config . XML用户界面布局定义文件。这将保存配置 UI 的用户界面定义。

9781430265504_Fig13-13.jpg

图 13-13 。右键点击新建/res/layout 文件夹,选择新建image Android 资源文件选项和对话框

bootstrap**父标签将使用默认的垂直**方向进行配置,这是您想要的,以及默认的布局常量 match_parent 。这告诉用户界面布局容器填充屏幕尺寸,因为显示屏是 LinearLayout 用户界面布局容器 XML 定义的父级。

在父容器中,添加子 < TextView > UI 小部件,它将包含用于用户界面设计的标题。添加一个带有 @+id/title 值的 ID 参数和一个带有**@ string/pro watch face _ config**值的 android:text 参数。最后,添加 layout_heightlayout_width 参数,Android OS 要求为每个用户界面元素指定这些参数。将 layout_width 设置为 match_parent ,将 layout_height 设置为 wrap_content 。这些常量设置将指定 TextView 元素的布局参数,这将迫使它从一边到另一边横跨屏幕,并限制它只与它包含的文本一样高,这将是您的 UI 设计的标题(标题)。

基本用户界面布局定义 XML 标记,如图图 13-14 所示,应该看起来像下面的 XML 定义结构:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/title"
        android:text="@string/prowatchface_config"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

9781430265504_Fig13-14.jpg

图 13-14 。在父< LinearLayout >容器中添加一个< TextView >,其标题的 android:id 值为

在我开始 onCreate()方法的 Java 编码之前,您需要创建在用户界面布局定义文件中引用的 <字符串> 常量。打开**/mobile/RES/values/strings . XML文件,在父<资源>标签下添加子<字符串>标签。将这个命名为 prowatchface_config** ,数据值为 Configure Watch Face,如图图 13-15 所示,这样你就可以直观地将这些放在一起比较。

9781430265504_Fig13-15.jpg

图 13-15 。创建一个名为 prowatchface_config 的<字符串>常量,其数据值为 Configure Watch Face

现在,您已经准备好切换齿轮,进入一些 Java 编码,这样您就可以实现配套应用的其余部分。onCreate()方法结构。添加setContentView(R.layout.activity_pro_watch_face_config);语句,如图 13-16 中的所示,并在类的顶部声明一个私有字符串 watchFacePeerId。如您所见,接下来您将使用 WatchFaceCompanion 类实例化 PeerId。getIntent(),和。getStringExtra()方法调用。接下来您将学习这些类和方法调用。

9781430265504_Fig13-16.jpg

图 13-16 。添加一个名为 watchFacePeerId 的私有字符串变量;将其设置为等于。getIntent( )getStringExtra()

使用实例化 watchFacePeerId 。getIntent()。getStringExtra( ) 方法链,然后传入 WatchFaceCompanion。EXTRA_PEER_ID 常量。

如图图 13-17 所示,Java 语句应该如下所示:

watchFacePeerId = getIntent( ).getStringExtra(WatchFaceCompanion.EXTRA_PEER_ID);

9781430265504_Fig13-17.jpg

图 13-17 。下拉错误建议菜单;选择将库“wearable-1.1.0”添加到类路径选项

请注意,在 IDE 的左侧有一个红色的错误灯泡,因此下拉此菜单并选择将库“wearable-1.1.0”添加到类路径选项。通常,第一个选项是最佳的。Android Studio 将根据它认为最可行的解决方案对选项进行优先排序。

有趣的是,当我选择这一点时,Android Studio 为 WatchFaceCompanion 类编写了一个 import 语句,因此 Android Studio 需要纠正他们的弹出助手用户界面代码来纠正这一异常,因为我查看了该项目的 Gradle 文件,但没有看到任何附加内容。

让我们花点时间对 WatchFaceCompanion 和 ComponentName 类进行一个高层次的概述,之后,您将继续您的 Java 编码。

WatchFaceCompanion 类:配置常数

Androidpublic final watch face companion类扩展了 java.lang.Object 并定义了常量供表盘配置活动类使用。这就是使用 Java final 关键字声明该类的原因。

WatchFaceCompanion 类的 Java 类层次结构如下所示:

java.lang.Object
  > android.support.wearable.companion.WatchFaceCompanion

正如您在本章中已经看到的,要注册您要在配套手机上启动的配置活动,您需要将 <元数据> 条目添加到 AndroidManifest.xml 文件中的手表表面组件(项目的磨损部分)。这指定了您的 Intent ACTION 常量,它将被“触发”以启动 Activity 子类。如果你想重温你之前写的 XML 标记,你可以在图 13-10 中看到这一点。

Activity 子类还需要有一个规范,它将列出在 XML 标记的元数据块中指定的相同的 ACTION 常量,此外还有两个 WEAR 类别,DEFAULT 和 COMPANION_CONFIGURATION,它们也已就位,如图 13-2 中的所示。

对于手表外观配置对话框活动类,用类别 com . Google . Android . wearable . watch face . category . wearable _ CONFIGURATION 替换 com . Google . Android . wearable . watch face . category . companion _ CONFIGURATION。

这个 WatchFaceCompanion 类使用两个常量,这两个常量都是字符串值:

  • EXTRA_PEER_ID 常量包含一个字符串 EXTRA 的键值,该字符串在电话侧配置活动意图(启动)对象中指定当前连接的设备的 PeerId。
  • EXTRA _ WATCH _ FACE _ COMPONENT常量包含字符串 EXTRA 的键值,该字符串为使用配置活动意图(启动)对象配置的手表面板指定 ComponentName

在开始编写与 WatchFaceCompanion 相关的 Java 代码之前,让我们更深入地研究一下 Android ComponentName 类,看看它为 Android 开发人员提供了什么,尤其是在 WatchFaces API 开发方面。

ComponentName 类:指定一个组件

Androidpublic final component nameclass 是 android.content 包的一部分,它实现了**Parcelable Cloneable Comparable**接口。该类扩展了 java.lang.Object 主类;因此,ComponentName 类具有以下 Java 类层次结构:

java.lang.Object
  > android.content.ComponentName

这个 ComponentName 类为特定的应用组件提供了一个标识符。Android 中的应用组件包括一个活动服务广播接收方内容提供方(数据库或数据存储)对象。因此,这个类允许开发者指定他们的 Android 组件类型。

在本书的例子中,它是一个活动组件类型,您正试图为 Android 操作系统定义它。需要封装在 ComponentName 对象中的两条信息来标识任何给定的 Android 组件。第一个是组件所在的包(字符串值),第二个是指定包中组件类型的子类名称(也是字符串值)。

该类指定了一个属性或字段,即公共静态最终创建者<组件名>创建者属性。

有四个重载的公共构造函数方法,没有一个是您将使用的,因为您将通过使用创建一个名为 ComponentName 的 componentName 对象。getIntent()。getParcelableExtra( ) 方法链。在本章的下一节中,您将使用下面的 Java 语句来实现这一点:

componentName = getIntent( ).getParcelableExtra(WatchFaceCompanion.EXTRA_WATCH_FACE_COMPONENT);

在这行 Java 代码中,您可以看到 ComponentName 类和 WatchFaceCompanion 类之间的链接,这是您刚刚了解到的。

ComponentName 类有 16 个公共方法,包括**。clone( )** 方法,它将克隆 ComponentName 对象,一个**。compare to(ComponentName component name)方法比较 component name 对象,一个。equals(Object object)** 方法,一个**。getClassName( )** 方法,一个**。getPackageName( )** 方法,一个**。toString( )** 方法,以及其他类似的工具方法,可用于访问 ComponentName 信息。如果您想深入了解这 16 种方法的细节,您可以访问 Android 开发者网站的 URL:

http://developer.android.com/reference/android/content/ComponentName.html

接下来,让我们完成使用 WatchFaceCompanion 类的 Java 代码的编写,IntelliJ 已经为该类编写了一个导入语句,如图 13-18 顶部突出显示的。之后,您将了解 GoogleApiClient。构建器嵌套(帮助器)类,这样您就可以编写。onCreate()方法实例化(即构建)一个 GoogleApiClient 对象。之后,您就完成了 Google Play 服务对象的创建。

设置监视面标识:ComponentName 和 PeerId

在 setContentView()方法调用后添加一行代码,并使用声明和实例化 TextView title 对象。 findViewById( ) 方法调用,引用你的 title ID。将 watchFacePeerId String 对象设置为等于 getIntent()。getStringExtra()方法链,引用来自 WatchFaceCompanion 类的 EXTRA_PEER_ID 常量。

在类的顶部声明一个名为 ComponentName 的 componentName 对象,这样就可以在 onCreate()方法中实例化这个对象。您将设置对象等于 getIntent()。getParcelableExtra()方法链。

在 getIntent()内部。getParcelableExtra()方法链,您将需要引用来自 WatchFaceCompanion 类的 EXTRA_PEER_ID 常量。

控件的 Java 代码。onCreate()方法的结构到目前为止应该看起来如下,这也可以在图 13-18 的底部看到:

ComponentName componentName;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_pro_watch_face_config);
    TextView title = (TextView)findViewById(R.id.title);
    watchFacePeerId = getIntent( ).getStringExtra(WatchFaceCompanion.EXTRA_PEER_ID);
    componentName = getIntent( ).getParcelableExtra(WatchFaceCompanion.EXTRA_WATCH_FACE_COMPONENT);
}

9781430265504_Fig13-18.jpg

图 13-18 。声明 ComponentName 对象;实例化 TextView、watchFacePeerId 和 componentName

现在您唯一要做的就是声明和实例化您的 GoogleApiClient 对象,这涉及到使用您的 GoogleApiClient。构建器类,我接下来会解释。

GoogleApiClient。构建器:构建 Google API 客户端

Androidpublic static final Google API client。Builder 类扩展了 java.lang.Object 主类,创建了以下类层次结构:

java.lang.Object
  > com.google.android.gms.common.api.GoogleApiClient.Builder

这个类创建了一个构建器类 ,用于配置 GoogleApiClient。

要实例化并构建一个名为 myGoogleApiClient 的 GoogleApiClient 对象,并为 Android Wearable API 应用提供 ConnectionCallbacks 和 OnConnectionFailedListener 支持,您可以使用以下结构:

myGoogleApiClient = new GoogleApiClient.Builder(this)
        .addConnectionCallbacks(this)
        .addOnConnectionFailedListener(this)
        .addApi(Wearable.API)
        .build( );

GoogleApiClient。Builder 类有两个公共构造函数方法。第一个,也就是上面显示的您将使用的那个,接受上下文对象,您将使用 Java this 关键字将它传递到方法中。此构造函数方法采用以下格式:

GoogleApiClient.Builder( Context context )

还有一个更复杂(重载)的构造函数方法,如果已经在 Java 代码中创建了 ConnectionCallbacks 对象和 OnConnectionFailedListener 对象,则可以在该方法中指定这些对象:

GoogleApiClient.Builder( Context context, GoogleApiClient.ConnectionCallbacks connectedListener,
                         GoogleApiClient.OnConnectionFailedListener connectionFailedListener )

这个 GoogleApiClient。Builder 类有十几个方法,我将在这里介绍它们(就像我介绍所有的 WatchFaceStyle 一样。构建器方法),因此您可以构建应用可能需要连接到 Google Play 服务服务器的任何类型的 GoogleApiClient 对象结构:

  • <O extends Api.ApiOptions.HasOptions> GoogleApiClient.Builder addApi(Api<O> api, O options)方法允许开发者指定客户端请求哪些 API。
  • GoogleApiClient.Builder addApi(Api<? extends Api.ApiOptions.NotRequiredOptions> api)方法还允许开发人员指定客户端请求哪些 API。
  • GoogleApiClient.Builder addConnectionCallbacks(GoogleApiClient.ConnectionCallbacks listener)方法允许您注册一个监听器来接收来自 GoogleApiClient 对象的连接事件。您将在我们的表盘应用中使用它。
  • 您将在 watch face 配置应用中使用的另一个方法是GoogleApiClient.Builder addOnConnectionFailedListener(GoogleApiClient.OnConnectionFailedListener listener)方法,该方法将您的侦听器添加到 register 中,以从您的 GoogleApiClient 对象接收连接失败事件。
  • GoogleApiClient.Builder addScope(Scope scope)方法允许开发人员指定 Wear 应用需要请求的任何 OAuth 2.0 范围。
  • GoogleApiClient build( )方法允许开发人员构建一个 GoogleApiClient 对象,用于与 Google Play 服务服务器 API 进行通信。
  • GoogleApiClient.Builder enableAutoManage(FragmentActivity fragmentActivity, int clientId, GoogleApiClient.OnConnectionFailedListener unresolvedConnectionFailedListener)方法允许开发人员使用连接到。onStart()方法,并在。onStop()方法。
  • GoogleApiClient.Builder setAccountName(String accountName)方法允许开发者在硬件设备上指定一个帐户名,用于连接 Google Play 服务服务器。
  • GoogleApiClient.Builder setGravityForPopups(int gravityForPopups)方法允许开发者使用 Android 重力常量在显示屏上指定游戏服务弹出窗口的一般位置。
  • GoogleApiClient.Builder setHandler(Handler handler)方法允许开发人员设置一个处理程序来指示调用回调时使用哪个线程。
  • GoogleApiClient.Builder setViewForPopups(View viewForPopups)方法允许开发人员将指定的视图设置为弹出窗口的内容视图。
  • GoogleApiClient.Builder useDefaultAccount( )方法允许开发者指定在连接到 Google Play 服务服务器时应该使用的默认帐户。

现在您对 Builder 类有了更好的了解,让我们使用 Java new 关键字和基本构造函数来构建 GoogleApiClient 对象。

构建 GoogleApiClient:使用可穿戴 API

让我们添加上一节中的构建器代码示例,它添加了 ConnectionCallbacks 和 OnConnectionFailed 侦听器对象,以及一个可穿戴的。API,并构建 myGoogleApiClient 对象。onCreate()方法的 Java 代码如图 13-19 中的所示,应该是这样的:

GoogleApiClient myGoogleApiClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_pro_watch_face_config);
    TextView title = (TextView)findViewById(R.id.title);
    watchFacePeerId = getIntent( ).getStringExtra(WatchFaceCompanion.EXTRA_PEER_ID);
    componentName = getIntent( ).getParcelableExtra(WatchFaceCompanion.EXTRA_WATCH_FACE_COMPONENT);
    myGoogleApiClient = new GoogleApiClient.Builder(this)
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(this)
            .addApi(Wearable.API)
            .build( );
}

9781430265504_Fig13-19.jpg

图 13-19 。声明并实例化 myGoogleApiClient 对象,并使用 Builder 类对其进行配置

请注意,您正在通过 Java this 关键字将该类的当前上下文对象传递给 GoogleApiClient (class),以及嵌套类 ConnectionCallbacks 和 OnConnectionFailedListener,我将在本章后面详细介绍它们。这样做的原因是,上下文对象包含了关于类的所有相关(系统)信息,这些类需要引用和使用这个信息(没有双关语)才能最佳地执行它们的相关功能。

启动和停止播放客户端:onStart()和 onStop()

既然已经在 onCreate()中创建了 Google Play 服务客户端和 UI 布局容器,下一步就是创建启动客户端的代码。创建一个受保护的 void onStart( ) 方法,该方法使用 Java super 关键字调用活动超类 onStart()方法,然后调用 myGoogleApiClient 对象的. connect()方法。

这可以用下面的 Java 代码来完成,也可以在图 13-20 中看到。我单击了 myGoogleApiClient 对象来突出显示它的用法:

@Override
protected void onStart( ) {
    super.onStart( );
    myGoogleApiClient.connect( );
}

9781430265504_Fig13-20.jpg

图 13-20 。添加受保护的 void onStart()和 onStop()方法,用于连接和断开 Google Play 服务

onStop()方法有点复杂,因为它涉及一个条件 if()语句,该语句评估 myGoogleApiClient 对象以查看它是否已连接。如果它是连接的,它就断开它。之后,使用 Java super 关键字将 onStop()方法函数调用传递给 Activity 超类,后者将应用从设备的系统内存中删除。以下代码实现了这一点:

@Override
protected void onStop( ) {
    if (myGoogleApiClient != null && myGoogleApiClient.isConnected( )) {
        myGoogleApiClient.disconnect( );
    }
    super.onStop( );
}

接下来,创建 onConnected()方法,该方法将包含连接到 Google Play 服务后要做的事情的 Java 代码。

连接客户端:创建 onConnected 方法

现在让我们实现 onConnected( ) 方法。IntelliJ 使用您在类声明中指定的 Java 接口为我们创建了该方法。在空的 bootstrap 方法中,您要做的第一件事是创建一个空的 if-else 方法,该方法查看watchfacpeerid并确定它是否已被使用,也就是说,它是否包含一个非空值。如图图 13-21 所示的(仍然)空方法结构如下所示:

@Override
public void onConnected(Bundle bundle) {
    if (watchFacePeerId != null) {
       // Things to do if a connection is detected, that is, watchFacePeerId has some ID value
    } else {
       // Things to do if there has not been any connection, that is, watchFacePeerId is empty
    }
}

9781430265504_Fig13-21.jpg

图 13-21 。添加一个空的 if-else 条件结构,评估 watchFacePeerId 字符串以查看它是否被使用

现在您已经有了评估结构,我将提供一些关于 UriUri 的信息。在实现其余的 Java 代码之前,先构建类。

Android Uri 类:统一资源标识符对象

Android 公共抽象 Uri 类扩展了 java.lang.Object master 类,实现了一个 Parcelable Comparable 接口。它可以在 android.net 的包装中找到。它的类层次结构如下所示:

java.lang.Object
  > android.net.Uri

Android Uri 对象包含不可变的统一资源标识符(Uri)引用路径。URI 引用包括一个 URI 和一个片段,即跟在井号(#)后面的 URI 的组成部分。

就像java.net包中的 Uri 类一样(不要混淆这些),Android Uri 类及其 Uri。构建器嵌套类构建并解析符合 RFC 2396 标准的 URI 引用。

为了最大化性能,Uri 类不对 Uri 路径内容本身执行数据验证。这意味着对于无效的 Uri 路径数据输入,Uri 的行为是未定义的。因此,这个类有些宽容;当面对无效的输入数据时,该类返回垃圾,而不是抛出异常,除非开发人员另有规定。

Android Uri 类包含一个嵌套类 Uri。构建器“助手”类,用于构建或操作 URI 引用。在对 Uri 进行简短讨论后,您将在 onConnect()方法中使用这两个类。下一节中的构建器类。

Android Uri 类中有 39 个方法,不幸的是,我不能详细讨论所有这些方法,因为您将使用 Uri 中的方法。生成器类。但是,如果您计划经常使用 Uri 对象,您可以在闲暇时通过以下 URL 深入研究所有这些方法:

http://developer.android.com/reference/android/net/Uri.html

安卓 Uri。构建器类:构建一个 Android URI 对象

公共静态最终 Uri。Builder 类扩展了 java.lang.Object,这意味着该类是临时编写的,并且具有以下类层次:

java.lang.Object
  > android.net.Uri.Builder

Uri。构建器类是一个嵌套类或辅助类,用于构建或操作 URI 引用。值得注意的是,这个 Builder 类对于并发使用是不安全的,也就是说,没有提供跨线程的同步;仅在一个线程中使用它。

如果您想构建一个已经存在的 Uri 对象,可以使用**。** buildUpon( ) 方法从 Uri 类。

Uri。Builder 类有一个公共构造函数方法,即 Uri。Builder()方法,它将构造或实例化一个新的 Uri。生成器对象。要声明和实例化一个名为 UriBuilder 的 uriBuilder 对象,可以使用以下 Java 编程语句:

Uri.Builder uriBuilder = new Uri.Builder( );

Uri。Builder 类有 17 个公共方法,我无法在这里一一介绍。但是,我将介绍您将用来完成 onConnect()方法的那些方法,您将在本章的下一节中完成:

  • 一个 Uri。构建器类**。authority(String authority)方法用于为 Uri 对象设置权限。一个 Uri。建造者阶级。【String path】**方法用于为 Uri 对象设置路径。
  • 一个 Uri。构建器类**。scheme(String scheme)** 方法可以用来为一个 Uri 对象指定一个方案。一个乌班**。build( )** 方法用于构造一个 Uri 对象,使用已经使用 Uri 设置的其他 Uri 属性。上面列出的 Android 开发者网站 Uri URL 上列出的构建器方法。

为客户机构建 Uri:完成 onConnected()

如果 watchFacePeerId 不为空,这意味着它已经被 Google Play 服务赋值,因此,在 onConnected()方法的if(watchFacePeerId != null)部分,您将需要声明并实例化 uriBuilder Uri。通过使用下面一行 Java 代码来创建对象:

Uri.Builder uriBuilder = new Uri.Builder( );

完成后,您就可以使用 Uri 了。你刚刚学过的构建器方法,设置方案为穿,你的路径为 PATH_WITH_FEATURE ,权限为 watchFacePeerId 值。一定要加一个**。在方法链的末尾进行 build( )** 方法调用,如图 13-22 和下面的 Java 语句所示:

Uri uri = uriBuilder.scheme("wear").path(PATH_WITH_FEATURE).authority(watchFacePeerId).build( );

9781430265504_Fig13-22.jpg

图 13-22 。实例化 Uri。名为 uriBuilder 的生成器对象;将名为 uri 的 Uri 对象设置为等于构建方案、路径和授权,然后使用。构建()

正如你在图 13-22 中看到的,你需要使用 Alt+Enter 组合键,并让 IntelliJ 为你编写一个 android.net.Uri 类导入语句。这将处理红色错误代码突出显示,至少对于 Uri (父类)、 Uri 是如此。构建器(嵌套类),和**。scheme( )** (method)引用,因为所有这些都包含在 android.net.Uri 中,因此将使用 import 语句正确引用。唯一保留的红色错误文本是 PATH_WITH_FEATURE 路径常量。

为了去掉最后一个红色的错误突出显示,您需要在类的顶部添加一个常量,使用 Java private static final 关键字链来定义 Java 常量值。使用字符串变量类型,命名为 PATH_WITH_FEATURE ,用下面的 Java 编程语句设置为等于**/watch _ face _ config/pro watch face**,如图图 13-23 所示:

private static final String PATH_WITH_FEATURE = "/watch_face_config/ProWatchFace";

既然您已经了解并设置了您的 Uri 对象,接下来您需要用这个自定义 Uri 配置的是 Wearable。API ,它是您使用 GoogleApiClient 创建的。建造者阶层的**。** addApi( ) 方法调用。这可以在图 13-19 和 13-20 中看到,如果你需要可视化的话。

9781430265504_Fig13-23.jpg

图 13-23 。使用/watch _ face _ config/pro watch face 值创建私有静态最终 PATH_WITH_FEATURE

Android 的 GMS DataApi 接口:配置数据 Api

这个安卓谷歌移动服务(GMS) DataApi 公共接口com . Google . Android . GMS . wearable . data API包的一部分。该接口用于为 Android 应用组件提供 API,以用于向 GMS 读取或写入 DataItemAsset 对象,在我们的示例中,用于可穿戴应用。DataApi 对象包含 DataItem 以及 Asset (sub)对象。

一个数据项将在您的 Android Wear 网络中的所有硬件设备之间同步。当 Wear 应用当前没有连接到网络上的任何节点时,可以加载或配置这些 DataItem 对象。当任何网络节点显示为在线时,这些 DataItem 对象将被同步。

DataItem 对象对于创建它们的应用是私有的。因此,DataItem 对象只能由其他网络节点上的应用访问。开发人员通常应该优化他们的 DataItem 对象,使其文件较小。

如果您需要传输大型或持久的数据对象,如图像,您应该使用素材对象,这是可以包含在 DataApi 对象中的另一种类型的对象。

每个 DataItem 对象都使用 URI 来标识,可通过。getUri()。Uri (object)将包含 DataItem 对象创建者和路径。完全指定的 URIs 应遵循以下格式:

穿:// < node_id > / <路径>

<node_id>部分是创建 DataItem 对象的可穿戴节点的节点 ID ,而<路径>是应用定义的路径。这意味着给定一个 DataItem 对象 URI,调用。getHost()方法可以返回对象创建者的节点 ID 数据值。</node_id>

在 DataApi 类的一些方法中,包括您将要使用的方法中,。getDataItem(GoogleApiClient,Uri) 方法,可以仅使用路径从 Uri 中省略(不利用)节点 ID 值。

在这个特定的用例中,Uri 对象可以用来引用多个数据项。这是因为多个节点可以创建使用相同确切路径值的 DataItem 对象。

包含部分指定数据项 Uri 的 URI 对象将在磨损后使用一个单正斜杠 ( / ) 字符,格式为磨损:/ <路径>

不同节点中的并发数据项对象修改可能导致不一致。在我们的用例中,DataItem 对象创建者将拥有 DataItem,因此 DataItem 对象将由原始创建者节点更新。

一个更复杂的用例可能使用所谓的“生产者消费者”方法,其中一个网络节点负责产生数据项,而另一个网络节点负责消费该数据项,当然是在该数据项被处理之后。如果您使用更复杂的用例,您应该确保 DataItem 对象具有唯一的 ID 值,并确保这些 DataItem 对象在创建后不会被修改。

DataApi 接口有四个嵌套接口:一个 DataApi。DataItemResult 接口包含单个 DataItem,即 DataApi。DataListener 接口与一起使用。addListener(GoogleApiClient,DataApi。DataListener) 方法来监听和接收数据事件, DataApi。deletedataaitemsresult接口包含被删除的 DataItem 对象的数量,最后是 DataApi。GetFdForAssetResult 接口包含所请求素材对象的文件描述符

DataApi 接口包含九个公共方法。您将使用抽象挂起结果<数据 Api。onConnect()方法中的 dataitem result>get dataitem(Google API client client,Uri uri) 方法。该方法从 Android Wear GMS 网络中检索单个 DataItem 对象。

使用 DataApi 类:配置可穿戴设备。应用接口

呼叫**。getDataItem( )** 关了可穿戴。DataApi 类,并传入您的 myGoogleApiClientUri 对象。通过向 PendingResult 类的添加方法调用来创建方法链。****setResultCallback()方法,并在当前上下文中传递对象与 Java 这个关键字。这是通过下面的 Java 语句完成的,在图 13-24 的底部高亮显示:

Wearable.DataApi.getDataItem(myGoogleApiClient, uri).setResultCallback(this);

9781430265504_Fig13-24.jpg

图 13-24 。设置可穿戴。API 到 Uri 对象。setResultCallback(this),。getDataItem(myGoogleApiClient,uri)

让我们快速看一下 Android PendingResult 类,然后创建一个使用 AlertDialog 类的名为 noConnectedDeviceDialog()的方法。

Android PendingResult 类:接收结果

Android GMS PendingResult public interface是 Wear API 的部分,包含在com.google.android.gms.common.api.PendingResult<R extends com.google.android.gms.common.api.Result>包中。这个接口有一个已知的间接子类:Batch 类。

PendingResult 对象包含从 Google Play 服务服务器调用 Wear API 方法的挂起结果。PendingResult 的最终结果对象属于类型 R ,这是 Java 的原始数据类型。可以使用两种方法之一在 Java 中检索原始数据包类型。

第一种方法是使用阻塞的调用。await( ) 或者**。await(long,TimeUnit)** 方法。第二种方法是使用 ResultCallback 接口(对象),这也是您将要做的。这是通过向**传递一个实现 ResultCallback 公共接口的对象来实现的。setResultCallback(result callback)**方法调用。

在使用。await(),或者传递给结果回调,如果再次尝试检索这个结果,就会导致抛出错误。释放与返回结果相关联的任何资源是调用实体或回调接收者的责任。一些结果类型可能会实现可释放的,在这种情况下是一个**。** release( ) 方法调用应该用来释放相关联的资源。

PendingResult 公共接口有六个方法,包括抽象 R 。await( ) 方法,该方法一直阻塞到 PendingResult 任务完成;还有一个抽象的 R 。await(long time,TimeUnit units) 方法,该方法将一直阻塞,直到 PendingResult 任务完成或等待 PendingResult 的时间单位(毫秒)超时。

还有一个抽象的虚空**。** cancel( ) 方法,该方法将请求取消 PendingResult。有一个抽象布尔**。****is cancelled()方法,该方法将指示 PendingResult 是否因调用{ Google API client # disconnect }**或调用而被取消。直接在 PendingResult 对象或封闭批处理对象上取消()。

最后是抽象的虚空**。setResultCallback(result callbackcallback)**方法,如果您希望结果对象在结果就绪时通过回调传递,该方法设置回调,以及一个抽象 void 。setResultCallback(result callbackcallback,long time,TimeUnit units) 方法,如果您希望在结果就绪或等待结果超时时通过回调传递结果,该方法将设置回调。

接下来,让我们创建一个方法,在没有连接的情况下提醒用户。

创建未连接的对话框:使用 AlertDialog

在 onConnected()方法 if-else 结构的 else 部分添加一个 noConnectedDeviceDialog()方法调用,IntelliJ 会用红色错误文本高亮显示,正如你在图 13-25 底部看到的高亮显示。

9781430265504_Fig13-25.jpg

图 13-25 。在 else 部分添加一个 noConnectedDeviceDialog()方法调用,并选择 Create Method 选项

从红色灯泡下拉菜单中选择创建方法选项,并让 IntelliJ 创建方法结构,如图图 13-26 所示。首先,声明两个名为 noConnectTextokButtonLabel 的字符串变量。

9781430265504_Fig13-26.jpg

图 13-26 。在 IntelliJ 为您创建的这个方法结构中,声明并实例化两个 String 对象

接下来,在 IntelliJ 的选项卡中打开mobile/RES/values/strings . XML文件,添加 noConnectedDeviceDialog()方法中引用的两个<字符串>常量,并为它们提供描述性的 AlertDialog 消息和 UI 按钮文本。这可以在图 13-27 中看到(突出显示),应该看起来像下面的 XML 标记:

<string name="no_connected_device">Wearable Device Not Connected!</string>
<string name="ok_button_label">OK</string>

9781430265504_Fig13-27.jpg

图 13-27 。创建文本<字符串>定义,告诉用户设备没有连接,并点击 OK

在您完成 noConnectedDeviceDialog()方法的编码之前,让我们快速了解一下 Android AlertDialog 类。

Android AlertDialog:为你的应用创建一个提醒对话框

Androidpublic alert Dialog类扩展了 Android Dialog 类,实现了 DialogInterface 。它包含在 android.app 包中,有三个已知的直接子类: DatePickerDialogProgressDialogTimePickerDialog 。类的层次结构如下所示:

java.lang.Object
  > android.app.Dialog
    > android.app.AlertDialog

AlertDialog 是一种特殊类型的 Android 对话框,可以显示一个、两个或三个按钮。如果你只想在对话框中显示一个字符串,这是为了告诉用户没有 GMS 连接。需要调用 setMessage( ) 方法。如果你想设计自定义的用户界面视图对象,你应该使用一个**。addView( )** 方法调用,您可以稍后使用更改视图 UI 设计。setView( ) 方法调用。

AlertDialog 类包含一个嵌套的 helper 类,它是一个名为 AlertDialog 的生成器类。Builder 类,您将在恢复 Java 编码时使用它来构造 AlertDialog 对象。

AlertDialog 类支持五个 Android 主题常量:Theme _ DEVICE _ DEFAULT _ DARKTheme _ DEVICE _ DEFAULT _ LIGHTTHEME_HOLO_DARKTHEME_HOLO_LIGHTTHEME_TRADITIONAL 常量,用于 Android 4.x 之前的 Android 版本,Android 4 . x 引入了 HOLO 主题。Android 5.x 使用了 Material 主题(上面提到的前两个常量)。

AlertDialog 有三个受保护的构造函数方法,包括您将要使用的方法AlertDialog(Context context),以及几个更高级的构造函数:

  • AlertDialog(Context context, int theme)构造函数将构造一个 AlertDialog,它使用您可以指定的完全独特的主题。
  • 一个AlertDialog(Context context, boolean cancelable, DialogInterface.OnCancelListener cancelListener)构造函数允许您指定一个 AlertDialog 是否是可取消的,并使用构造函数方法调用给它附加一个 cancelListener。

Android AlertDialog。构建器:构建警报对话框

Android 的公共静态 AlertDialog。Builder 类扩展了 java.lang.Object,因此被临时编码以构建警告对话框。它具有以下层次:

java.lang.Object
  > android.app.AlertDialog.Builder

该类有两个公共构造函数方法。您将使用的第一个工具是 AlertDialog。Builder(Context context) 方法,它将使用一个上下文对象(在我们的例子中是 Java this 关键字)为这个 AlertDialog 构造一个 AlertDialog。生成器对象和用于创建它的 AlertDialog 对象。

第二个构造函数是 AlertDialog。Builder(Context context,int theme) ,它允许为正在创建的 AlertDialog 指定不同的主题。

警报对话框。Builder 类有 36 个公共方法,我无法在这里详细介绍所有这些方法,但是我将在下一节介绍您将在 Java 代码中使用的方法。

  • 警报对话框**。create( )** 方法使用您提供给 AlertDialog 的参数创建一个 AlertDialog。生成器类。
  • **。setCancelable(boolean cancelable)**方法设置你的对话框是否可以被取消。
  • 。setMessage(int messageId) 方法将使用给定的资源 Id 设置要显示的消息,在我们的例子中,是一个 XML <字符串>常量。
  • 一个**。setPositiveButton(int textId,DialogInterface。OnClickListener listener)** 方法将设置一个监听器,当对话框的肯定按钮被按下时,这个监听器将被调用。
  • show( ) 方法使用提供给 AlertDialog 的参数显示 AlertDialog。生成器并将 AlertDialog 对象覆盖在屏幕上。

使用警报对话框。构建器:报警对话系统编码

在 noConnectedDeviceDialog()方法中,声明并实例化一个 AlertDialog。使用 Java new 关键字命名为 alertBuilder 的构建器,并将上下文 (this)对象传递给基本的构建器构造器方法。现在,您可以调用这个对象的三个方法,使用点标记链接,使用来自定义 AlertDialog。setMessage( ) 和**。setCancelable( )** 定义文本和 UI(取消)函数和**。setpostivebutton()**实现 UI (Button)元素及其 onClick()监听器和处理。如图图 13-28 所示,基本 Java 代码应该如下所示:

AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this);
alertBuilder.setMessage(noConnectText)
            .setCancelable(false)
            .setPositiveButton(okButtonLabel, new DialogInterface.OnClickListener( )
{ // handler });

9781430265504_Fig13-28.jpg

图 13-28 。实例化 AlertDialog。生成器使用。setMessage(),。setCancelable()和。setPositiveButton()

正如你在图 13-28 中看到的,你有一些红色的错误代码高亮显示,这告诉你你需要使用 Alt+Enter 组合键来让 IntelliJ 为 DialogInterface 类写一个导入语句。该类的导入触发 IntelliJ 为您提供另一个错误解决方案下拉列表。选择第一种实施方式选项,如图图 13-29 所示。

9781430265504_Fig13-29.jpg

图 13-29 。选择“实现方法错误解决”选项,让 IntelliJ 编写 onClick()方法

IDE 将在 DialogInterface 内部编写一个public void onClick(dialog interface dialog,int which)** 方法。OnClickListener( )** 对象,可以在图 13-30 中看到,以及声明、实例化和显示 AlertDialog 对象的代码,如下所示:

AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this);
alertBuilder.setMessage(noConnectText).setCancelable(false)
            .setPositiveButton(okButtonLabel, new DialogInterface.OnClickListener( ) {
                public void onClick(DialogInterface dialog, int which) { }
            });
AlertDialog alertDialog = alertBuilder.create( );
alertDialog.show( );

9781430265504_Fig13-30.jpg

图 13-30 。声明并实例化一个名为 AlertDialog 的 alertDialog 对象;调用. show()方法来显示它

编码 onResult 方法:DataItem 和 DataMap

接下来,您需要编写 onResult( ) 方法,它将包含一个 if-else 结构,该结构评估活动是否已经接收到 DataItemResult,因此方法名为 onResult()。if()条件使用一个逻辑和来检测是否有效。getStatus()。isSuccess( ) 方法调用链返回 true 和 if**。getDataItem( )** 方法调用不返回任何东西(null),意味着 DataItemResult 对象内部有东西。

一个空的 DataItemResult 处理结构,如图图 13-31 所示,应该是这样的(空)Java 条件 if( )-else{}处理结构:

public void onResult(DataApi.DataItemResult dataItemResult) {
        if ( dataItemResult.getStatus( ).isSuccess( )  &&  dataItemResult.getDataItem( ) != null ) {
                   // DataItemResult Detected! Processing
        } else {
                 // No DataItemResult Detected Processing
        }
}

9781430265504_Fig13-31.jpg

图 13-31 。在计算 dataItemResult 对象的 onResult()方法中创建 if-else 结构

现在,您可以编写 DataItem、DataMap 和 DataMapItem 对象处理代码。它位于语句的 if()部分,因为只有当 DataItemResult 对象包含要处理的数据时,它才会被执行。

下一步是进行编码,此外还要更详细地查看两个 Android GMS 类和一个 Android GMS 接口,这样您就可以理解这些与数据相关的对象在您的应用中的用途。

Android 的 DataItem 接口:磨损数据的基础

Android 公共 DataItem 接口实现了freez able,是com . Google . Android . GMS . wearable . DataItem包的一部分。DataItem 表示存储在 Android Wear 网络中的数据的基础对象。DataItem 对象在网络中的所有设备间复制。

DataItem 对象包含数据的“blob”以及相关联的素材。使用包含其创建者和路径的 Uri 来标识 DataItem。一个 DataItem 类有四个公共方法:??。getAssets( ) 方法检索包含在 DataItem 中的素材的映射。getData( ) 方法检索存储在指定 Uri 的数据的数组。getUri( ) 方法返回 DataItemUri;以及**。setData(byte[ ] data)** 方法在 DataItem 内部设置一个数据 byte[ ]数组。让我们在代码中实现一个数据项!

加载 DataItem 对象:使用. getDataItem()方法

让我们创建一个名为 configDataItem 的 DataItem 对象,并将其设置为等于一个**。getDataItem( )** 方法调用dataitem resultdataitem result 对象,该对象使用以下 Java 代码被传递到 onResult()方法中,并且可以在图 13-32 中看到突出显示。如您所见,您将需要使用 Alt+Enter 工作流程,这样 IntelliJ 将导入 DataItem 类:

DataItem configDataItem = dataItemResult.getDataItem( );

9781430265504_Fig13-32.jpg

图 13-32 。声明名为 configDataItem 的 DataItem,并将其设置为等于对 dataItemResult 的 getDataItem()调用

接下来让我们看看 Android GMS DataMapItem 类,因为这是您将在 onResult()方法 if()结构中使用的下一个对象。

Android 的 DataMapItem 类:带有地图的 DataItem

一个 Android 公共 DataMapItem 类扩展了 java.lang.Object,它包含在com . Google . Android . GMS . wearable包中,其层次结构如下:

java.lang.Object
  > com.google.android.gms.wearable.DataMapItem

DataMapItem 类(对象)包装 DataItem 对象以创建称为 DataMapItem 对象的高级映射 DataItem 对象。这些对象包含更多的结构化数据,甚至是可序列化的数据。该类有三个公共方法: static。from DataItem(DataItem DataItem)方法提供了一个包装 DataItem 对象的 DataMapItem 对象;。getDataMap( ) 方法提取了一个数据映射对象,您将在接下来了解这个对象;而**。getUri( )** 方法提取一个 Uri 对象。让我们实现 DataMapItem。

使用 DataMapItem 对象。fromDataItem()方法

声明一个名为 dataMapItem 的 DataMapItem,并将其设置为等于一个**的结果。from dataitem(configDataItem)**方法使用下面的语句调用 DataMapItem 对象,该语句在图 13-33 : 中突出显示

DataMapItem dataMapItem = DataMapItem.fromDataItem(configDataItem);

9781430265504_Fig13-33.jpg

图 13-33 。声明名为 dataMapItem 的 DataMapItem,并将其设置为等于 fromDataItem(configDataItem)调用

接下来,让我们看看 Android GMS DataMap 类,然后您将完成 onResult()方法结构的 if()部分的编码。

安卓资料图

Android GMS public DataMap 类扩展了 java.lang.Object,是com . Google . Android . GMS . wearable包的一部分。GMS 数据映射 Java 类层次结构如下所示:

java.lang.Object
  > com.google.android.gms.wearable.DataMap

数据映射对象包含由 PutDataMapRequestDataMapItem 对象支持的数据映射。数据映射对象也可以与捆绑对象相互转换。值得注意的是,在此数据转换过程中,此转换过程将删除 DataMap 对象未明确支持的任何数据类型。

DataMap 类有一个公共构造函数方法 DataMap()和 55 个公共方法,我无法在这里一一介绍。我将介绍一些你可能想在穿戴应用开发中使用的更有用的方法:

  • clear( ) 方法将从调用该方法的数据映射对象的映射中删除所有数据元素(因此它清除了一个数据映射)。
  • 。containsKey(String key) 如果所请求的键包含在调用方法的 DataMap 对象的映射中,方法将返回 true。
  • 。fromByteArray(byte[ ] bytes) 方法将返回一个数据映射对象,给出一个字节数组,在 Java 中注释为 byte[ ]。
  • 。getByte(String key) 方法可以返回一个与给定键相关联的值,如果给定键不存在所需类型的映射,则返回字节 0。
  • 。getByte(String key,byte defaultValue) 方法返回与给定键相关联的数据值,如果所提供的键不存在所需类型的映射,则返回 defaultValue。
  • **。getByteArray(String key)**方法将返回与给定键关联的值,如果给定键不存在所需类型的映射,或者如果空值与该键显式关联,则返回 null。
  • 。getDataMap(String key) 方法将返回一个与给定键相关联的值,如果给定键不存在所需类型的映射,则返回 null;如果与该键显式相关联,则返回 null 值。
  • 一个**。** 如果数据映射的映射为空,isEmpty( ) 方法返回 true。一个**。** keySet( ) 方法将返回一个集合,包含在数据映射中用作键的字符串。
  • 。putAll(DataMap dataMap) 方法可以将给定数据映射中的所有映射插入到数据映射中。。putByte(String key,byte value) 将一个字节值插入到数据映射的映射中,替换提供的键的任何现有值。
  • 。putByteArray(String key,byte[ ] values) 方法将一个字节数组值插入到该数据映射的映射中,替换所提供键的任何当前存在的值。
  • 。putDataMap(String key,DataMap value) 方法将数据映射插入到目标数据映射的映射中。这将替换所提供键值的任何现有数据映射。
  • toByteArray( ) 方法可以返回序列化的 byte[ ]数组对象,将 DataMap 的内容表示为数组。
  • 。remove(String key) 方法有选择地从目标数据映射的映射中删除任何带有所提供键值的数据条目。
  • size( ) 方法将返回数据映射中键值对的数量。
  • toBundle( ) 方法将返回(创建)一个 Bundle 对象,该对象包含目标数据映射中包含的所有元素,该方法已被调用。

创建数据映射对象:使用. getDataMap()方法

声明一个名为 configDataMap 的数据映射对象,并使用实例化它。getDataMap( ) 方法,在前面的代码行中创建的 dataMapItem 对象中调用。这是通过以下语句完成的,该语句在图 13-34 中突出显示。不要忘记使用一个 Alt+Enter 工作流程,让 IntelliJ 为您编写导入数据映射 Java 导入语句:

DataMap configDataMap = dataMapItem.getDataMap( );

9781430265504_Fig13-34.jpg

图 13-34 。声明名为 configDataMap 的数据映射,并将其设置为等于 dataMapItem.getDataMap()调用

因为在本章中您将实现核心的 Google 移动服务引擎,所以接下来您将在 ProWatchFaceConfigListenerService 类的主体中完成 onMessageReceived()方法的编码。

创建监听程序服务:。onMessageReceived()

关闭 IntelliJ 中所有打开的编辑选项卡,进入 wear/java 文件夹,使用右键跳转到源工作流程打开 ProWatchFaceConfigListenerService 类,如图图 13-35 所示。

9781430265504_Fig13-35.jpg

图 13-35 。关闭所有选项卡,进入 wear/java,右键单击并打开 ProWatchFaceConfigListenerService

正如你在图 13-36 中看到的,因为 IntelliJ 为你创建了空类,你需要下拉错误助手对话框菜单并选择一个实现方法选项。这将允许 IntelliJ 完成空类的创建,覆盖所有需要实现的方法。

有趣的是,您不会在这些方法中添加任何新的(专有的)代码,但是请注意,这并不意味着这些方法不做任何事情(超类将实现它们的默认行为代码),只是它们不为这个实现做任何其他事情(定制的或新的)。

现在您可以声明mygoogle API clientGoogle API client 并开始工作了!

9781430265504_Fig13-36.jpg

图 13-36 。单击红色错误灯泡下拉菜单,选择实施方法选项,单击确定按钮

在类的顶部声明私有的 mygoogle API clientgoogleapi client 对象,正如在图 13-37 中突出显示的那样,这样你就可以在一个**on messagereceived(MessageEvent message event)**方法结构中构建它,在简单讨论了这个 message event 类之后,你将对其进行编码。

9781430265504_Fig13-37.jpg

图 13-37 。添加一个名为 myGoogleApiClient 的私有 GoogleApiClient 对象;将@Override 方法留空

Android MessageEvent 类:处理消息

Android GMSpublic message event 接口处理关于监听器接收到的消息的信息,在本例中是可穿戴监听器服务。它包含在com . Google . Android . GMS . wearable包中。

该接口公开了四个公共方法:。getData( ) 方法,该方法返回用消息传递的数据;。getPath( ) 方法,该方法返回消息被传递到的路径;。getRequestId( ) 方法,该方法返回发送方生成的消息的请求 ID;还有那个**。getSourceNodeId( )** 方法,该方法返回发送方的节点 Id。您将使用。getData()方法提取您的设置。

实现 MessageEvent 对象:提取数据

接下来让我们覆盖 onMessageReceived()方法,因为这将是 ConfigListenerService 用来处理 MessageEvent 对象中保存的配置设置数据的主要方法。使用以下 Java 代码创建空的**public void on messagereceived(message event message event)**方法结构,如图图 13-38 所示:

@Override
public void onMessageReceived(MessageEvent messageEvent) {
    // Message Event Processing Code goes in here
}

9781430265504_Fig13-38.jpg

图 13-38 。添加一个公共 void onMessageReceived(message event)方法;使用 Alt+Enter 导入该类

在 onMessageReceived()方法中,声明名为 rawData 的 byte[ ] 数组,然后将其设置为等于。getData()方法 使用以下 Java 代码调用 messageEvent MessageEvent 对象:

byte[ ] rawData = messageEvent.getData( );

请记住,这个 messageEvent 对象是传递给 onMessageReceived()方法的对象。下一行 Java 代码将创建 DataMap 对象,我们将其命名为 keysToOverwrite ,然后使用加载它。 fromByteArray( ) 方法调用,使用下面的 Java 代码语句:

DataMap keysToOverwrite = DataMap.fromByteArray(rawData);

正如你在图 13-39 中看到的红色错误代码,你必须使用 Alt+Enter 组合键,并让 IntelliJ 导入数据映射类以在 ProWatchFaceConfigListenerService 类中使用。

9781430265504_Fig13-39.jpg

图 13-39 。添加一个名为 rawData 的字节数组,并使用。getData()方法来加载它;添加数据映射对象

创建 onMessageReceived()方法的下一步是创建两个空的条件 if()结构来保存条件处理任务。

其中一个将在 GoogleApiClient 对象为空(null 值)时使用,另一个将在 GoogleApiClient 存在但当前未连接时使用。这两个条件 if 结构的 Java 代码可以是 见图 13-40 ,应该看起来像下面的 Java 代码:

if( myGoogleApiClient ==  null ) {
      // Code to Create and Build a GoogleApiClient Object
}
if( !myGoogleApiClient.isConnected( ) ) {
    // Code to Execute if GoogleApiClient is NOT connected
}

9781430265504_Fig13-40.jpg

图 13-40 。添加两个条件 if()语句,以查看客户端对象是否为空,或者客户端对象是否未连接

如果 myGoogleApiClient 对象中最初没有加载任何数据,也就是说,如果 myWatchFaceClient 对象具有空值,那么您可能希望使用嵌套的 builder helper 类创建一个新的 GoogleApiClient 对象。

这是同一个 GoogleApiClient。您之前为 ProWatchFaceCompanionConfigActivity.java 类编写的构建器代码,它应该类似于下面的 Java 代码块,如图 13-41 所示:

if( myGoogleApiClient == null ) {
    myGoogleApiClient = new GoogleApiClient.Builder(this)
        .addConnectionCallbacks(this)
        .addOnConnectionFailedListener(this)
        .addApi(Wearable.API)
        .build( );
}

正如你在图 13-41 中看到的,你需要使用 Alt+Enter 组合键,并让 Intelli-J 导入 Android Wearable 类,这样你就可以在 GoogleApiClient 内部使用它的 API。构建器方法链。addApi()方法调用。

接下来,您需要编写另一个条件 if()结构来评估 GoogleApiClient 对象(如果它不为 null),以查看它是否连接到服务器。如果它没有连接到 Google Play 服务服务器,那么您将需要使用编写一些代码。 blockingConnect( ) 方法,将 Wear 应用连接到 Google Play 服务服务器。

9781430265504_Fig13-41.jpg

图 13-41 。实例化和。build()一个新的 GoogleApiClient。如果 myGoogleApiClient 对象为 null(空),则为生成器对象

在实现 if(!idspnonenote)之前。myGoogleApiClient.isConnected())结构,让我们更仔细地看看 ConnectionResult 类(和对象),您将使用它来提取 WatchFaces 配置数据。之后,您可以完成 onMessageReceived()方法的实现,然后在下一章中,您将创建一个配置工具类,将所有这些整合在一起。

ConnectionResult 类:连接到网络

Android 的public final****connection result类扩展 Java.lang.Object 并实现 Parcelable 。它的 Java 类层次结构如下所示:

java.lang.Object
  > com.google.android.gms.common.ConnectionResult

ConnectionResult 类包含当客户端无法连接到 Google Play 服务时使用的所有可能的网络连接错误代码。

GoogleApiClient 使用错误代码。onconnectionfailed listener helper 类,它们都列在表 13-1 中,以便您熟悉它们。这些是 Google Play 服务服务器在出现问题时产生的错误。与 Google Play 合作将是 Wear 开发者需要掌握的重要领域;因此,我想详细介绍一个 ConnectionResult 常量表,因为您需要知道这些标志。

ConnectionResult 类有一个创建 ConnectionResult 对象的公共构造函数方法。这个公共构造函数方法采用格式connection result(int status code,PendingIntent pendingIntent) 。这个类有十个公共方法,包括一个**。方法描述内容();。equals( )** 方法;。getErrorCode( ) 方法,指示中断连接的错误类型;。getResolution( ) 方法,包含解决失败连接的待定意图,以及**。hasResolution( )** 方法,调用将返回 true。startResolutionForResult(Activity,int) 并将启动任何可能需要用户交互的意图对象。

表 13-1 。ConnectionResult 类常量及其对网络连接状态的意义

|

连接结果常量

|

它意味着什么

| | --- | --- | | API _ 不可用 | 您试图连接的 API 组件不可用 | | 已取消 | 客户端通过调用。断开( ) | | 开发者 _ 错误 | 该应用不知何故被错误配置了 | | 内部错误 | 出现内部错误 | | 中断 | 等待连接完成时发生中断 | | 无效 _ 账户 | 客户端试图使用无效的帐户名连接服务 | | 许可证检查失败 | 该应用未授权给用户 | | 网络 _ 错误 | 出现网络连接或传输错误 | | 分辨率 _ 要求 | 完成连接需要某种形式的解决方案 | | 服务 _ 禁用 | 设备上已安装的 Google Play 服务版本被禁用 | | 服务 _ 无效 | 设备上安装的 Google Play 服务不可信 | | 服务 _ 缺失 | 设备中缺少 Google Play 服务应用软件 | | 服务 _ 更新 _ 必需 | 安装的 Google Play 服务版本已过期 | | 登录失败 | 客户端试图连接到未登录的服务用户 | | 需要登录 | 客户端试图连接到未登录的服务用户 | | 成功 | 您的连接已成功协商 | | 超时 | 等待连接完成时超时 |

有一个**。isSuccess( )** 方法,如果连接成功,则返回 true。一个**。startResolutionForResult(Activity Activity,int requestCode)** 方法通过启动任何需要用户交互的 Intent 对象来解决网络错误。还有一个**。toString( )** 将结果数据转换成文本的方法和一个**。writeToParcel(Parcel out,int flags)** 方法将结果写入一个 Parcel 对象格式。现在,让我们实现一个 ConnectionResult!

实现 ConnectionResult:阻塞连接

下一个条件 if()结构将确定 GoogleApiClient 对象是否连接到 Google Play 服务服务器,方法是使用 myGoogleApiClient.isConnected()方法调用的反义词(由前面的感叹号表示的 Java NOT 操作数)。如果 GoogleApiClient 对象没有连接,您将声明一个 ConnectionResult 对象,将其命名为 myConnectionResult ,并将其设置为等于**。blockingConnect( )** 方法调用关闭 myGoogleApiClient 对象。使用带有时间单位的 30 整数值,给网络连接尝试 30 秒。秒常量重试后续连接尝试。这将通过使用以下 Java 代码来完成,如图 13-42 底部突出显示的:

if( !myGoogleApiClient.isConnected( ) ) {
    ConnectionResult myConnectionResult = myGoogleApiClient.blockingConnect(30, TimeUnit.SECONDS);
}

9781430265504_Fig13-42.jpg

图 13-42 。声明并命名 myConnectionResult 对象,并使用。blockingConnect()方法

对于本章来说,这是一个完美的停止(休息)点,这一章涵盖了 Google Play 服务和 API,这些 API 允许我们在下一章开始编写配置常量工具类之前与它连接和对话。

摘要

在本章中,您学习了 Android 中的 Google 移动服务(GMS)包,它是用于与 Google Play 服务服务器交互的 API。您在编写实现 WatchFace 配置应用所需的另外两个类的上下文中看到了这一点。

在下一章中,您将编写第三个类,一个工具类,然后您将开始在真实世界的设备上测试 watch faces 并使用真实世界的服务!

首先,您创建了ProWatchFaceCompanionConfigActivity.java类和使其运行所需的 XML 素材,然后您创建了 GoogleApiClient 对象,这样您就可以与 Google 的 Play 服务联网。您学习了如何使用 GoogleApiClient 构建客户端。构建器以及如何用 DataItem、DataMapItem 和 DataMap 对象(类)封装数据,以及这些如何与您的手表外观配置数据相关联。

然后你创建了一个ProWatchFaceConfigListenerService.java类,它将与伴随的活动类 ProWatchFaceCompanionConfigActivity 以及你将在下一章创建的工具类一起使用。

在下一章中,您将创建 ProWatchFaceUtility 类,并将它连接到您在本章中创建的类,以及您在本章之前的章节中创建的 watch face 应用。**

十四、表盘配置伴侣活动工具和可穿戴 API

现在,您已经为项目的移动部分中的 watch face companion 配置活动编写了基础活动类,是时候创建将存在于项目的穿戴部分中的 ProWatchFaceUtility 类了。该类将定义配置设置常量,并包含操作它们的函数(方法),使用数据映射、数据项目和数据映射项目对象,以及可穿戴类的 API数据 Api节点 Api消息 Api 属性(数据字段)。在这一章中,你将会学到所有关于这个可穿戴类及其 API 的知识。

在本章中,您还将使用 XML 标记来为“观察界面配置”活动创建用户界面设计。这个 UI 设计将使用四个微调器小部件,这将允许您的用户选择八种标准系统颜色中的一种,用于您的表盘设计元素,包括刻度线、时针、分针和秒针。

在这一章中,你将学习并使用 Java 代码实现 Android Wearable class 数据字段 API 的每一个属性。其中包括 API数据 Api节点 Api消息 Api 。您还将了解 Android LinearLayout 容器类以及 Android 微调器TextView 小部件。

您将实现一个复杂的 Android 工具类,它使用 DataAPI、NodeAPI 和 MessageAPI 在 GMS 网络上进行对话。您将开发键-值数据对,该数据对将为 DataMap、DataItem 和 DataMapItem 对象定义表面配置数据,并且您将在 Java 代码中实现这一点。

ProWatchFaceUtility 类:管理数据

在第一部分中,您将为表盘用户手机上的对话框创建配套配置基础的最后一个类,该类允许用户配置他们的表盘设计颜色。

Pro WatchFaces 工具类将存在于 Android Studio 项目的 wear/java 部分,以及 ProWatchFaceConfigListenerService 和呈现手表表面本身的初始 ProWatchFaceService 类。

在您为希望您的用户能够为手表界面交互模式配置的颜色值创建了 ProWatchFaceUtility.java 类结构和常量之后,我将讨论 PutDataMapRequest 类,然后您将编写代码来实现一个工具类,使用您在过去几章中学习的类。

创建 ProWatchFaceUtility 类:定义常数

右键单击/wear/java/package 文件夹,如图 14-1 所示,选择新建image Java 类菜单序列,进入新建类对话框。因为这是 utility 类,所以我们将其命名为 ProWatchFaceUtility 并在对话框的下拉中选择类的种类作为,如图图 14-1 右上角所示。单击 OK 按钮并创建该类,该类将在 IntelliJ 中的新编辑选项卡中打开。

9781430265504_Fig14-01.jpg

图 14-1 。使用新的image Java 类菜单序列和对话框来创建你的 ProWatchFaceUtility 类

在 public 关键字之后、类声明和名称之前添加 Java final 关键字,使您的类成为 final。这将锁定它,仅用作 WatchFaces 配置工具。

接下来,添加五个公共静态最终字符串常量。第一个应该是一个 PATH_WITH_FEATURE 常量,它为 watch face companion 应用提供一个唯一标识符 ,就像您在自己的 mobile 应用组件 watchfaceconnectionconfigactivity 中创建的那样。如果您创建了多个 Watch Faces API 应用,那么每个应用都应该有一个唯一的 ID,它是不同的 Watch Faces 组件通过 GMS 网络基础设施与匹配的应用组件进行对话的方式。

四个颜色常量允许用户定义表盘设计特征所用的颜色,即刻度线、时针、分针和秒针。对于 DataItem 和 DataMap 对象中使用的键值对,您将需要颜色常量 。让我们先使用设计元素类型前的 KEY_COLOR 创建键常量,这样 KEY_COLOR_TICK_MARK 将表示您的刻度线颜色配置数据值的键值。

您的最终类声明,连同五个 Java 常量声明,如图 14-2 中突出显示的那样,应该看起来像下面的代码:

public final class ProWatchFaceUtility {
    public static final String PATH_WITH_FEATURE = "/watch_face_config/ProWatchFace";
    public static final String KEY_COLOR_TICK_MARK = "COLOR_TICK_MARK";
    public static final String KEY_COLOR_HOUR_HAND = "COLOR_HOUR_HAND";
    public static final String KEY_COLOR_MINUTE_HAND = "COLOR_MINUTE_HAND";
    public static final String KEY_COLOR_SECOND_HAND = "COLOR_SECOND_HAND";
}

9781430265504_Fig14-02.jpg

图 14-2 。将最后一个修饰符添加到类声明以及路径和交互模式颜色值的常量中

现在你已经定义了你的键串(听起来像是一个国家的热门话题:“你定义了我的键串宝贝”),让我们为每一个定义 Android 颜色类常量,这将定义默认的颜色设置。到目前为止,这个应用包括白色(刻度)、蓝色(小时)、绿色(分钟)和红色(秒)。

确保使用 Android 系统颜色常量,因为这些常量已经在操作系统中为您定义了。稍后你可以使用**。** parseColor( ) 方法将这些转化为 Android OS 可以使用的整数颜色数据值。这显示在图 14-3 的中间部分,使用以下 Java 代码:

public static final String COLOR_TICK_MARK_INTERACTIVE = "White";
public static final String COLOR_HOUR_HAND_INTERACTIVE = "Blue";
public static final String COLOR_MINUTE_HAND_INTERACTIVE = "Green";
public static final String COLOR_SECOND_HAND_INTERACTIVE = "Red";

9781430265504_Fig14-03.jpg

图 14-3 。创建一个私有的静态 int parseOptionColor()方法,将颜色字符串转换为颜色类常量

让我们创建一个名为私有静态方法。parseOptionColor( )返回一个表示 Android 颜色类常量的 int (整数)数据值。

这个方法将接受一个字符串参数,您将把它命名为 optionColor ,并使用将颜色值作为一个整数返回。 toLowerCase( ) 方法调用,关闭 optionColor 参数。这是在 Color.parseColor( ) 方法调用的参数区域内完成的,因为您总是希望编写密集的 Java 代码。你的 Java 方法结构,可以在图 14-3 的底部看到突出显示,应该看起来像下面的 Java 代码:

private static int parseOptionColor(String optionColor) {
    return Color.parseColor(optionColor.toLowerCase( ));
}

确保单击红色的错误代码突出显示,并使用 Alt+Enter 工作流程让 IntelliJ 编写您需要的导入语句。

下一步是使用。您编码的 parseOptionColor()方法来创建 COLOR_VALUE 常量。这些将用于您的键-值对的第二部分,用于 DataItem 和 DataMap 对象中,并将使用与您的默认颜色常量相同的常量名称,除了您将在单词 COLOR 之后插入单词 VALUE ,以便您的常量更具描述性,例如:COLOR _ VALUE _ TICK _ MARK _ INTERACTIVE

四个方法调用的 Java 代码,为你的键-值数据对的值部分设置常量值,可以在图 14-4 的最底部看到,应该看起来像下面四个 Java 语句:

public static final int COLOR_VALUE_TICK_MARK_INTERACTIVE =
                                           parseOptionColor(COLOR_TICK_MARK_INTERACTIVE);
public static final int COLOR_VALUE_HOUR_HAND_INTERACTIVE =
                                           parseOptionColor(COLOR_HOUR_HAND_INTERACTIVE);
public static final int COLOR_VALUE_MINUTE_HAND_INTERACTIVE =
                                           parseOptionColor(COLOR_MINUTE_HAND_INTERACTIVE);
public static final int COLOR_VALUE_SECOND_HAND_INTERACTIVE =
                                           parseOptionColor(COLOR_SECOND_HAND_INTERACTIVE);

9781430265504_Fig14-04.jpg

图 14-4 。创建调用 parseOptionColor()方法来配置自身的 COLOR_VALUE 常量

现在,您已经定义了十几个常数,并编写了一个非常小但有用的实用方法,是时候进入一些更复杂的方法编码,并了解另一个与通过网络发送 DataItem 对象(和 DataMap 对象)相关的 Android GMS 类了。

接下来您将创建的方法将常量值加载到 DataMap 对象中,并将它们作为 DataItem 对象提交给 GMS。

将 DataItems 载入至 DataMap: .putConfigDataItem()

让我们创建一个**公共静态 void****putConfigDataItem()**方法,它在参数列表区域接受一个 GoogleApiClient 对象和一个 DataMap 对象。

将 GoogleApiClient 对象命名为 googleApiClient ,将 DataMap 对象命名为 newConfigData 。因为在这个 Java 类中您还没有使用这些对象(类)类型,所以您将得到红色的错误代码高亮显示,为此您将需要使用 Alt+Enter 工作进程来让 IntelliJ 为您编写这些类的导入语句。

空的 Java 方法结构在图 14-5 底部用黄色突出显示,看起来应该像下面的 Java 代码:

public static void putConfigDataItem(GoogleApiClient googleApiClient, DataMap newConfigData) { }

9781430265504_Fig14-05.jpg

图 14-5 。创建一个公共静态 void putConfigDataItem(Google API client,DataMap)空方法结构

在使用 PutDataMapRequest 类和对象(这是该方法的核心)之前,接下来让我们了解一下这个 Android GMS 类。

Android PutDataMapRequest 类:放入数据请求

Android GMSpublic****PutDataMapRequest类扩展了 java.lang.Object,包含在com . Google . Android . GMS . wearable包中。该类的 Java 层次结构如下所示:

java.lang.Object
  > com.google.android.gms.wearable.PutDataMapRequest

PutDataMapRequest 类是 PutDataRequest 类的支持数据映射的版本。该类有六个公共方法,这些方法与向 Google Play 服务服务器输入数据地图请求相关。

asPutDataRequest( ) 方法,当您写完 putConfigDataItem()方法后,您将在本章的下一节中使用它,它从 PutDataMapRequest 对象创建 PutDataRequest 对象。

您还将使用**。create(String path)** 方法,该方法将使用自定义 PATH_WITH_FEATURE(常量)路径创建 PutDataMapRequest 对象。还有一个**。使用 DataMapItem 对象从 DataMapItem 创建 PutDataMapRequest 的方法。一个**。createwitheautoappendedid(String path prefix)方法使用随机生成的 Id 创建一个自动化的 PutDataMapRequest,并以您的路径为前缀。

有一个**。** getDataMap( ) 方法,您将使用它从 PutDataMapRequest 对象中提取一个 DataMap 对象,以及一个**。** getUri( ) 方法如果要从 PutDataMapRequest 对象中提取 Uri 对象。

使用 putdatamaprequest 放置配置数据项

现在让我们实现一个 PutDataMapRequest 对象。我将向您展示如何在 ProWatchFaceUtility.java 类中使用它来将您的配置数据项放到 Google Play 中。声明并实例化 PutDataMapRequest,将其命名为 putDataMapRequest 。将此设置为调用**。** create( ) 方法关闭 PutDataMapRequest 类,如图图 14-6 所示,使用以下 Java 代码:

PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(PATH_WITH_FEATURE);

9781430265504_Fig14-06.jpg

图 14-6 。创建名为 putDataMapRequest 的 PutDataMapRequest 对象,并使用。创建( )

您将把 PATH_WITH_FEATURE 常量传递到。create()方法向 Google Play 服务标识这个 PutDatamapRequest 对象与您的特定 WatchFaces API 应用相关。想想这就像给你的应用打上独特的品牌,这样 GMS 就不会把事情搞混了。

现在,您已经创建了一个 PutDataMapRequest 对象,该对象带有 watch face 应用的唯一路径说明符,下一步是通过使用从 PutDataMapRequest 对象中提取一个“空”的 DataMap 对象,从而为这个 PutDataMapRequest 对象创建 DataMap 对象。 getDataMap( ) 方法。你完成的(空的)Java 类声明看起来像下面的 Java 结构,这也可以在图 14-7 中看到:

DataMap configurationToPut = putDataMapRequest.getDataMap( );

9781430265504_Fig14-07.jpg

图 14-7 。创建名为 configurationToPut 的数据映射对象;用 putDataMapRequest.getDataMap()加载它

正如你在图 14-7 中看到的,你需要使用一个 Alt+Enter 组合键来让 IntelliJ 为你编写数据映射类导入语句。现在可以用配置数据加载空的 DataMap 对象了。

为此,使用**。** putAll( ) 方法带有传递到 putConfigDataItem()方法中的 newConfigData DataMap 对象。这就是包含您的配置参数的数据映射将如何替换位于 PutDataMapRequest:

configurationToPut.putAll(newConfigData);

正如你在图 14-8 中注意到的,这一行代码不需要导入并且没有错误,所以你现在可以进入最复杂的 Java 语句,你将为这个 putConfigDataItem()方法编码,该方法与使用将 PutDataMapRequest 放入一个可穿戴DataApi 对象有关。putDataItem( )

9781430265504_Fig14-08.jpg

图 14-8 。使用。putAll()将 newConfigData 数据映射加载到 configurationToPut 数据映射中,并调用。putDataItem()

在您在 Java 代码的其余部分中使用 Android Wearable 类来访问它的各种 API 之前,让我们仔细看看这个类。通过这种方式,您将熟悉它的 API 类型、数据字段和嵌套类。

Android 可穿戴类:Android 的可穿戴 API

Android 公共可穿戴类扩展了 java.lang.Object,是com . Google . Android . GMS . Wearable包的一部分。需要注意的是,Wearable class 和 wearable package 使用了相同的名称,所以面向 Android 的 Wearable 既是一个包,也是一个类,smartwatch 平台也是如此。

Wearable 类(和对象)包含 Android Wear 平台的可穿戴 API。这个可穿戴类的类层次结构如下所示,表明这个可穿戴类是为 Android Wear 临时编写的:

java.lang.Object
  > com.google.android.gms.wearable.Wearable

有一个嵌套的助手类,即 Wearable。WearableOptions 类,包含 Android Wearable API 的 API 配置参数。

可穿戴类或对象包含的四个属性、特性或数据字段代表主要 API,以及网络节点 API、消息传递 API 和数据 API。

公共静态最终 Api <可穿戴。WearableOptions > API 字段包含一个要传递给的令牌对象。addApi(Api) 方法启用 Wearable Options 对象中概述的可穿戴选项(特性)。公共静态最终数据 Api 数据 Api 字段包含数据 Api,而公共静态最终消息 Api 消息 Api 字段包含消息 Api,并且公共静态最终节点 Api 节点 API 字段包含网络节点 API。

使用可穿戴类:放置一个 DataApi 数据请求

您将要编写的接下来的七行 Java 代码实际上是一个复杂的 Java 语句,它既有方法(点)链接,又有嵌套在。setResultCallback()方法!这个构造从一个可穿戴的类开始,引用它的数据 Api 字段,其中有一个**。** putDataItem( ) 方法被调用。

在里面。putDataItem()参数区域,你传递名为的 GoogleApiClient 对象 googleApiClient 以及一个的结果。asPutDataRequest( ) 方法调用 putDataMapRequest 对象,将其转化为 PutDataRequest 对象。PutDataRequest 类(和对象)用于在 Android Wear 网络中创建新的 DataItem 对象。

那个。putDataItem()方法调用需要将 GoogleApiClient 对象和 PutDataRequest 对象作为参数传入。Java 语句的这一部分将请求提交给 GMS 服务器。

语句的下一部分,连接到。putDataItem()方法使用点链接,就是**。setResultCallback( )** 方法,该方法设置应用监听来自 GMS 服务器的数据请求响应。

在里面。setResultCallback()方法的参数区,你实例化一个新的 ResultCallback < DataApi。>dataitem result对象。在该构造中,您@覆盖了**public void****on result()**方法,使其为空,以便它执行 DataItemResult 对象(包含在 ResultCallback 对象中)的默认处理。如图图 14-8 所示,Java 语句应该看起来像下面的 Java 代码结构:

Wearable.DataApi.putDataItem(googleApiClient, putDataMapRequest.asPutDataRequest( ))
                .setResultCallback(new ResultCallback<DataApi.DataItemResult>( ) {
                    @Override
                    public void onResult(DataApi.DataItemResult dataItemResult) {
                        // an empty method represents using the default onResult( ) functionality
                    }
                });

现在您已经处理了 DataApi 请求,让我们看看节点 Api。

使用 Android 的节点 API:。fetchConfigDataMap()方法

让我们创建一个**。** fetchConfigDataMap( ) 方法 将使用节点 API 来获取(检索)配置数据映射对象。声明一个公共静态 void fetchConfigDataMap( ) 方法,带有名为 clientfinal GoogleApiClient 对象和名为 callbackfinal fetchConfigDataMap callback对象。

由此产生的空类结构可以在图 14-9 的中看到,并将利用以下 Java 代码:

public static void
fetchConfigDataMap( final GoogleApiClient client, final FetchConfigDataMapCallback callback ) {
    // Empty Method
}

9781430265504_Fig14-09.jpg

图 14-9 。在常量后面创建一个空的公共静态 void fetchConfigDataMap()方法结构

在图 14-9 中可以看到,FetchConfigDataMapCallback 有红色代码错误高亮显示,因为它引用的公共接口还没有创建。如果您下拉一个错误建议选项列表,您将看到“创建公共接口 FetchConfigDataMapCallback”,现在我们就这么做吧。在类顶部的常量后添加一个公共接口 FetchConfigDataMapCallback 。在公共接口内,声明实现所需的**void onconfigdatamappled(data map config)**方法。

FetchConfigDataMapCallback 接口的 Java 结构可以在图 14-10 的底部高亮显示,看起来应该像下面的 Java 公共接口结构:

public interface FetchConfigDataMapCallback {
    void onConfigDataMapFetched(DataMap config);
}

9781430265504_Fig14-10.jpg

图 14-10 。使用 onConfigDataMapFetched()方法创建公共接口 FetchConfigDataMapCallback

在 FetchConfigDataMap()方法中,您将使用一个 NodeApi 类和对象,所以让我们先快速概述一下这个 Android GMS 类。

Android NodeApi 接口:搜索连接的节点

Androidpublic****NodeApi 接口 为你的 Wear 应用公开 Api,以利用 poll (即搜索)本地或连接的节点。这个 NodeApi 接口是com . Google . Android . GMS . wearable . NodeApi包的一部分。节点 API 事件可以传递给设备上的所有应用,您将学习如何监听和利用这些节点事件。这个 NodeApi 接口有三个嵌套的(也称为 helper)接口:

  • NodeApi。GetConnectedNodesResult 接口包含 Wear GMS 网络上已连接节点的列表。
  • NodeApi。GetLocalNodeResult 接口包含一个惟一的名称和一个 ID,它将惟一地代表用户的硬件设备。在本章的下一节,你将在你的 Java 代码中使用这个接口。
  • 一个 NodeApi。NodeListener 接口旨在与 Android 的一起使用。addListener(GoogleApiClient,NodeApi。NodeListener) 方法来接收节点 API 事件,以便它们可以被处理。

这个 NodeApi 接口有四个公共方法:

  • PendingResult **。addListener(GoogleApiClient 客户端,NodeApi。**方法注册一个能够接收(过滤)所有节点 API 事件的监听器。
  • <nodeapi.getconnectednodesresult>抽象 pending result**。getConnectedNodes(Google API client 客户端)**方法将获得用户硬件设备当前连接的节点列表。</nodeapi.getconnectednodesresult>
  • <nodeapi.getlocalnoderesult>抽象 pending result**。**方法 getLocalNode(GoogleApiClient)将获取指向用户当前硬件设备的节点。在本章的下一节,你将在你的 Java 代码中使用这个方法。</nodeapi.getlocalnoderesult>
  • 抽象 pending result**。removeListener(GoogleApiClient 客户端,NodeApi。NodeListener listener)** 方法将删除先前使用。addListener(GoogleApiClient,NodeListener)。

现在让我们完成。fetchConfigDataMap()方法,该方法使用与 NodeApi 相关的方法和类。

线束节点 Api:使用 getLocalNode()和 getLocalNodeResult()

在一个空的 fetchConfigDataMap()方法结构中,访问 Android 的 WearableNodeApi 接口,使用点符号。叫**。getLocalNode( )** 关闭该构造,并传入名为 client 的 GoogleApiClient 对象,该对象被传入该 fetchConfigDataMap()方法。

接下来,使用 Java 方法链接并添加一个**。** setResultCallback( ) 方法调用。在其中,使用 Java new 关键字实例化 ResultCallback 对象,为 **< NodeApi 创建一个 ResultCallback 对象。getlocalnodesult>**对象类型,使用 ResultCallback < NodeApi 指定。GetLocalNodeResult > Java 构造。在这个构造中,您将@Override 一个 onResult()方法,接下来您将使用 Java 代码完成该方法,它将决定当您获得 LocalNode 结果时您希望发生什么。

目前为止 fetchConfigDataMap()方法的 Java 结构,包括 Wearable。NodeApi 结构,获取localnodesult,设置一个结果回调,以及你(当前)空的 onResult( ) 方法 体,可以在图 14-11 中看到,应该看起来像下面的 Java 代码:

public static void
fetchConfigDataMap(final GoogleApiClient client, final FetchConfigDataMapCallback callback)
{
    Wearable.NodeApi.getLocalNode(client)
                    .setResultCallback(new ResultCallback<NodeApi.GetLocalNodeResult>( ) {
                        @Override
                        public void onResult(NodeApi.GetLocalNodeResult getLocalNodeResult) {
                            // Java code to be executed when a LocalNodeResult object appears
                        }
                    });
}

9781430265504_Fig14-11.jpg

图 14-11 。调用. getLocalNode()和。setResultCallback()并覆盖 onResult()方法

在 onResult(NodeApi。getlocalnodesult getlocalnodesult)方法,您将声明名为 myLocalNode 的 String 对象,并使用的结果加载它。getNode()。getId( ) 方法链,关闭了getlocalnodesult对象,该对象已经被传递到这个 onResult()方法结构中。

一旦 myLocalNode 加载了这个 NodeApi 标识数据,您将声明一个名为 uri 的 Uri 对象,并使用一个 Uri 实例化它。构建器类结合 new 关键字的构造器方法。使用方法链接,你将设置 URI 方案为 wear ,路径为 PATH_WITH_FEATURE ,权限为 myLocalNode String 对象中的 ID 数据,如图图 14-12 所示。到目前为止,您的 Java 代码应该如下所示:

Wearable.NodeApi.getLocalNode(client)
                .setResultCallback(new ResultCallback<NodeApi.GetLocalNodeResult>( ) {
                    @Override
                    public void onResult(NodeApi.GetLocalNodeResult getLocalNodeResult) {
                        String myLocalNode = getLocalNodeResult.getNode( ).getId( );
                        Uri uri = new Uri.Builder( )
                                         .scheme("wear")
                                         .path(ProWatchFaceUtility.PATH_WITH_FEATURE)
                                         .authority(myLocalNode)
                                         .build( );
                    }
                });

9781430265504_Fig14-12.jpg

图 14-12 。添加一个 myLocalNode 字符串来提取 LocalNodeResult 节点数据,并使用它构建一个 Uri 对象

现在您已经有了 GoogleApiClient 和 Uri 对象,您可以在 Wearable.DataApi 之外的. getDataItem()方法调用中使用它们。setResultCallback()方法,请求传递 FetchConfigDataMapCallback 对象回调的新 DataItemResultCallback()构造函数,该对象回调被传递到此 fetchConfigDataMap()方法构造中。正如你将在图 14-13 中看到的,你完成的 Java 方法结构看起来像这样:

public static void
fetchConfigDataMap(final GoogleApiClient client, final FetchConfigDataMapCallback callback)
{
  Wearable.NodeApi.getLocalNode(client)
                  .setResultCallback(new ResultCallback<NodeApi.GetLocalNodeResult>( ) {
                      @Override
                      public void onResult(NodeApi.GetLocalNodeResult getLocalNodeResult) {
                          String myLocalNode = getLocalNodeResult.getNode( ).getId( );
                          Uri uri = new Uri.Builder( )
                                           .scheme("wear")
                                           .path(ProWatchFaceUtility.PATH_WITH_FEATURE)
                                           .authority(myLocalNode)
                                           .build( );
                          Wearable.DataApi.getDataItem(client, uri)
                                          .setResultCallback(new DataItemResultCallback(callback));
                      }
                  });
}

9781430265504_Fig14-13.jpg

图 14-13 。打电话给。【getDataItem()方法关闭可穿戴。DataApi,将其传递给客户机和新的 Uri 对象

如图图 14-13 所示,仍然有一个红色的错误代码需要突出显示,所以让我们接着做,这样你就可以让这个方法没有错误,然后继续创建其他需要的方法。

单击这一行代码,最好是突出显示红色错误代码,然后您应该会在 ide 的左侧看到一个红色的错误灯泡下拉箭头。

单击向下箭头,下拉一个菜单列表,列出 IntelliJ 认为可以解决代码连续性问题的错误修复建议。有两种选择:创建一个完全独立的类,或者在 ProWatchFaceUtility.java 类中创建一个私有或内部类。为了保持实现 watch faces 应用紧凑所需的类的数量,选择第二个“创建内部类' DataItemResultCallback '”选项,可以看到在图 14-14 的底部突出显示。

9781430265504_Fig14-14.jpg

图 14-14 。选择“创建内部类”错误下拉菜单选项和 ProWatchFaceUtility 目标类

这将弹出第二个选择器对话框,其中有两个选项列表,允许您指定一个您希望包含内部类的目标类 。您想要 ProWatchFaceUtility 类中的私有静态内部类,所以通过双击选择它,IntelliJ 将编码一个私有静态DataItemResultCallback ,实现 ResultCallback < DataApi。DataItemResult >接口接口。这将有一些红色的错误高亮显示,直到你实现了所需的方法,这将在本章的下一节中进行。

IntelliJ 将在 ProWatchFaceUtility 类的底部创建私有的 static (inner)类,正如在图 14-15 的底部可以看到的。

9781430265504_Fig14-15.jpg

图 14-15 。现在回调对象引用已经就位,私有静态内部类也创建好了

让我们编写构成该方法主体的 Java 代码,它将使用 DataMapItem 从 DataItem 中提取手表表面配置数据映射,就像您在使用。fromDataItem()方法 。

使用可穿戴数据 Api: DataItemResultCallback()类

在这个 DataItemResultCallback 类中,您要做的第一件事是声明一个私有的 final FetchConfigDataMapCallback 对象,然后将其命名为 mCallback。接下来,创建一个公共 DataItemResultCallback()方法,该方法将名为 Callback 的 FetchConfigDataMapCallback 对象作为参数。

在此方法中,将在此类顶部创建的 mCallback 对象设置为传递到此 DataItemResultCallback()方法中的回调对象。如图图 14-16 所示,代码应该如下所示:

private static class DataItemResultCallback implements ResultCallback<DataApi.DataItemResult> {
    private final FetchConfigDataMapCallback mCallback;
    public DataItemResultCallback(FetchConfigDataMapCallback callback) {
        mCallback = callback;
    }
}

9781430265504_Fig14-16.jpg

图 14-16 。添加私有 final mCallback 变量;将其设置为等于传入 DataItemResultCallback()的回调

注意在图 14-16 中,红色波浪状的错误高亮仍然存在,所以鼠标悬停在错误高亮上,显示错误消息,它说“类必须被声明为抽象的或者实现方法 onResult(R)。”

覆盖公共空间。onResult(DataApi。DataItemResult dataItemResult)方法,在 DataItemResultCallback()方法的末尾,它将添加一个空方法(暂时)并删除红色的错误突出显示。在图 14-17 中显示没有错误的 Java 方法应该如下所示:

private static class DataItemResultCallback implements ResultCallback<DataApi.DataItemResult> {
    private final FetchConfigDataMapCallback mCallback;
    public DataItemResultCallback(FetchConfigDataMapCallback callback) {
        mCallback = callback;
    }
    @Override
    public void onResult(DataApi.DataItemResult dataItemResult)// empty method removes error }
}

9781430265504_Fig14-17.jpg

图 14-17 。添加@Override onResult()方法以移除 DataItemResultCallback 上突出显示的红色错误

接下来,您需要创建条件 if()结构,该结构确定 onResult()方法是否返回了 DataItemResult(外部 if()结构),以及第二个内部条件 if()结构,该结构将确定 DataItemResult 是否已被使用或是否为空(也就是说,它是!= null)。

第一个外部条件 if()结构使用了一个**。getStatus( )** isSuccess( ) 方法链接出dataItemResultdataItemResult 对象,您之前已经看到使用它来获得成功结果状态标志(true 值)。

然后,第二个内部条件 if()结构使用 getDataItem()方法调用 dataItemResult 对象,并将其与空值进行比较。如果 DataItemResult 对象内部有内容,则处理 If 部分。如果为空(null),则处理构造的 else 部分。

嵌套 if()结构中的第一行代码也与 DataItemResult 处理相关,所以我也将在这里包含它。这个**。** getDataItem( ) 方法再次用于获取有效的 DataItem 结果,并将其安装到一个名为 configDataItemDataItem 对象中,在一个 Java 语句中声明、命名并实例化该 DataItem,以便它可以接收一个**DataItem result . get DataItem()**方法调用对象传输。

控件的 Java 代码。onResult()方法结构,也可以在图 14-18 的底部看到突出显示,应该如下所示:

public void onResult(DataApi.DataItemResult dataItemResult) {
    if (dataItemResult.getStatus( ).isSuccess( )) {
        if (dataItemResult.getDataItem( ) != null) {
            DataItem configDataItem = dataItemResult.getDataItem( );
        } else {
        }
    }
}

9781430265504_Fig14-18.jpg

图 14-18 。在 onResult()内创建一个 if()和嵌套的 if-else 结构;使用。getDataItem()来提取结果

既然所有的 DataItemResult 对象处理都已就绪,接下来您要做的事情是将 DataItem 转换为 DataMapItem,以便稍后您可以将 DataMapItem 转换为您需要的 DataMap 对象。

用 DataItem 对象加载 DataMapItem 对象的代码如图 14-19 中的所示,应该类似于下面两行代码:

DataItem configDataItem = dataItemResult.getDataItem( );
DataMapItem dataMapItem = DataMapItem.fromDataItem(configDataItem);

9781430265504_Fig14-19.jpg

图 14-19 。声明一个 dataMapItem,将其命名为 DataMapItem,并将其设置为等于。fromDataItem()

现在,所有的 DataMapItem 对象处理都已就绪,接下来您要做的是将 DataMapItem 转化为 DataMap,这样以后您就可以将 DataMap 对象传递给 onConfigDataMapFetched()方法调用。在图 14-20 的中可以看到 DataItem 到 DataMapItem 再到 DataMap 的这种转换,应该使用这三行 Java 代码来完成:

DataItem configDataItem = dataItemResult.getDataItem( );
DataMapItem dataMapItem = DataMapItem.fromDataItem(configDataItem);
DataMap config = dataMapItem.getDataMap( );

9781430265504_Fig14-20.jpg

图 14-20 。声明一个名为 config 的数据映射,将它设置为对 dataMapItem 的. getDataMap()调用的结果

条件语句 if 部分的最后一步是通过使用配置数据映射对象传递给mcall back FetchConfigDataMapCallback对象。onConfigDataMapFetched】方法调用。

在条件 if-else 语句的 else 部分,如果 DataItem 对象确实为空(null ),那么只需通过使用 Java new 关键字和 DataMap( ) 构造函数方法调用实例化一个新的 DataMap 对象。onConfigDataMapFetched()方法,从 mcall back FetchConfigDataMapCallback 对象调用。完成的 onResult()方法结构,如图图 14-21 所示,应该如下图所示:

public void onResult(DataApi.DataItemResult dataItemResult) {
            if (dataItemResult.getStatus( ).isSuccess( )) {
                if (dataItemResult.getDataItem( ) != null) {
                    DataItem configDataItem = dataItemResult.getDataItem( );
                    DataMapItem dataMapItem = DataMapItem.fromDataItem(configDataItem);
                    DataMap config = dataMapItem.getDataMap( );
                    mCallback.onConfigDataMapFetched(config);
                } else {
                    mCallback.onConfigDataMapFetched(new DataMap( ));
                }
            }
}

9781430265504_Fig14-21.jpg

图 14-21 。使用将 FetchConfigDataMapCallback 对象设置为数据映射。onConfigDataMapFetched()

请注意,我单击了。代码中的 onConfigDataMapFetched()方法跟踪其实现,返回到 FetchConfigDataMapCallback Java 接口,该接口在图 14-21 的顶部以蓝色突出显示。

接下来,让我们创建为这个类编码所需的最后一个主要方法。那个。overwriteKeysInConfigDataMap()方法将替换用户指定的任何已更改的键-数据对,从而创建一个全新的 DataMap 对象。

替换更改的数据:overwriteKeysInConfigDataMap

让我们创建**公共静态 void****overwriteKeysInConfigDataMap()**方法,带有 final GoogleApiClient,您将命名为 googleApiClient ,以及 final DataMap 对象,您将命名为 configKeysToOverwrite ,如图图 14-22 所示。Java 代码应该类似于这个空方法主体声明:

public static void overwriteKeysInConfigDataMap(final GoogleApiClient googleApiClient, final DataMap configKeysToOverwrite) {  // empty method: your method body will go in here  }

9781430265504_Fig14-22.jpg

图 14-22 。创建空的公共静态 void overwriteKeysInConfigDataMap()方法

你要在这个方法体中编码的第一件事是对你在本章前面创建的方法之一的方法调用。这将被 ProWatchFaceUtility 类名、句点和方法名引用,如图图 14-23 所示。

在方法调用参数区域内,您将传递 GoogleApiClient 对象,并使用 Java new 关键字实例化一个新的fetchConfigDataMapCallback对象。空方法声明的 Java 代码应该类似于以下代码:

ProWatchFaceUtility.fetchConfigDatamap(googleApiClient, new fetchConfigDataMapCallback( ) { empty method });

9781430265504_Fig14-23.jpg

图 14-23 。使用 IntelliJ 弹出帮助器对话框实现 FetchConfigDataMapCallback 接口

fetchConfigDatamap(GoogleApiClient,FetchConfigDataMapCallback) 方法当前被 IntelliJ IDEA 指定(着色)为未使用,如图 14-22 所示。未使用的方法或变量是使用格雷码文本颜色定义的,一旦它们在其他代码中被引用,它们将变成黑色。在图 14-23 中可以看到,IntelliJ 可以帮助编码一个空的方法体;例如,如果您键入一个 Java new 关键字和 Fe 字符,将会弹出一个 IntelliJ helper 对话框,您可以在其中从下拉选项列表中选择 FetchConfigDataMapCallback 接口。

当选择 FetchConfigDataMapCallback 接口时,IntelliJ 也会实现所需的**。onconfigdatamappled(data map config)**方法,如图 14-24 中的所示,产生如下 Java 代码:

ProWatchFaceUtility.fetchConfigDatamap(googleApiClient, new fetchConfigDataMapCallback( ) {
    @Override
    public void onConfigDataMapFetched(DataMap config)// an empty method created by IntelliJ }
});

9781430265504_Fig14-24.jpg

图 14-24 。IntelliJ 将为您实现完整的(空的)FetchConfigDataMapCallback 基础结构

现在您所要做的就是编写创建新数据映射的 Java 语句,通过覆盖当前(或者默认,如果这是第一次更新的话)数据映射来保存最新的(更新的)配置数据数组。

更新数据映射对象:onConfigDataMapFetched()

在 IntelliJ 为您创建的这个公共 void onConfigDataMapFetched()方法中,将传递到该方法中的 DataMap 参数的名称更改为 currentConfig 。这样做是为了更准确地反映传递到这个方法结构中的内容。

接下来,声明并实例化一个名为 overwriteConfig新的数据映射对象,该对象用于保存数据映射,并将包含修改后的配置参数。要使用传递给该方法的 currentConfig 数据映射加载该对象,请调用**。** putAll( ) 方法使用 currentConfig 对象作为参数,关闭 overwriteConfig DataMap 对象。

到目前为止,你可以看到在图 14-25 底部高亮显示的 Java 方法结构,看起来应该像下面的 Java 代码:

ProWatchFaceUtility.fetchConfigDatamap(googleApiClient, new fetchConfigDataMapCallback( ) {
    @Override
    public void onConfigDataMapFetched(DataMap currentConfig) {
        DataMap overwriteConfig = new DataMap( );
        overwriteConfig.putAll(currentConfig);
    }
});

9781430265504_Fig14-25.jpg

图 14-25 。声明并实例化名为 overwriteConfig 的数据映射,并使用. putAll()方法加载它

现在,您的当前设置或默认设置(如果这是您的第一次配置参数更新)位于 overwriteConfig 数据映射中,然后您将使用相同的 putAll()方法来覆盖(替换)传递到此 overwriteKeysInConfigDataMap()方法中的新配置参数。

这个工作流程确保如果在两个之后的传入(更新或更改)数据映射中有任何不完整或缺失的键-数据对。putAll()方法,它们将被处理,并且将有一个完整的数据映射对象,其中包含所有参数,包括已更改的和未更改的(或默认的)。

到目前为止,你可以看到 Java 方法结构在图 14-26 的底部突出显示,看起来应该像下面的 Java 代码:

ProWatchFaceUtility.fetchConfigDatamap(googleApiClient, new fetchConfigDataMapCallback( ) {
    @Override
    public void onConfigDataMapFetched(DataMap currentConfig) {
        DataMap overwriteConfig = new DataMap( );
        overwriteConfig.putAll(currentConfig);
        overwriteConfig.putAll(configKeysToOverwrite);
    }
});

9781430265504_Fig14-26.jpg

图 14-26 。用另一个。putAll()方法调用,用当前数据覆盖更新的配置数据

您需要做的最后一个编程“动作”是通过将更新后的 overwriteConfig DataMap 对象传递给来编写该对象。 putConfigDataItem( ) 方法,您在本章前面编写的代码。因为您在当前方法体之外调用这个方法,所以您将在这个方法名的前面加上类名,就像这样:prowatchface utility . putconfigdataitem()。在参数区域内,传递包含更新(覆盖)的观察面配置数据的 GoogleApiClient 对象和 DataMap 对象。

该方法结构的最终 Java 代码可以在图 14-27 的底部高亮显示,应该如下所示:

ProWatchFaceUtility.fetchConfigDatamap(googleApiClient, new fetchConfigDataMapCallback( ) {
    @Override
    public void onConfigDataMapFetched(DataMap currentConfig) {
        DataMap overwriteConfig = new DataMap( );
        overwriteConfig.putAll(currentConfig);
        overwriteConfig.putAll(configKeysToOverwrite);
        ProWatchFaceUtility.putConfigDataItem(googleApiClient, overwriteConfig);
    }
});

9781430265504_Fig14-27.jpg

图 14-27 。使用将更新的数据映射配置数据发送到智能手表。从 ProWatchFaceUtility 类调用了 putConfigDataItem()方法

接下来您需要做的是从 ProWatchFaceConfigListenerService 类调用 ProWatchFaceUtility 类,将两个类连接在一起。

连接地图:从监听器调用工具

单击ProWatchFaceConfigListenerService.java选项卡,或者如果它没有在 IDE 中打开,则将其打开。向 onMessageReceived( ) 方法添加最后一行代码,该方法将把 keysToOverwrite DataMap 对象发送到 ProWatchFaceUtility 类**。****overwriteKeysInConfigDataMap()**方法。如图图 14-28 所示,Java 语句应该如下所示:

ProWatchFaceUtility.overwriteKeysInConfigDataMap(myGoogleApiClient, keysToOverwrite);

9781430265504_Fig14-28.jpg

图 14-28 。打开 Listener 类并调用 ProWatchFaceUtility 类的 overwriteKeysInConfigDataMap 方法,同时传递 keysToOverwrite

正如你将在图 14-29 中看到的,工具类的 Java 代码是无错误的,并且没有任何 Java 代码(除了常量,我将在下面处理)是灰色的

9781430265504_Fig14-29.jpg

图 14-29 。现在这两个类是交叉连接的(互相使用),唯一的格雷码是常量

现在,您已经准备好返回到 prowatchfacecompcompanionconfigactivity,它是您在第十三章中开始开发所有代码时编写的。最后,您将在已创建的其他类中创建所需的键值数据对,并完成创建颜色选择用户界面设计所需的所有 UI 设计工作,供 watch face 用户用来定制他们的 watch face 应用。

完成配置助手:UI 设计

打开ProWatchFaceCompanionConfigActivity.java类(在你的项目的移动部分),添加你在 ProWatchFaceUtility.java 类顶部声明的四个相同的 KEY_COLOR 常量。

这些常量的唯一区别是它们将使用 Java private 关键字声明,因为它们只在类中使用,而不是像在 ProWatchFaceUtility 中那样使用 public 关键字。这些常量声明可以在图 14-30 中看到,看起来像下面的 Java 语句:

private static final String PATH_WITH_FEATURE = "/watch_face_config/ProWatchFace";
private static final String KEY_COLOR_TICK_MARK = "COLOR_TICK_MARK";
private static final String KEY_COLOR_HOUR_HAND = "COLOR_HOUR_HAND";
private static final String KEY_COLOR_MINUTE_HAND = "COLOR_MINUTE_HAND";
private static final String KEY_COLOR_SECOND_HAND = "COLOR_SECOND_HAND";

9781430265504_Fig14-30.jpg

图 14-30 。声明与配套活动中的工具常量相匹配的私有 KEY_COLOR 常量

现在,所有用于在所有类之间传递 DataMap 和 DataItem 对象的 Java 代码都已就绪,让我们换个方式,进入 UI 设计模式。让我们编写一些 XML 标记来定义智能手机上 watch faces Configuration Companion 活动的用户体验。

使用微调器小部件选择颜色:XML UI 布局

在编辑选项卡中打开mobile/RES/activity _ pro _ watch _ face _ config . XML文件,在< TextView >标签下添加嵌套的< LinearLayout >标签。将 orientation 参数设置为 horizontal,这样文本小部件将位于 Spinner 小部件的旁边。在这个 LinearLayout 容器中,嵌套一个 TextView 标签,并使用@string/pro_config_tick_mark 引用<字符串>常量。然后将 layout_width 参数设置为零,将 layout_weight 参数设置为一。如图图 14-31 所示,XML 标记应该如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView android:id="@+id/title" android:text="@string/prowatchface_config"
        android:layout_width="match_parent" android:layout_height="wrap_content" />
    <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
        android:orientation="horizontal" >
        <TextView android:text="@string/pro_config_tick_mark" android:layout_width="0dp"
            android:layout_height="wrap_content" android:layout_weight="1" />
    </LinearLayout>
</LinearLayout>

9781430265504_Fig14-31.jpg

图 14-31 。添加一个嵌套的< LinearLayout >,并在其中嵌套一个< TextView >标签来保存第一个 UI 构造

您将 layout_width 参数设置为零密度像素,或密度独立像素(DIP) 的原因是,该设置告诉 Android OS 允许 layout_weight 参数确定屏幕分配的相对布局百分比。如果您将微调器小部件 layout_weight 设置为 2,那么您的 TextView 将获得屏幕的三分之一,计算为 1/(1+2)。Spinner layout_weight 为 3,这是您接下来要实现的,它使 TextView 占屏幕的 25%,而 Spinner 占屏幕的 75%。

让我们去掉在图 14-31 中看到的红色错误代码高亮显示,打开mobile/RES/values/strings . XML文件,使用数据值 Tick Marks 添加名为 pro_config_tick_mark 的<字符串>常量。当你这样做的时候,为时针、分针和秒针添加其他的<字符串>常量。如图 14-32 所示,XML 标记应该如下所示:

<string name="pro_config_tick_mark">Tick Marks</string>
<string name="pro_config_hour_hand">Hour Hand</string>
<string name="pro_config_minute_hand">Minute Hand</string>
<string name="pro_config_second_hand">Second Hand</string>

9781430265504_Fig14-32.jpg

图 14-32 。为 UI 标签创建<字符串>常量,用于刻度线和时针、分针、秒针

在这个最初的子水平构造中,添加小部件,就在小部件的下面。使用格式 @+id/tickMarks 给它一个 tickMarks ID,并再次将 layout_width 设置为 0dp(如果您愿意,也可以设置为 0dip),将 layout_weight 设置为 3。

要用颜色值加载微调器小部件,使用格式 @array/color_array条目参数集添加到名为 color_array数组中。

到目前为止,你的 XML 标记,可以在图 14-33 中看到突出显示,应该看起来像下面的 XML 布局定义结构:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView android:id="@+id/title" android:text="@string/prowatchface_config"
        android:layout_width="match_parent" android:layout_height="wrap_content" />
    <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
        android:orientation="horizontal" >
        <TextView android:text="@string/pro_config_tick_mark" android:layout_width="0dp"
            android:layout_height="wrap_content" android:layout_weight="1" />
        <Spinner android:id="@+id/tickMarks" android:entries="@array/color_array"
            android:layout_width="0dp" android:layout_height="wrap_content"
            android:layout_weight="3" />
    </LinearLayout>
</LinearLayout>

9781430265504_Fig14-33.jpg

图 14-33 。在引用 color_array 的< TextView >标签下添加一个<微调器>小部件子标签

要去掉红色的错误文本突出显示,请单击 strings.xml 选项卡并添加颜色数据值常量,在创建引用这些颜色常量值的数组对象之前,必须将这些常量放置到位。

我将使用 Android Color 类中的八个原色常量。这些是 Android 操作系统中定义的最标准化的颜色,在黑色背景色的映衬下会非常好看。

这将为您的用户提供 1,680 种不同(非唯一)的颜色组合,这意味着同一种颜色可以用于多个表盘设计元素。计算变量个数的方法是 C!(色数阶乘)除以 E!(元素个数阶乘)。

有八种不同的颜色可能性,或者说八个阶乘 8!,也就是 8765432*1=40320 。然后你用四个不同的表盘设计元素,或者四个阶乘 4 来除它!,也就是 432*1=24 。这给了你 1680 种可能的颜色组合!

定义八种颜色的 XML 标记如图图 14-34 所示,应该如下所示:

<string name="color_yellow">Yellow</string>
<string name="color_blue">Blue</string>
<string name="color_red">Red</string>
<string name="color_green">Green</string>
<string name="color_cyan">Cyan</string>
<string name="color_magenta">Magenta</string>
<string name="color_gray">Gray</string>
<string name="color_white">White</string>

9781430265504_Fig14-34.jpg

图 14-34 。添加八个<字符串>常量定义,引用最常见的 Android OS 颜色常量

在颜色常数 XML 定义就绪之后,您将添加一个对象 XML 定义,它将创建保存颜色常数字符串值的数组对象构造。

这个标签的作用是通过嵌套引用< string-array > Array 对象定义构造中的< string >常量的 < item > 标签,使用 XML 创建一个 String Array 对象。

您需要编码以实现字符串数组结构的创建的 XML 标记可以在图 14-35 中看到,应该如下所示:

<string-array name="color_array">
    <item>@string/color_yellow</item>
    <item>@string/color_blue</item>
    <item>@string/color_red</item>
    <item>@string/color_green</item>
    <item>@string/color_cyan</item>
    <item>@string/color_magenta</item>
    <item>@string/color_gray</item>
    <item>@string/color_white</item>
</string-array>

9781430265504_Fig14-35.jpg

图 14-35 。添加一个<字符串数组>结构,其中填充了引用八种颜色的八个<项目>子标签

正如您将在图 14-36 中看到的,现在您已经创建了 XML 字符串颜色常量和数组,没有红色的错误代码高亮显示,您已经准备好复制并粘贴您的第一个嵌套的< LinearLayout >结构,并为时针颜色选择微调器 UI 元素创建第二个。

9781430265504_Fig14-36.jpg

图 14-36 。复制并粘贴第一个嵌套的 LinearLayout UI 容器,为时针创建第二个容器

选择子 XML 结构及其两个子 UI 小部件,右键选择复制,或者使用 CTRL+C 组合键。接下来,单击鼠标将插入条(光标)插入父 LinearLayout 的最后一个< /LinearLayout >结束标记之前,单击鼠标右键并选择粘贴,或者使用 CTRL+V 组合键进行粘贴。结果可以在图 14-36 中看到,应该如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView android:id="@+id/title" android:text="@string/prowatchface_config"
        android:layout_width="match_parent" android:layout_height="wrap_content" />
    <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
        android:orientation="horizontal" >
        <TextView android:text="@string/pro_config_tick_mark" android:layout_width="0dp"
            android:layout_height="wrap_content" android:layout_weight="1" />
        <Spinner android:id="@+id/tickMarks" android:entries="@array/color_array"
            android:layout_width="0dp" android:layout_height="wrap_content"
            android:layout_weight="3" />
    </LinearLayout>
    <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
        android:orientation="horizontal" >
        <TextView android:text="@string/pro_config_hour_hand" android:layout_width="0dp"
            android:layout_height="wrap_content" android:layout_weight="1" />
        <Spinner android:id="@+id/hourHand" android:entries="@array/color_array"
            android:layout_width="0dp" android:layout_height="wrap_content"
            android:layout_weight="3" />
    </LinearLayout>
</LinearLayout>

因为您希望允许用户为四个表盘设计元素配置颜色,所以您需要再次执行这个节省时间的复制和粘贴工作流程,并选择这两个嵌套的 LinearLayout 容器 XML 标记块,然后将它们粘贴到 UI 定义的底部。

最终你会得到一个主父级垂直 LinearLayout 容器来排列嵌套的子级水平 LinearLayout 容器,其中包含 TextView (标签)、 Spinner (颜色选择器)和 UI 元素。

最终的activity _ pro _ watch _ face _ config . xml用户界面布局设计 XML 定义,可以看到,没有错误,在图 14-37 中,应该使用以下 XML 标记:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView android:id="@+id/title" android:text="@string/prowatchface_config"
        android:layout_width="match_parent" android:layout_height="wrap_content" />

    <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
        android:orientation="horizontal" >
        <TextView android:text="@string/pro_config_tick_mark" android:layout_width="0dp"
            android:layout_height="wrap_content" android:layout_weight="1" />
        <Spinner android:id="@+id/tickMarks" android:entries="@array/color_array"
            android:layout_width="0dp" android:layout_height="wrap_content"
            android:layout_weight="3" />
    </LinearLayout>

    <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
        android:orientation="horizontal" >
        <TextView android:text="@string/pro_config_hour_hand" android:layout_width="0dp"
            android:layout_height="wrap_content" android:layout_weight="1" />
        <Spinner android:id="@+id/hourHand" android:entries="@array/color_array"
            android:layout_width="0dp" android:layout_height="wrap_content"
            android:layout_weight="3" />
    </LinearLayout>

    <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
        android:orientation="horizontal" >
        <TextView android:text="@string/pro_config_minute_hand" android:layout_width="0dp"
            android:layout_height="wrap_content" android:layout_weight="1" />
        <Spinner android:id="@+id/minuteHand" android:entries="@array/color_array"
            android:layout_width="0dp" android:layout_height="wrap_content"
            android:layout_weight="3" />
    </LinearLayout>

    <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
        android:orientation="horizontal" >
        <TextView android:text="@string/pro_config_second_hand" android:layout_width="0dp"
            android:layout_height="wrap_content" android:layout_weight="1" />
        <Spinner android:id="@+id/secondHand" android:entries="@array/color_array"
            android:layout_width="0dp" android:layout_height="wrap_content"
            android:layout_weight="3" />
    </LinearLayout>

</LinearLayout>

9781430265504_Fig14-37.jpg

图 14-37 。复制并粘贴前两个嵌套的 LinearLayout UI 容器,以创建分针和秒针

既然已经构建了 XML UI 布局定义,那么让我们切换回 Java 编程模式,并编写使这些微调器起作用的方法。

设置微调器小部件:setUpColorPickerSelection()

在接下来的几节中,您将编写与后端处理相关的 Java 方法(Java 代码;XML 标记是颜色选择小部件的前端设计。这些是通过使用标签放置的 Spinner 类(对象)定义来实现的。

单击 ProWatchFaceCompanionConfigActivity.java 选项卡(或打开它)并在类的底部结束花括号(})之前添加一个私有的 void setUpColorPickerSelection()方法。您需要向该方法传递四个参数:一个是您在 XML UI 定义中创建的 Spinner ID 参数,一个保存配置数据键的字符串,一个包含键-值对的数据映射,以及一个默认颜色常量的资源 ID 整数。如图 14-38 中的所示,Java 代码应该如下所示:

private void setUpColorPickerSelection(int spinnerId, final String configKey, DataMap config,
                                       int defaultColorNameResId) {
    String defaultColorName = getString(defaultColorNameResId);
    int defaultColor = Color.parseColor(defaultColorName);
    int color;
}

9781430265504_Fig14-38.jpg

图 14-38 。在类的末尾添加私有的 void setUpColorPickerSelection()方法,并声明变量

您需要在这个方法中放置的下一个 Java 构造是一个 if-else 条件语句,它将确定数据映射是否已经被加载(使用)或者它是否是一个未使用的数据映射。

确定这一点的方法是使用 if(config!= null)条件,因为非 null 数据映射包含一个数据映射!如果数据图是“实时”的,您可以使用**。getInt(String,integer)** ,调用配置数据映射对象。这将提取与 configKey 字符串键关联的整数值。这被赋给你之前声明的颜色整数变量。

另一方面,在构造的 else 部分中,如果数据映射为空(null 值),只需将 color 整数变量设置为 defaultColor 值,该值是使用。getString()方法调用。该构造的 Java 代码,如图 14-39 所示,应该看起来像下面的方法结构:

private void setUpColorPickerSelection(int spinnerId, final String configKey, DataMap config,
                                       int defaultColorNameResId) {
    String defaultColorName = getString(defaultColorNameResId);
    int defaultColor = Color.parseColor(defaultColorName);
    int color;
    if (config != null) {
        color = config.getInt(configKey, defaultColor);
    } else {
        color = defaultColor;
    }
}

9781430265504_Fig14-39.jpg

图 14-39 。添加 if-else 条件结构,评估配置数据映射,并相应地处理它

完成该方法的最后一步是创建一个名为 spinner 的 Java Spinner 对象,使用< Spinner > XML 定义使用 findViewById( ) 方法对其进行膨胀。Android 中的膨胀过程是使用之前创建的 XML 对象定义填充 Java 对象。然后创建一个 colorNames String[ ] 数组,并使用 XML**构造和创建的数据填充它。** getStringArray( ) 方法连锁掉一个 getResources( ) 方法。对循环使用一个**,并用你的颜色常量值加载 colorNames 数组。执行此操作的 Java 代码如图 14-40 所示,应该如下所示:**

private void setUpColorPickerSelection(int spinnerId, final String configKey, DataMap config,
                                       int defaultColorNameResId) {
    String defaultColorName = getString(defaultColorNameResId);
    int defaultColor = Color.parseColor(defaultColorName);
    int color;
    if (config != null) {
        color = config.getInt(configKey, defaultColor);
    } else {
        color = defaultColor;
    }
    Spinner spinner = (Spinner) findViewById(spinnerId);
    String[] colorNames = getResources( ).getStringArray(R.array.color_array);
    for (int i = 0; i < colorNames.length; i++) {
        if (Color.parseColor(colorNames[i]) == color) {
            spinner.setSelection(i);
            break;
        }
    }
}

9781430265504_Fig14-40.jpg

图 14-40 。创建一个名为 spinner 的 Spinner 对象;用 XML 数组中定义的 String[ ]数组数据加载它

现在,您已经用颜色值加载了微调器对象,下一步我们要设置颜色选择器侦听器对象来侦听用户选择的更改。

设置微调器监听器:setUpColorPickerListener()

现在,您已经用先前创建的中的颜色常量值填充了微调器对象,您还需要设置监听器对象,它将监听用户对微调器默认(或先前)颜色常量选择所做的任何更改。

在类的末尾创建一个private void****setUpColorPickerListener()方法,该方法接受微调器 id 整数configKey KEY_COLOR 字符串颜色常量值作为其两个方法参数。

在方法内部,声明一个名为 spinnerSpinner 对象,并使用 findViewById(spinnerId) 方法调用和参数对其进行膨胀。您在刚刚创建的前一个方法中完成了此操作;但是,请注意,这两个变量都是在每个方法中使用的局部(或私有)变量,因此它们并不冲突。对于这个方法,您需要一个惟一的 Spinner 对象,因为您将基于它构造一个侦听器结构。

到目前为止的 Java 方法结构,可以在图 14-41 的底部看到突出显示,应该看起来像下面的 Java 代码:

private void setUpColorPickerListener(int spinnerId, final String configKey) {
    Spinner spinner = (Spinner) findViewById(spinnerId);
}

9781430265504_Fig14-41.jpg

图 14-41 。在类的底部创建一个公共的 void setUpColorPickerListener()方法;然后给旋转器充气

您需要做的下一件事是使用。setOnItemSelectedListener()方法来创建 AdapterView 侦听器。Spinner 类是从 AdapterView 类的子类,因此 Spinner 对象也是 AdapterView 对象。它使用一个适配器视图。【OnItemSelectedListener()【构造函数】方法调用,配合 Java new 关键字。使用下面的 Java 代码创建这个监听器,如图 14-42 底部突出显示的:

private void setUpColorPickerListener(int spinnerId, final String configKey) {
    Spinner spinner = (Spinner) findViewById(spinnerId);
    spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener( ) { // empty method });
}

9781430265504_Fig14-42.jpg

图 14-42 。从微调器调用. setOnItemSelectedListener()方法,并构造一个新的 AdapterView 侦听器

正如您在图 14-42 中看到的,您将需要使用 Alt+Enter 工作流程来让 IntelliJ 为 AdapterView 类编写一个导入语句。一旦 IntelliJ 做到了这一点,它将重新评估您的代码,并在 AdapterView 下给出一个红色波浪状的错误高亮显示。OnItemSelectedListener()构造函数方法。在图 14-43 所示的错误下拉列表中,选择实现方法选项,实现两个需要的方法,选择 Insert @Override 。IntelliJ 将为您创建一个 onItemSelected( ) 构造。

9781430265504_Fig14-43.jpg

图 14-43 。使用错误建议下拉列表,选择实现方法,并实现所有必需的方法

在空的 onItemSelected()方法中,如图 14-44 中突出显示的,创建一个最终字符串 colorName 变量并调用**。** getItemAtPosition( ) 方法关闭了名为 parentAdapterView 对象,并将其传递到该方法结构中。如果您愿意,可以使用 IntelliJ 弹出助手对话框。

9781430265504_Fig14-44.jpg

图 14-44 。创建一个名为 colorName 的最终字符串变量,并使用。getItemPosition()方法来加载它

当用户在微调器中选择了颜色常量后,使用 configKey 数据映射和来自**Color . parse Color(colorName)嵌套语句的整数结果调用sendconfiupdatemessage()**方法。

正如您在图 14-45 中看到的,这个 sendConfigUpdateMessage()方法还不存在,所以使用错误建议下拉菜单并选择创建方法选项,让 IntelliJ 在ProWatchFaceCompanionConfigActivity(一个选择目标类对话框选项)中为您编码空方法结构。

9781430265504_Fig14-45.jpg

图 14-45 。使用数据映射和颜色键调用 sendConfigUpdateMessage()方法;然后选择创建方法

编辑**私有 void。sendConfigUpdateMessage(String configKey,int I)**IntelliJ 为您编写的方法结构,通过将 int i 改为 int color 来更准确地反映方法内部的情况。现在,您可以编写 sendConfigUpdateMessage()方法结构的内部代码,然后通过编写。setUpAllPickers()方法。

9781430265504_Fig14-46.jpg

。IntelliJ 将创建 sendConfigUpdateMessage(String configKey,int i)方法;然后将 I 重命名为 color

如果有 watchFacePeerId 值,则执行该方法中的所有内容。

if(watchFacePeerId!= null) 结构就是构造一个名为 newConfig 的 DataMap,然后用一个. putInt()方法加载它,并向这个方法传递 configKeyColor 常量。如图图 14-47 所示的 Java 代码应该如下所示:

If ( watchFacePeerId != null ) { DataMap newConfig = new DataMap( );
                                 newConfig.putInt(configKey, color); }

9781430265504_Fig14-47.jpg

图 14-47 。创建 if()条件以查看 watchFacePeerId 是否存在;在其中,创建一个数据映射,并用键-值对加载它

接下来的两行代码将声明一个名为 rawConfigDatabyte[ ] 数组,并使用一个**。** toByteArray( ) 方法调用从名为 newConfig 的数据映射中提取数据。重担由穿戴者来承担。MessageApi 和。sendMessage()方法调用,该方法使用下面的代码将 rawConfigData byte[ ]数组以及唯一的 watchFacePeerId、GoogleApiClient 对象和 PATH_WITH_FEATURE 常量传递给 GMS 服务器,如图 14-48 所示:

byte[] rawConfigData = newConfig.toByteArray( );
Wearable.MessageApi.sendMessage(myGoogleApiClient,watchFacePeerId,PATH_WITH_FEATURE,rawConfigData);

9781430265504_Fig14-48.jpg

图 14-48 。从数据映射创建一个 byte[]数组,并使用。sendMessage()方法提交给可穿戴者

接下来,您需要创建一个方法来设置所有四个 Spinner UI 元素,这将允许应用为所有表盘设计元素设置颜色选择。

设置所有四个微调器:A .setUpAllPickers()方法

在 public void onResult()方法后添加一行代码,声明一个private void****setUpAllPickers()方法。您要编写的最后一个方法将调用您刚刚编写完的 setUpColorPickerSelection()和 setUpColorPickerListener()方法。它接受一个保存数据映射对象的数据映射参数,并将它连同要配置的表面设计元素和为这些元素选择的默认颜色值一起传递给 setUpColorPickerSelection()方法。

正如你从编码中所知。setUpColorPickerSelection()方法,您将需要传递 Spinner 对象的 ID 引用、要设置的数据的 KEY_COLOR 字符串常量、包含键值数据对的 configData DataMap 对象以及默认颜色字符串引用。

Java 方法结构可以在图 14-49 中看到,一旦您声明了它并添加了四个方法调用来配置手表表面元素的四个微调器对象中的每一个,它应该看起来像下面的代码:

private void setUpAllPickers(DataMap configData) {
   setUpColorPickerSelection(R.id.tickMarks, KEY_COLOR_TICK_MARK, configData, R.string.color_gray);
   setUpColorPickerSelection(R.id.hourHand, KEY_COLOR_HOUR_HAND, configData, R.string.color_blue);
   setUpColorPickerSelection(R.id.minuteHand,KEY_COLOR_MINUTE_HAND,configData,R.string.color_green);
   setUpColorPickerSelection(R.id.secondHand,KEY_COLOR_SECOND_HAND,configData,R.string.color_red);
}

9781430265504_Fig14-49.jpg

图 14-49 。调用手表外观设计元素的 setUpColorPickerSelection()方法,传递 configData

现在,您已经配置了所有的 Spinner UI 小部件,它们最初将显示什么颜色(默认),接下来您需要做的是设置一个监听器,这样如果您的用户更改了这个颜色设置,您的应用代码就可以处理这个新设置,并将该颜色发送到 watch face 应用。监听器需要知道监听哪个 Spinner (ID ),以及如果被触发要处理哪个 KEY_COLOR 常量。

调用 setUpColorPickerListener()方法四次来设置每个微调器的 Java 代码在图 14-50 中高亮显示,它应该看起来像下面完成的 Java 方法结构:

private void setUpAllPickers(DataMap configData) {
   setUpColorPickerSelection(R.id.tickMarks, KEY_COLOR_TICK_MARK, configData, R.string.color_gray);
   setUpColorPickerSelection(R.id.hourHand, KEY_COLOR_HOUR_HAND, configData, R.string.color_blue);
   setUpColorPickerSelection(R.id.minuteHand,KEY_COLOR_MINUTE_HAND,configData,R.string.color_green);
   setUpColorPickerSelection(R.id.secondHand,KEY_COLOR_SECOND_HAND,configData, R.string.color_red);
   setUpColorPickerListener(R.id.tickMarks, KEY_COLOR_TICK_MARK);
   setUpColorPickerListener(R.id.hourHand, KEY_COLOR_HOUR_HAND);
   setUpColorPickerListener(R.id.minuteHand, KEY_COLOR_MINUTE_HAND);
   setUpColorPickerListener(R.id.secondHand, KEY_COLOR_SECOND_HAND);
}

9781430265504_Fig14-50.jpg

图 14-50 。为表盘设计元素调用 setUpColorPickerListener()方法;然后传递微调器 ID 和密钥

现在一切就绪,让我们尝试在 Nexus 5 AVD 模拟器中运行 watch face 应用的移动组件,看看您的 UI 设计是否有效。

测试 WatchFaceCompanion 活动:Nexus 5

让我们在 Android Studio 的默认 AVD 模拟器中运行你一直在开发的 WatchFaces 应用的移动组件,这个模拟器恰好是运行 Android 5.0 的 Google 的 Nexus 5。使用运行image编辑配置菜单序列进入图 14-51 中的运行/调试配置对话框,将 IntelliJ 模块下拉设置为移动并将首选 Android 虚拟设备下拉设置为Nexuas 5 API****21。在活动部分,将启动单选按钮设置为ProWatchFaceCompanionConfigActivity类。点击应用按钮,然后点击确定按钮,最终完成您的设置。

9781430265504_Fig14-51.jpg

图 14-51 。使用“配置”对话框设置运行选项

使用**运行image运行‘ProWatchFaceCompanionConfigActivity’**菜单序列,运行应用代码,查看它是否运行并检查 UI。

正如你在图 14-52 中看到的,我得到了一个包错误 说“包 Android . support . wearable . companion 不存在”并且一个 Gradle Build“找不到符号变量 WatchFaceCompanion”真扫兴!

9781430265504_Fig14-52.jpg

图 14-52 。在 Gradle 构建消息窗格中出现了四个关于配套包的错误

代码中引用了 WatchFaceCompanion 类,这是引发错误的地方。我知道这个类(和包)确实存在,因为我已经在 Android 开发者网站上广泛查阅了它的信息。

因为 Android . support . wearable . companion 包似乎不见了,由于 Gradle Build 已经用黄色为您突出显示了导入语句,如图 14-52 所示,您检查编译语句的逻辑位置是应用的移动组件的 Gradle 配置文件。

方法是点击项目窗格中 Gradle Scripts 部分旁边的向右箭头。这将下拉(打开)Gradle Scripts 文件夹中的内容,包括 build.gradle 脚本。

找到build . gradle(Module:mobile)配置文件,点击右键,选择跳转到源代码选项,在编辑窗格中打开。

您将寻找的是配置的依赖项部分,其中添加了编译语句 ,提供了支持库的路径。

9781430265504_Fig14-53.jpg

。打开 Gradle Scripts 下拉箭头并右键单击它,然后为移动模块选择 Jump to Source

在图 14-54 底部的依赖关系部分可以看到。我安装了 appcompat-v7play-services ,但没有安装可穿戴。添加编译“com . Google . Android . wearable:1.1。+" 语句来修复这个问题。

9781430265504_Fig14-54.jpg

图 14-54 。加一个编译“com . Google . Android . support:wearable:1.1。依赖项部分中的+"语句

下一次我测试该应用时,我注意到 IntelliJ 正在使用一个 Wear Round AVD,这很奇怪,所以我使用了一个运行image编辑配置菜单序列来检查 IntelliJ 当前使用的设置。

果然在图 14-55 中可以看到,目标设备>模拟器下拉设置为空白(未设置),所以我选择了 Nexus 5 API 21 默认 Android Studio AVD 模拟器。

9781430265504_Fig14-55.jpg

图 14-55 。在运行/调试配置中检查目标设备image模拟器

这一次编译器没有抛出错误,这意味着我已经修复了 build.gradle 配置文件的问题,但随后我得到了一个 AVD 错误,与没有英特尔硬件有关,因为我使用的是 AMD-64 8 核处理器,正如你在图 14-56 底部看到的。

9781430265504_Fig14-56.jpg

图 14-56 。再次运行应用,如果您在 AMD-64 系统上,您可能会得到这些英特尔硬件加速执行管理器(HAXM)错误

在为 Android OS 开发时,你需要勤奋并准备好一切,所以我使用了工具image Android image AVD 管理器来改变我的 AVD 模拟器。

我需要更改配置以使用一个 ARM 仿真器 版本,正如我在设置 Wear 仿真器时所做的那样,并且我确保您同时拥有 x86 和 ARM 版本,因为不是每个人都使用英特尔架构。

打开工具image安卓image AVD 管理器对话框,选择 Nexus 5 API 21,然后点击最右边的绿色铅笔图标。这将打开一个虚拟设备 配置 对话框,您可以在其中更改核心设置。

请注意,在该对话框的顶部,您可以更改您的 AVD 名称,并且您可能希望在某个时间点为您的 Wear AVD 仿真器执行此操作,例如,将 Android Wear Square and Round 2 AVDs 重命名为 Android Wear Square and Round ARM。总是花一些时间来定制你的想法,所以在你的应用开发过程中,一切对你来说都是非常清楚的。

在对话框的第三部分,您将看到该 AVD 的软件平台,即 Lollipop,该硬件(软件)仿真器当前设置为 x86 (Intel),我必须将其更改为 ARM,以便在我的 AMD-64 系统上进行测试。

找到该部分右侧的 Change 按钮,您可以使用该按钮访问一个对话框,如果需要,该对话框允许您选择 ARM 硬件仿真器。正如你将在图 14-57 中看到的,我需要更改 AVD 以使用 ARM 硬件仿真而不是 Intel 硬件仿真,所以我在测试过程中遇到的另一个错误现在已经解决了。你很快就能看到这个多旋转器的 UI 设计了!

9781430265504_Fig14-57.jpg

图 14-57 。使用虚拟设备配置对话框来重命名或重新配置硬件仿真设置

点击完成按钮,确保 CPU/ABI 栏中列出了新的设置,在我的例子中是 ARM ,如图 14-58 中的高亮显示。现在,您已经准备好尝试运行应用并对其进行测试。

9781430265504_Fig14-58.jpg

图 14-58 。请务必在 Android 虚拟设备管理器主屏幕中检查您的新模拟器设置

正如你在图 14-59 中看到的,Nexus 5 仿真器现在正在启动,你可以看到我忘记了用 _ARM 扩展名重命名这个仿真器,所以我必须返回去重新命名!左边是初始启动屏幕,中间是图标,因为一个应用图标不可用,我将再次使用 IntelliJ 中的运行命令,这会将这个应用放入仿真器,因为它已经在系统内存中启动了。

9781430265504_Fig14-59.jpg

图 14-59 。运行 Nexus 5 API 21 AVD 仿真器中的配套配置活动,检查 UI 设计

正如你在图 14-59 的右手边所看到的,填充了嵌套的< LinearLayout >容器的< LinearLayout > UI 设计正确地呈现了< TextView >和< Spinner >小部件,并且如预期的那样在其中包含了< string-array >数据。没有通过您编写的 Java 代码设置默认值的原因是因为 AVD 模拟器只能到此为止,并且与 Google Play GMS 服务器连接不是 IntelliJ 中 AVD 环境的当前功能。

出于这个原因,我将在下一章解释如何在真实世界的硬件产品上测试基于 WatchFaces API 的 Android 应用,如三星 Gear S 或索尼智能手表 3,使用真实世界的网络提供商(我使用 T-Mobile)和使用真实世界的智能手机(我使用 T-Mobile 的三星 Note4)。这是越来越令人兴奋的每一个进步的章节!

摘要

在这一章中,你学习了 Android 中的可穿戴包以及可穿戴包类,其中包含用于通过 GMS 网络进行通信的 API。这些包括 APIDataAPINodeAPIWearableAPI ,所有这些都在你在本章编写的 Java 代码中使用,以实现最终的 ProWatchFaceUtility.java 类。

在创建了工具类并定义了常量之后,您编写了 putConfigDataItems( ) 方法来将 DataItem 对象加载到 DataMap 对象中。您了解了 PutDataMapRequest 类以及如何输入数据映射请求,然后您了解了 Android Wearable 类及其四个 API 数据字段。

接下来,您创建了一个 fetchConfigDataMap( ) 方法向 GMS 网络请求 DataMapResult。这使用您编写的**fetchConfigDataMapCallback()**接口来设置 ResultCallback ,并使用 onResult( ) 方法结构来处理它。

然后,您创建了 dataItemResultCallback( ) 方法,该方法利用 fetchDataMapCallback()方法并处理 DataItem 对象,以提取用于在 onResult()方法结构内创建 DataMap 对象的 DataMapItem 对象。如果提取的 DataItem 为空(null),则创建一个空数据映射。

接下来,您创建了 overwriteKeysInDataMap( ) 方法来更新现有的配置数据映射对象。这实现了一个onconfigdatamappled()方法,该方法被定义为需要在 fetchConfigDataMapCallback()接口内实现,并使用了。putAll( ) 方法调用,用用户最新的配置首选项覆盖当前(或新)数据映射中的键。

然后,您转换思路,打开了ProWatchFaceCompanionConfigActivity类,并添加了与 ProWatchFaceUtility 类中使用的常量相匹配的常量。然后,在一个activity _ pro _ watch _ face _ config . XML定义文件中,使用 XML 标记为四个微调器小部件设计用户界面。

接下来,您添加了设置颜色选择器(微调器)小部件的 Java 方法,然后使用 AVD 模拟器测试了应用的移动端。您看到了一些可能会妨碍应用测试的问题,以及如何解决这些问题。您确保了您的 UI 设计能够正常工作,现在您已经准备好在真实的硬件和真实的网络上进行测试。

在下一章中,您将更仔细地了解如何在实际的硬件设备上测试您的 watch faces 应用,包括使用 Java Singleton 设计模式,这是 watch faces API 实现中的最后一个编码步骤,然后我将介绍如何设置硬件、逐步构建以及运行和测试您的应用。