原文:
zh.annas-archive.org/md5/C54AD11200D923EEEED7A76F711AA69C
译者:飞龙
前言
Android Wear 2.0 是针对可穿戴智能设备的一个强大平台。从 Wear 发布之日起,Wear 2.0 使可穿戴设备市场的新设备激活率接近 72%。谷歌正在与多个标志性品牌合作,为智能手表带来最佳用户体验。市场上新设备的穿戴硬件不断改进,展示了穿戴设备所具有的潜力。谷歌通过材料设计、独立应用程序、表盘创新等,引入了一种全新的体验可穿戴技术的方式。
穿戴平台越来越受欢迎,Android 开发者可以提高他们为穿戴设备编程的能力,从而获益。
这本书帮助创建五个穿戴设备应用程序,并配有详尽的解释。我们从探索穿戴设备特定的用户界面组件开始,制作一个可穿戴的笔记应用程序,并构建一个可以在地图上保存快速便签的穿戴地图应用程序。我们还将构建一个带有配套移动应用程序的完整聊天应用程序。我们还将开发一个健康和健身应用程序,用于监测脉搏速率,提醒喝水等,并编写一个数字手表。我们通过探索 Wear 2.0 平台的能力来完成本书。
在构建出色的穿戴应用程序中享受乐趣。
本书涵盖的内容
第一章,让你准备起飞 - 设置你的开发环境,教你编写第一个穿戴应用程序,探索特定于穿戴应用程序的基本 UI 组件,并讨论 Android Wear 设计原则。
第二章,让我们帮助你捕捉思维 - WearRecyclerView 及更多,涵盖了WearableRecyclerView
和WearableRecyclerView
适配器,以及SharedPreferences
、BoxInsetLayout
和动画DelayedConfirmation
。
第三章,让我们帮助你捕捉思维 - 保存数据和定制 UI,探讨了 Realm 数据库的集成、自定义字体、UI 更新以及项目的最终确定。
第四章,测量你的健康 - 传感器,展示了传感器的准确性、电池消耗、Wear 2.0 休眠模式、材料设计等。
第五章,测量你的健康 - 同步收集的传感器数据,专注于同步收集的传感器数据,从穿戴设备收集传感器数据,处理接收到的数据以计算卡路里和距离,从移动应用程序向穿戴应用程序发送数据,Realm 数据库集成,WearableRecyclerView
和CardView
。
第六章,无处不在的方法 - WearMap 和 GoogleAPIclient,解释了开发者 API 控制台;地图 API 密钥;以及 SHA1 指纹,SQlite 集成,Google Maps,Google API 客户端和 Geocoder。
第七章,无处不在的方法 - UI 控件及更多,探讨了理解 UI 控件、标记控件、地图缩放控件、Wear 中的 StreetView 以及最佳实践。
第八章,智能聊天方式 - 消息传递 API 及更多,讨论了为移动应用程序配置 Firebase,创建用户界面,理解消息传递 API,使用 Google API 客户端以及构建 Wear 模块。
第九章,智能聊天方式 - 通知及更多,涵盖了 Firebase 功能、通知、材质设计 Wear 应用程序 Wear 2.0 输入法框架等。
第十章,仅为你的时间 - WatchFace 和服务,概述了CanvasWatchFaceService
和注册一个手表表盘,CanvasWatchFaceService.Engine
和回调,手表表盘元素及其初始化编写手表表盘,以及处理手势和点击事件。
第十一章,关于 Wear 2.0 的更多信息,探讨了独立应用程序,曲线布局和更多 UI 组件的 Complications API,不同的导航和动作,手腕手势,输入法框架,以及将 Wear 应用程序分发到 Play 商店。
阅读本书所需的条件
要能够跟随本书,你需要一台安装了最新版 Android Studio 的计算机。你需要互联网来设置 Wear 开发所需的所有 SDK。如果你有一个 Wear 设备来测试应用程序,那将很好;否则,Android Wear 模拟器将完成这项工作。
本书的目标读者
本书面向已经对编程和 Android 应用开发有深入了解的 Android 开发者。本书帮助读者从中级开发者进阶为专家级 Android 开发者,通过增加 Wear 开发技能来丰富他们的知识。
约定
在本书中,你将发现多种文本样式,用于区分不同类型的信息。以下是一些样式示例及其含义的解释。
界面中特定的命令或工具将如下标识:
选择“保存”按钮。
文本中的代码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 处理程序将如下显示:“我们可以通过使用include
指令包含其他上下文。”
代码块按照以下方式设置:
compile 'com.google.android.support:wearable:2.0.0' compile 'com.google.android.gms:play-services-wearable:10.0.1' provided 'com.google.android.wearable:wearable:2.0.0'
当我们希望您注意代码块中的特定部分时,相关的行或项目会以粗体显示:
<?xml version="1.0" encoding="utf-8"?> <android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.ashok.packt.wear_note_1.MainActivity" tools:deviceIds="wear"> </android.support.wearable.view.BoxInsetLayout>
任何命令行输入或输出都如下所示:
adb connect 192.168.1.100
新术语和重要词汇会以粗体显示。您在屏幕上看到的词,例如菜单或对话框中的,会像这样出现在文本中:“让默认选择的模板为始终开启的穿戴应用代码存根 Always On Wear Activity。”
警告或重要提示会像这样出现。
提示和技巧会像这样出现。
读者反馈
我们一直欢迎读者的反馈。请告诉我们您对这本书的看法——您喜欢或不喜欢的地方。读者的反馈对我们很重要,因为它能帮助我们开发出您真正能从中获益的图书。要给我们发送一般性反馈,只需发送电子邮件至feedback@packtpub.com
,并在邮件的主题中提及书籍的标题。如果您在某个主题上有专业知识,并且有兴趣撰写或参与书籍编写,请查看我们的作者指南:www.packtpub.com/authors。
客户支持
既然您已经拥有了 Packt 的一本书,我们有很多方法可以帮助您充分利用您的购买。
下载示例代码
您可以从您的账户www.packtpub.com
下载本书的示例代码文件。如果您在其他地方购买了这本书,可以访问www.packtpub.com/support
注册,我们会直接将文件通过电子邮件发送给您。您可以按照以下步骤下载代码文件:
-
使用您的电子邮件地址和密码登录或注册我们的网站。
-
将鼠标悬停在顶部的“支持”标签上。
-
点击“代码下载与勘误”。
-
在搜索框中输入书籍的名称。
-
选择您要下载代码文件的书。
-
从下拉菜单中选择您购买本书的地方。
-
点击“代码下载”。
下载文件后,请确保您使用最新版本的以下软件解压或提取文件夹:
-
对于 Windows 用户,可以使用 WinRAR / 7-Zip。
-
对于 Mac 用户,可以使用 Zipeg / iZip / UnRarX。
-
对于 Linux 用户,可以使用 7-Zip / PeaZip。
本书的代码包也托管在 GitHub 上:github.com/PacktPublishing/Android-Wear-Projects
。我们还有其他丰富的书籍和视频代码包,可以在github.com/PacktPublishing/
查看。请查看!
勘误
尽管我们已经尽力确保内容的准确性,但错误仍然会发生。如果您在我们的书中发现了一个错误——可能是文本或代码中的错误——如果您能报告给我们,我们将不胜感激。这样做,您可以避免其他读者感到沮丧,并帮助我们改进本书后续版本。如果您发现任何勘误信息,请通过访问www.packtpub.com/submit-errata
,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情。一旦您的勘误信息被验证,您的提交将被接受,勘误信息将被上传到我们的网站或添加到该标题勘误部分下现有的勘误列表中。要查看之前提交的勘误信息,请前往www.packtpub.com/books/content/support
,并在搜索字段中输入书名。所需信息将在勘误部分下显示。
盗版
互联网上版权材料的盗版问题在所有媒体中持续存在。在 Packt,我们非常重视保护我们的版权和许可。如果您在任何形式的互联网上发现我们作品非法副本,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。请通过 copyright@packtpub.com
联系我们,并提供疑似盗版材料的链接。我们感谢您帮助保护我们的作者和我们提供有价值内容的能力。
问题
如果您对本书的任何方面有问题,可以通过 questions@packtpub.com
联系我们,我们将尽力解决问题。
第一章:让你准备好起飞 - 设置你的开发环境
穿戴实用性工具的文化一直是现代文明的一部分,它帮助我们执行某些动作。对人类来说,手表已经成为检查时间和日期的增强工具。佩戴手表可以让你只需一瞥就能检查时间。技术将这种戴表体验带到了下一个层次。第一个现代可穿戴手表是计算器和手表的组合,于 1970 年推出。几十年来,微处理器和无线的进步导致了“无处不在的计算”这一概念的引入。在此期间,大多数领先的电子行业初创公司开始工作在自己的想法上,这使得可穿戴设备变得非常流行。
技术巨头公司,如谷歌、苹果、三星和索尼,都加入了可穿戴设备时代的行列。它们推出了自己的竞争性可穿戴产品,在可穿戴设备市场上取得了极大的成功。更有趣的是,谷歌的 Android Wear 非常强大,遵循与安卓智能手机开发相同的实践,并且与苹果手表操作系统和三星的 Tizen 操作系统开发者社区相比,拥有非常好的开发者社区。
谷歌在 2014 年 3 月宣布了 Android Wear。自那时起,作为智能手表和可穿戴软件平台的 Android Wear 一直在发展。谷歌在设计和用户体验方面的持续进步导致了新一代 Android Wear 操作系统的诞生,它能够以前所未有的方式处理生物识别传感器,并在平台上拥有更多功能;谷歌称之为 Android Wear 2.0。
Android Wear 2.0 在应用开发方面将带来很多令人兴奋的竞争性功能。Android Wear 2.0 允许开发者构建和雕琢自己针对 Android Wear 的特定想法;无需将手表和移动应用配对。谷歌称之为独立应用。Android Wear 2.0 引入了一种在 Android 手表内输入的新方式:一个新的应用程序编程接口,称为 Complications,它允许表盘显示来自生物识别和其他传感器的重要信息。Android Wear 2.0 新的通知更新支持将帮助用户和开发者以更全面的方式呈现通知。
在本章中,我们将探讨以下内容:
-
安卓穿戴设计原则
-
探索特定于穿戴应用的基本 UI 组件
-
为穿戴应用开发设置开发环境
-
创建你的第一个安卓穿戴应用
安卓穿戴设计原则
设计穿戴应用与设计移动或平板应用不同。穿戴操作系统非常轻量级,并且有特定的任务要通过与穿戴者分享正确信息来完成。
通用穿戴原则是及时、一目了然、易于点击、节省时间。
及时
在正确的时间提供正确的信息。
一目了然
保持穿戴应用用户界面的清洁和整洁。
易于点击
用户将点击的动作应该具有适当的间距和图片大小。
节省时间
创建能够快速完成任务的最佳应用流程。
对于任何穿戴应用,我们需要适当的构建块来控制应用程序的业务逻辑和其他架构实现。以下是开发穿戴应用的情况,有助于我们更好地雕刻穿戴应用:
-
定义布局
-
创建列表
-
显示确认信息
-
穿戴设备导航与操作
-
多功能按钮
定义布局
穿戴应用可以使用我们在手持 Android 设备编程中使用的相同布局,但需要针对穿戴应用的具体限制。在穿戴应用中,我们不应执行类似于手持 Android 设备的繁重处理操作,并期待良好的用户体验。
针对圆形屏幕设计的应用在方形穿戴设备上可能不会看起来很好。为了解决这个问题,Android Wear 支持库提供了以下两个解决方案:
-
BoxInsetLayout
-
曲线布局
我们可以提供不同的资源,让 Android 在运行时检测 Android Wear 的形状。
创建列表
列表允许用户从一组条目中选择一个条目。在旧的 Wear 1.x API 中,WearableListView
帮助程序员构建列表和自定义列表。Wearable UI 库现在有支持curvedLayout
的WearableRecyclerView
,在穿戴设备上拥有最佳的实现体验。
我们可以添加手势和其他出色的功能:
探索穿戴设备的 UI 组件
在这一小节中,让我们探索常用的穿戴特定 UI 组件。在穿戴应用编程中,我们可以使用在移动应用编程中使用的所有组件,但在使用之前需要仔细考虑如何在穿戴设备中容纳这些组件的视觉外观。
WatchViewStub
:WatchViewStub
帮助针对不同形态的穿戴设备渲染视图。如果你的应用被安装在圆形手表设备上,WatchViewStub
将加载针对圆形手表的特定布局配置。如果是方形,它将加载方形布局配置:
<?xml version="1.0" encoding="utf-8"?>
<android.support.wearable.view.WatchViewStub
android:id="@+id/watch_view_stub"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:rectLayout="@layout/rect_activity_main"
app:roundLayout="@layout/round_activity_main"
tools:context="com.ashokslsk.wearapp.MainActivity"
tools:deviceIds="wear"></android.support.wearable.view.WatchViewStub>
WearableRecyclerView
:WearableRecyclerView
是特定于穿戴设备的recyclerview
实现。它为穿戴设备视口中的数据集提供了灵活的视图。我们将在接下来的章节中详细探索WearableRecyclerView
。
<android.support.wearable.view.WearableRecyclerView
android:id="@+id/recycler_launcher_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
注意:WearableListView
已弃用;Android 社区建议使用WearableRecyclerView
。
CircledImageVIew
:一个由圆形环绕的Imageview
。对于展示圆形形态穿戴设备中的图片来说,这是一个非常方便的组件:
<?xml version="1.0" encoding="utf-8"?>
<android.support.wearable.view.CircledImageView
android:id="@+id/circledimageview"
app:circle_color="#2878ff"
app:circle_radius="50dp"
app:circle_radius_pressed="50dp"
app:circle_border_width="5dip"
app:circle_border_color="#26ce61"
android:layout_marginTop="15dp"
android:src="img/skholinguaicon"
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
BoxInsetLayout
:这个布局直接扩展到Framelayout
,并且能够识别 Wearable 设备的形状因素。形状感知的FrameLayout
可以将子元素框定在屏幕中心的正方形内:
<android.support.wearable.view.BoxInsetLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.ranjan.androidwearuicomponents.BoxInsetLayoutDemo">
<TextView
android:text="@string/hello_world"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_box="all" />
</android.support.wearable.view.BoxInsetLayout>
在 Wear 2.0 发布之后,为了沉浸式活动体验,一些组件被弃用,谷歌严格禁止使用它们;我们仍然可以使用 Android 编程中我们了解的所有组件。
显示确认操作
与手持 Android 设备中的确认操作相比,在 Wear 应用程序中,确认操作应该占据整个屏幕或比手持设备显示的对话框更多。这确保用户可以一眼看到这些确认操作。Wearable UI 库帮助在 Android Wear 中显示确认计时器和动画计时器。
DelayedConfirmationView(延迟确认视图)
DelayedConfirmationView
是一个基于计时器的自动确认视图:
********
<android.support.wearable.view.DelayedConfirmationView
android:id="@+id/delayed_confirm"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="img/cancel_circle"
app:circle_border_color="@color/lightblue"
app:circle_border_width="4dp"
app:circle_radius="16dp">
</android.support.wearable.view.DelayedConfirmationView>
Wear 导航与操作
在 Android Wear 的新版本中,Material design库增加了以下两个交互式抽屉:
-
导航抽屉
-
操作抽屉
导航抽屉
允许用户在应用程序中的视图之间切换。开发者可以通过将setShouldOnlyOpenWhenAtTop()
方法设置为 false,允许抽屉在滚动父内容内的任何位置打开:
<android.support.wearable.view.drawer.WearableNavigationDrawer
android:id="@+id/top_drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_red_light"
app:navigation_style="single_page"/>
操作抽屉
操作抽屉为您的应用程序提供了简单且常用的操作。默认情况下,操作抽屉出现在屏幕底部,并为用户提供特定操作:
<android.support.wearable.view.drawer.WearableActionDrawer
android:id="@+id/bottom_drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_blue_dark"
app:show_overflow_in_peek="true"/>
多功能按钮
除了电源按钮外,Android Wear 还支持设备上的多功能按钮。Wearable 支持库提供了由制造商包含的多功能按钮的 API:
@Override
// Activity
public boolean onKeyDown(int keyCode, KeyEvent event){
if (event.getRepeatCount() == 0) {
if (keyCode == KeyEvent.KEYCODE_STEM_1) {
// Do stuff
return true;
} else if (keyCode == KeyEvent.KEYCODE_STEM_2) {
// Do stuff
return true;
} else if (keyCode == KeyEvent.KEYCODE_STEM_3) {
// Do stuff
return true;
}
}
return super.onKeyDown(keyCode, event);
}
对于有关 Wear 设备编程的设计指南的任何疑问,请访问developer.android.com/training/wearables/ui/index.html
。
为 Wear 开发设置开发环境
在本节中,我们将为 Wear 应用程序开发设置一个开发环境。
先决条件
-
您喜欢的操作系统(Windows、macOS 或 Linux)
-
确定您的操作系统上是否安装了最新版本的 JRE
-
安装最新版本的 JDK 或 Open JDK
-
安装最新版本的 Android Studio(在撰写本书时,最新版本为 2.2.3,任何更新的版本应该都可以)
安装 Android Studio
访问developer.android.com/studio/index.html
下载最新版本的 Android Studio。谷歌强烈建议为所有 Android 应用程序开发使用 Android Studio,因为 Android Studio 与 Gradle 紧密集成并提供了有用的 Android API:
安装完 Android Studio 之后,现在需要在 SDK 管理器的 SDK Platforms 标签中下载必要的 SDK。安装一个完整的 Android 版本;在本书的范围内,我们将安装 Android 7.1.1 API 级别 25:
成功安装了 Nougat 7.1.1 API 级别 25 的 SDK 后,在SDK Tools标签下,确保你已经安装了以下组件,如下面的截图所示:
-
Android 支持库
-
Google Play 服务
-
Google 仓库
-
Android 支持仓库
谷歌会定期更新 IDE 和 SDK 工具,请保持你的开发环境是最新的。
注意:如果你打算让你的应用程序在中国可用,那么你必须使用 Google Play 服务客户端库的特殊发布版本 7.8.87 来处理手机和手表之间的通信:developer.android.com/training/wearables/apps/creating-app-china.html
访问以下链接查看 SDK 工具的更新发行说明:developer.android.com/studio/releases/sdk-tools.html.
强烈建议从稳定版本通道更新你的 IDE。Android Studio 的更新在四个不同的通道上可用:
-
金丝雀版本
-
开发版本
-
测试版本
-
稳定版本
金丝雀版本(Canary channel):Android Studio 的工程团队持续工作以改进 Android Studio。在这个版本通道中,每周都会发布一次更新,其中将包含新的功能更改和改进;你可以在发行说明中查看这些更改。但此通道的更新不推荐用于应用程序的生产环境。
开发版本(Dev Channel):在这个通道上,发布版本会在 Android Studio 团队完成一轮完整的内部测试后进行。
测试版本(Beta channel):在这个通道上,更新完全基于稳定的金丝雀版本。在将这些版本发布到稳定通道之前,谷歌会在测试版本通道中发布它们以获取开发者的反馈。
稳定版本(Stable Channel):是 Android Studio 的官方稳定版本,你可以在谷歌的官方页面developer.android.com/studio.
下载。
默认情况下,Android Studio 会从稳定版本通道接收更新。
创建你的第一个 Android Wear 应用程序
在这一节中,让我们了解创建你的第一个 Wear 项目所需的基本步骤。
在你继续创建应用程序之前,请确保你已经安装了一个带有 Wear 系统映像的完整 Android 版本,并且你有最新版本的 Android Studio。
下面的图片是 Android Studio 的初始界面。在这个窗口中,可以导入旧的 ADT Android 项目,配置 Android SDK,以及更新 Android Studio。
Android Studio 欢迎窗口,带有开始操作的基本控制:
创建你的第一个穿戴项目
在 Android Studio 窗口中点击“开始新的 Android Studio 项目”选项。你将会看到一个包含项目详细信息的窗口。
下面的截图显示了允许用户配置他们的项目详细信息(如项目名称、包名称以及项目是否需要本地 C++ 支持)的窗口:
你可以按自己的意愿为项目命名。选择项目名称和项目在本地系统的位置后,你可以在窗口中按下“下一步”按钮,这将打开另一个包含一些配置查询的窗口,如下截图所示:
在这个窗口中,如果你取消勾选“手机和平板电脑”选项,可以选择编写一个独立的穿戴应用程序。这样,你将只看到穿戴应用程序模板:
现在,Android Studio 模板仅以下列选项提示 Android Wear 活动模板:
-
添加无活动
-
始终开启的穿戴活动
-
空白穿戴活动
-
显示通知
-
Google 地图穿戴活动
-
表盘界面
活动模板选择器可以帮助你访问默认的基础模板代码,这些代码已经模板化,可以直接在项目中使用:
要创建第一个项目,我们将选择“空白穿戴活动”,并在窗口中点击“下一步”按钮。Android Studio 将提示另一个窗口以创建活动和布局文件的名称。在这个模板中,Android 可穿戴设备的两种形状因素(大部分是圆形和方形)已经预填充了基础代码存根:
当你的项目准备好创建时,点击“完成”按钮。点击完成后,Android Studio 将会花一些时间为我们创建项目。
做得好!你现在已经为 Android Wear 独立应用程序创建了一个工作基础模板代码,无需手机伴侣应用程序。成功创建后,你会看到以下文件和代码默认添加到你的项目中:
如果你的 SDK 没有更新到 API 级别 25,你可能会在 Android Studio 项目创建提示中看到带有 Android Wear 支持库 1.x 的穿戴选项;你可以在 Wear 模块的 Gradle 文件中用以下依赖关系进行更新:
compile 'com.google.android.support:wearable:2.0.0'
创建穿戴模拟器
创建穿戴模拟器的过程与创建手机模拟器非常相似。
在 AVD 管理器中,点击“创建虚拟设备…”按钮:
根据你的应用程序需求,选择所需的模拟器形状因素。现在,让我们创建一个 Android Wear 方形模拟器:
选择适合你的穿戴的正确模拟器后,你将得到另一个提示,选择穿戴操作系统。让我们选择如下的 API 级别 25 Nougat 模拟器:
最后一个提示会根据您的需求询问模拟器名称和其他方向配置:
做得好!现在,我们已经成功为项目创建了一个方形表盘的模拟器。让我们在模拟器中运行我们创建的项目:
谷歌建议在真实硬件设备上开发 Wear 应用以获得最佳用户体验。然而,在模拟器上工作可以创建不同的屏幕表盘尺寸,以检查应用程序的渲染效果。
使用实际的 Wear 设备工作
-
在 Wear 设备上打开设置菜单
-
前往关于设备
-
点击构建号七次以启用开发者模式
-
现在,在手表上启用 ADB 调试
您现在可以通过 USB 电缆直接将 Wear 设备连接到您的机器。通过以下设置,您可以通过 Wi-Fi 和蓝牙调试应用程序。
通过 Wi-Fi 调试
确保您的手表已启用开发者选项。只有当 Wear 设备和机器连接到同一网络时,才能通过 Wi-Fi 进行调试。
-
在 Wear 设备开发者选项中,点击通过 Wi-Fi 调试
-
手表将显示其 IP 地址(例如,192.168.1.100)。记下这个信息;下一步我们需要用到。
-
将调试器连接到设备
-
使用以下命令,我们可以将实际设备连接到 ADB 调试器:
adb connect 192.168.1.100
启用蓝牙调试
我们需要确保在开发者选项中启用了调试,如下所示:
-
启用通过蓝牙调试
-
在伴侣应用中前往设置
-
启用通过蓝牙调试
-
通过电缆将手机连接到机器
-
您可以使用以下命令建立连接:
adb forward tcp:4444 localabstract:/adb-hub
adb connect 127.0.0.1:4444
在您的 Android Wear 中,当它询问时允许 ADB 调试。
既然我们已经有了开发环境的工作设置,让我们了解基本的 Android Wear 特定 UI 组件。
摘要
在本章中,我们了解了 Wear 应用程序开发的初步设置。我们了解了需要下载的必要组件,设置 Wear 模拟器,将 Wear 模拟器连接到 ADB 桥,通过 Wi-Fi 进行调试以及特定于 Wear 开发的用户界面组件。在下一章中,我们将探讨如何构建一个记事本应用程序,该程序可以保存用户输入的数据。
第二章:让我们帮助捕捉你的想法 —— WearRecyclerView 及更多功能
戴上能帮助我们执行某些动作的实用工具,这种文化一直是现代文明的一部分。对人类来说,腕表已经成为检查时间和日期的增强工具。佩戴手表可以让你只需一瞥就能知道时间。科技将这种戴表体验带到了下一个层次。第一款现代可穿戴手表是计算器和手表的组合,于 1970 年推出。几十年来,微处理器和无线的进步导致了“无处不在的计算”这一概念的出现。在这段时间里,大多数领先的电子行业初创公司开始着手他们的想法,这使得可穿戴设备变得非常流行。
科技巨头公司,如谷歌、苹果、三星和索尼,都加入了可穿戴设备时代的行列。他们推出了自己的竞争性可穿戴产品,这些产品在可穿戴设备市场上取得了极大的成功。更有趣的是,谷歌的 Android Wear 功能强大,遵循与 Android 智能手机开发相同的实践,并且与 Apple Watch OS 和三星的 Tizen OS 开发社区相比,拥有非常好的开发者社区。
谷歌在 2014 年 3 月宣布了 Android Wear。从那时起,作为智能手表和可穿戴软件平台的 Android Wear 一直在发展。谷歌在设计和用户体验方面的持续进步导致了新一代 Android Wear 操作系统的诞生,该系统具有前所未有的处理生物识别传感器的能力,平台功能更多;谷歌称之为 Android Wear 2.0。
Android Wear 2.0 在应用开发方面将引起极大的兴奋,具有显著竞争力的特性待开发。Android Wear 2.0 允许开发者构建和雕琢他针对 Android Wear 的特定想法;无需将手表与移动应用配对。谷歌称之为独立应用程序。Android Wear 2.0 引入了一种在 Android 表中输入的新方式:一个新的应用程序编程接口,称为 Complications,它允许表盘显示来自生物识别和其他传感器的重要信息。Android Wear 2.0 的新更新通知支持将帮助用户和开发者以更全面的方式呈现通知。
在本章中,我们将探讨以下内容:
-
Android Wear 设计原则
-
探索特定于可穿戴应用的基本 UI 组件
-
为可穿戴应用开发设置开发环境
-
创建你的第一个 Android Wear 应用程序
Android Wear 设计原则
设计可穿戴应用与设计移动或平板应用不同。Wear 操作系统非常轻量级,并且有特定的任务要完成,即与佩戴者分享正确信息。
通用可穿戴原则是:及时性、一瞥即得、易于轻触、节省时间。
及时性
在正确的时间提供正确的信息。
一瞥即得
保持穿戴应用用户界面干净、不杂乱。
易于点击
用户将点击的动作应该具有适当的间距和图片大小。
节省时间
创建能够快速完成任务的最佳应用流程。
对于任何穿戴应用,我们需要合适的构建块来控制应用程序的业务逻辑和其他架构实现。以下是开发穿戴应用的场景,帮助我们更好地雕刻穿戴应用:
-
定义布局
-
创建列表
-
显示确认信息
-
穿戴导航和动作
-
多功能按钮
定义布局
穿戴应用可以使用我们在手持式 Android 设备编程中使用的相同布局,但需要针对穿戴应用的具体限制。我们不应该在穿戴应用中进行类似于手持式 Android 设备的繁重处理操作,并期待良好的用户体验。
为圆形屏幕设计的应用在方形穿戴设备上看起来不会很好。为了解决这个问题,Android Wear 支持库提供了以下两个解决方案:
-
BoxInsetLayout
-
Curved Layout
(曲线布局)
我们可以提供不同的资源,允许 Android 在运行时检测 Android Wear 的形状。
创建列表
列表允许用户从一组项目中选择一个项目。在旧版的 Wear 1.x API 中,WearableListView
帮助程序员构建列表和自定义列表。现在,穿戴 UI 库有了支持curvedLayout
的WearableRecyclerView
,并在穿戴设备上拥有最佳的实现体验。
我们可以添加手势和其他出色的功能:
探索穿戴设备的 UI 组件
在这一小节中,让我们探索常用的穿戴特定 UI 组件。在穿戴应用编程中,我们可以使用我们在移动应用编程中使用的所有组件,但在使用之前,我们需要仔细考虑如何在穿戴设备中容纳组件的视觉外观。
WatchViewStub
:WatchViewStub
有助于为不同表盘形状的穿戴设备渲染视图。如果您的应用安装在圆形手表设备上,WatchViewStub
将加载针对圆形手表的特定布局配置。如果是方形,它将加载方形布局配置:
<?xml version="1.0" encoding="utf-8"?>
<android.support.wearable.view.WatchViewStub
android:id="@+id/watch_view_stub"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:rectLayout="@layout/rect_activity_main"
app:roundLayout="@layout/round_activity_main"
tools:context="com.ashokslsk.wearapp.MainActivity"
tools:deviceIds="wear"></android.support.wearable.view.WatchViewStub>
WearableRecyclerView
:WearableRecyclerView
是专为穿戴设备实现的recyclerview
。它为穿戴设备视口中的数据集提供了灵活的视图。我们将在接下来的章节中详细探讨WearableRecyclerView
:
<android.support.wearable.view.WearableRecyclerView
android:id="@+id/recycler_launcher_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
注意:WearableListView
已弃用;Android 社区建议使用WearableRecyclerView
。
CircledImageVIew
:一个由圆形环绕的Imageview
。这是在圆形表盘穿戴设备上展示图片的非常实用的组件:
<?xml version="1.0" encoding="utf-8"?>
<android.support.wearable.view.CircledImageView
android:id="@+id/circledimageview"
app:circle_color="#2878ff"
app:circle_radius="50dp"
app:circle_radius_pressed="50dp"
app:circle_border_width="5dip"
app:circle_border_color="#26ce61"
android:layout_marginTop="15dp"
android:src="img/skholinguaicon"
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
BoxInsetLayout
:此布局直接扩展到Framelayout
,并且能够识别 Wearable 设备的形状因素。形状感知的FrameLayout
可以将子元素框定在屏幕中心正方形内:
<android.support.wearable.view.BoxInsetLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.ranjan.androidwearuicomponents.BoxInsetLayoutDemo">
<TextView
android:text="@string/hello_world"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_box="all" />
</android.support.wearable.view.BoxInsetLayout>
自从 Wear 2.0 版本发布后,为了提供沉浸式活动体验,一些组件被弃用,谷歌严格禁止使用它们;我们仍然可以在 Android 编程中使用我们所熟知的所有组件。
显示确认信息
与手持 Android 设备中的确认操作相比,在 Wear 应用程序中,确认操作应该占据整个屏幕或比手持设备显示的对话框更多。这确保用户可以一眼看到这些确认信息。Wearable UI 库帮助在 Android Wear 中显示确认计时器和动画计时器。
DelayedConfirmationView
DelayedConfirmationView
是基于计时器的自动确认视图:
********
<android.support.wearable.view.DelayedConfirmationView
android:id="@+id/delayed_confirm"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="img/cancel_circle"
app:circle_border_color="@color/lightblue"
app:circle_border_width="4dp"
app:circle_radius="16dp">
</android.support.wearable.view.DelayedConfirmationView>
Wear 导航和动作
在 Android Wear 的新版本中,Material design库新增了以下两种交互式抽屉:
-
导航抽屉
-
动作抽屉
导航抽屉
允许用户在应用程序中的视图之间切换。开发者可以通过将setShouldOnlyOpenWhenAtTop()
方法设置为 false,允许抽屉在滚动父内容内的任何位置打开。
<android.support.wearable.view.drawer.WearableNavigationDrawer
android:id="@+id/top_drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_red_light"
app:navigation_style="single_page"/>
动作抽屉
动作抽屉提供了访问应用程序中简单且常见的操作。默认情况下,动作抽屉出现在屏幕底部,并为用户提供特定操作:
<android.support.wearable.view.drawer.WearableActionDrawer
android:id="@+id/bottom_drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_blue_dark"
app:show_overflow_in_peek="true"/>
多功能按钮
除了电源按钮外,Android Wear 还支持设备上的另一个按钮,即多功能按钮。Wearable 支持库提供了确定制造商包含的多功能按钮的 API:
@Override
// Activity
public boolean onKeyDown(int keyCode, KeyEvent event){
if (event.getRepeatCount() == 0) {
if (keyCode == KeyEvent.KEYCODE_STEM_1) {
// Do stuff
return true;
} else if (keyCode == KeyEvent.KEYCODE_STEM_2) {
// Do stuff
return true;
} else if (keyCode == KeyEvent.KEYCODE_STEM_3) {
// Do stuff
return true;
}
}
return super.onKeyDown(keyCode, event);
}
如果您对 Wear 设备编程的设计指南有任何疑问,请访问developer.android.com/training/wearables/ui/index.html
。
设置 Wear 开发的开发环境
在本节中,我们将为 Wear 应用程序开发设置一个开发环境。
先决条件
-
您喜欢的操作系统(Windows、macOS 或 Linux)
-
确定您的操作系统上是否安装了最新版本的 JRE
-
安装最新版本的 JDK 或 Open JDK
-
安装最新版本的 Android Studio(在撰写本书时,最新版本是 2.2.3,任何更新的版本应该都可以)
安装 Android Studio
访问developer.android.com/studio/index.html
下载最新版本的 Android Studio。谷歌强烈建议在所有 Android 应用程序开发中使用 Android Studio,因为 Android Studio 与 Gradle 紧密集成并提供了有用的 Android API:
安装完 Android Studio 后,现在是在 SDK 管理器的 SDK Platforms 标签下下载必要的 SDK 的时候了。安装一个完整的 Android 版本;在本书的范围内,我们将安装 Android 7.1.1 API 级别 25:
在成功安装了 Nougat 7.1.1 API 级别 25 的 SDK 后,在SDK 工具标签下,确保你已经安装了以下组件,如下面的截图所示:
-
Android 支持库
-
Google Play 服务
-
Google 仓库
-
Android 支持仓库
谷歌频繁发布 IDE 和 SDK 工具的更新;请保持你的开发环境是最新的。
注意:如果你打算让你的应用程序在中国可用,那么你必须使用 Google Play 服务客户端库的特殊发布版本 7.8.87 来处理手机和手表之间的通信:developer.android.com/training/wearables/apps/creating-app-china.html
访问以下链接查看 SDK 工具的更新发布说明:developer.android.com/studio/releases/sdk-tools.html.
强烈建议从稳定渠道更新你的 IDE。Android Studio 的更新在四个不同的渠道上可用:
-
金丝雀渠道
-
开发渠道
-
测试版渠道
-
稳定渠道
**金丝雀渠道:**Android Studio 工程团队不断努力使 Android Studio 变得更好。在这个渠道上,每周将有一个更新发布,并将包括新的功能变更和改进;你可以在发布说明中查看这些更改。但此渠道的更新不建议用于应用程序生产。
**开发渠道:**在这个渠道上,发布会在 Android Studio 团队完成一轮完整的内部测试后进行。
**测试版渠道:**在这个渠道上,更新完全基于稳定的金丝雀构建。在将这些构建发布到稳定渠道之前,谷歌会在测试版渠道中发布它们以获取开发者的反馈。
**稳定渠道:**是 Android Studio 的官方稳定版本,可在谷歌官方页面 developer.android.com/studio.
下载。
默认情况下,Android Studio 从稳定渠道接收更新。
创建你的第一个 Android Wear 应用程序
在本节中,让我们了解创建你的第一个 Wear 项目的必要步骤。
在你继续创建你的应用程序之前,请确保你已经安装了一个带有 Wear 系统镜像的完整版 Android,并且你有最新版本的 Android Studio。
下面的图片是 Android Studio 的初始界面。在这个窗口中,你可以导入传统的 ADT Android 项目,配置 Android SDK,以及更新 Android Studio。
Android Studio 欢迎窗口,带有开始使用的基本控制:
创建你的第一个 Wear 项目
在 Android Studio 窗口中点击“开始新的 Android Studio 项目”选项。您将看到一个带有项目详情的窗口。
下面的截图显示了允许用户配置项目详情的窗口,如项目名称、包名称以及项目是否需要本地 C++支持:
您可以按自己的意愿为项目命名。选择项目名称和项目在本地系统的位置后,您可以点击窗口中的“下一步”按钮,这将打开另一个带有一些配置查询的窗口,如下截图所示:
在此窗口中,如果您取消勾选“手机和平板电脑”选项,可以选择编写独立的 Wear 应用程序。这样,您将只看到 Wear 应用程序模板:
现在,Android Studio 模板仅提示以下选项集的 Android Wear 活动模板:
-
添加无活动
-
始终开启的 Wear 活动
-
空白 Wear 活动
-
显示通知
-
Google Maps Wear 活动
-
表盘界面
活动模板选择器可以帮助您访问默认的模板化样板代码,这些代码可以直接在项目中使用:
要创建第一个项目,我们将选择“空白 Wear 活动”,并在窗口中点击“下一步”按钮。Android Studio 将提示另一个窗口以创建活动和布局文件的名称。在此模板中,Android 可穿戴设备的两种形式因素(主要是圆形和方形)已经预填充了样板代码存根:
当您的项目准备好创建时,点击“完成”按钮。点击完成后,Android Studio 将花费一些时间为我们创建项目。
做得好!您现在已经为 Android Wear 独立应用程序创建了工作的样板代码,而没有手机伴侣应用程序。成功创建后,您将看到以下文件和代码默认添加到您的项目中:
如果您的 SDK 没有更新到 API 级别 25,那么在 Android Studio 项目创建提示中,您可能会看到带有 Android Wear 支持库 1.x 的 Wear 选项;你可以在 Wear 模块的 Gradle 文件中用以下依赖关系进行更新:
compile 'com.google.android.support:wearable:2.0.0'
创建 Wear 模拟器
创建 Wear 模拟器的过程与创建手机模拟器非常相似。
在 AVD 管理器中,点击“创建虚拟设备…”按钮:
根据您的应用程序需求选择所需的形式因素模拟器。现在,让我们创建一个 Android Wear 方形模拟器:
在为您的 Wear 选择合适的模拟器之后,您将看到一个提示,选择 Wear 操作系统。如下截图所示,我们选择 API 级别 25 的 Nougat 模拟器:
最后一个提示会根据你的需要询问模拟器名称和其他方向配置:
做得好!现在,我们已经成功为项目创建了一个方形尺寸的模拟器。让我们在模拟器中运行我们创建的项目:
谷歌建议在真实硬件设备上开发 Wear 应用以获得最佳用户体验。然而,在模拟器上工作可以创建不同的屏幕尺寸,以检查应用程序的渲染情况。
使用实际的 Wear 设备工作
-
在 Wear 设备上打开设置菜单
-
前往关于设备
-
点击构建号七次以启用开发者模式
-
现在在手表上启用 ADB 调试
现在你可以用 USB 线将 Wear 设备直接连接到你的机器上。通过以下设置,你可以在 Wi-Fi 和蓝牙上进行应用调试。
通过 Wi-Fi 进行调试
确保你的手表已启用开发者选项。只有在 Wear 设备和机器连接到同一网络时,才能通过 Wi-Fi 进行调试。
-
在 Wear 设备开发者选项中,点击“通过 Wi-Fi 调试”
-
手表将显示其 IP 地址(例如,192.168.1.100)。保留参考,下一步我们需要这个。
-
将调试器连接到设备
-
使用以下命令,我们可以将实际设备连接到 ADB 调试器:
adb connect 192.168.1.100
启用蓝牙调试
我们需要确保在开发者选项中启用了调试,如下所示:
-
启用通过蓝牙的调试
-
在伴侣应用的设置中
-
通过蓝牙启用调试
-
通过电缆将手机连接到机器
-
你可以使用以下命令建立连接:
adb forward tcp:4444 localabstract:/adb-hub
adb connect 127.0.0.1:4444
在你的 Android Wear 上,当它提示时允许 ADB 调试。
现在我们已经设置了开发环境,让我们了解基本的 Android Wear 特定 UI 组件。
概述
在本章中,我们了解了 Wear 应用开发的初步设置。我们了解了需要下载的必要组件,设置 Wear 模拟器,将 Wear 模拟器连接到 ADB 桥,通过 Wi-Fi 进行调试,以及特定于 Wear 开发的界面组件。在下一章中,我们将探讨如何构建一个笔记应用,该应用可以保存用户输入的数据。
第三章:让我们帮助捕捉你的想法 - 保存数据和定制 UI
从零开始构建记事本应用程序是一次很好的学习经历。在本章中,我们将了解如何按照 Wear 设计标准升级同一代码以实现新的用户界面。Wear-note 1 应用程序将通过以下更新迁移到 Wear-note 2:
-
集成 Realm 数据库
-
UI 更新
-
集成自定义字体和资源
-
完成项目收尾工作
为了在 Android Studio 中进一步协助开源 wear-note 1 项目,编译项目并逐屏检查项目。你会发现该应用程序的主要功能是在 Wear 设备上保存便签。在此应用程序中,我们有白色的背景和黑色的字体颜色。SharedPreferences
帮助应用程序持久化数据。
进一步回顾,我们知道如何使用 WearableRecyclerView
、DelayedConfirmationView
和 BoxInsetLayout
在 Wear 设备上获得最佳的应用体验。
接下来,让我们按照之前提到的更改来完成项目。我们将这个应用程序称为 wear-note-2。在你的 res
目录下的 values
目录中,打开 string.xml
文件,将应用程序名称更改为 Wear-note-2,如下所示:
<string name="app_name">Wear-note-2</string>
Wear-note-2
让我们开始在这个子模块中用 RealmDB 替换数据库并开发功能。
RealmDB 在现代 Android 编程世界中引起了轰动;它是内置在 Android SDK 中的 SQLite 数据库的一个简单替代品。Realm 并没有使用 SQLite 作为其核心引擎;它有自己的 C++ 核心,专为移动设备而设计。Realm 使用 C++ 核心以通用、基于表的形式保存数据。Realm 处理一系列复杂的查询,并允许从多种语言以及许多临时查询访问数据。
Realm 的优势
-
Realm 的速度是 SQLite 的 10 倍
-
易于使用
-
跨平台
-
内存高效
-
文档齐全且社区支持优秀
进一步探索 Realm,我们会发现 Realm 做了许多优化,比如整数打包和将常见字符串转换为枚举,这比 SQLite + ORM 数据库解决方案的性能更好。传统的 SQLite + ORM 抽象是泄漏的,ORM 仅将对象及其方法转换为 SQL 语句,导致性能不佳。另一方面,Realm 是一个面向对象的数据库,这意味着你的数据以对象形式保存,这在你的数据库中得到了体现。Realm 使用 B+ 树在内存中映射整个数据,每当查询数据时,Realm 计算偏移量,从内存映射区域读取,并返回原始值。通过这种方式,Realm 避免了零拷贝(传统的从数据库读取数据的方式会导致不必要的复制(原始数据 > 反序列化表示 > 语言级对象))。
当开发者想要实现延迟加载时,Realm 是一个完美的选择;因为属性以列的形式表示而不是行,它可以按需延迟加载属性,并且由于列结构,读取速度更快,而插入速度较慢。但对于移动应用程序的上下文来说,这是一个很好的权衡。
Realm 使用 多版本并发控制(MVCC)模型,这意味着可以同时进行多个读事务,并且在提交写事务的同时也可以进行读取。
欲了解更多信息,请访问realm.io/news/jp-simard-realm-core-database-engine/.
Realm 的缺点
在选择 Realm 之前,应该考虑其一些瓶颈。不过,对于高扩展性的 Android 应用程序,这些瓶颈是可以接受的:
-
我们无法将 Realmdb 导入到其他应用程序中
-
我们不能跨线程访问对象
-
不支持 ID 的自动递增
-
迁移 Realmdb 是一项痛苦的工作
-
缺乏复合主键
-
仍在积极开发中
在 Wear-note-1 项目中,将 string.xml
中的名称更改为 Wear-note-2 后,我们需要在 gradle-project 模块中添加适当的 Realm 依赖。在撰写本书时,最新的 Realm 版本是 3.0.0,因此我们将详细讨论依赖项和代码。
重新构建代码和依赖关系
Realm 现在有了一个新机制。它不仅仅是一个 Gradle 依赖项,还是一个 Gradle 插件。我们将学习如何在项目中添加和使用 Realm。Gradle 插件依赖项应该添加到项目范围,即项目级别的 Gradle 依赖项。添加以下类路径,并通过互联网使其与项目同步:
classpath "io.realm:realm-gradle-plugin:3.0.0"
将此依赖项添加到如下截图所示位置的 Gradle 项目级别文件中。在依赖项部分添加 classpath
:
添加类路径依赖后,完整的 Gradle 文件将如下所示:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
//Realm plugin
classpath "io.realm:realm-gradle-plugin:3.0.0"
// NOTE: Do not place your application dependencies here; they
belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
接下来,我们将把 Realm 插件应用到项目中,以使用如以下截图所示的所有 Realm 功能:
apply plugin: 'realm-android'
Realm 社区已经开源了一些修改过的 Gradle 文件样本:
[`github.com/realm/realm-java/blob/master/examples/build.gradle
https://github.com/realm/realm-java/blob/master/examples/introExample/build.gradle`](https://github.com/realm/realm-java/blob/master/examples/build.gradle)
现在,我们已经准备好将 SharedPreferences
替换为 Realm。让我们开始吧。
打开 Android Studio,在 model 包中,我们定义了 Note.java
POJO 类,其中包含原始数据字符串 notes 和字符串 ID。这些变量有它们的 getter 和 setter。额外的更改如下:
将 POJO 模型扩展到 RealmObject
并创建一个空构造函数:
public class Note extends RealmObject {
private String notes = "";
private String id = "";
//Empty constructor
public Note() {
}
public Note(String id, String notes) {
this.id = id;
this.notes = notes;
}
public String getNotes() {
return notes;
}
public void setNotes(String notes) {
this.notes = notes;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
在 MainActivity
中,我们将全局实例化 Realm
类,并在 onCreate
方法中如下初始化:
//MainActivity scope
//Realm Upgrade
private Realm realm;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
....
//Realm init
Realm.init(this);
realm = Realm.getDefaultInstance();
}
在updateAdapter()
方法中,我们将必须添加从 Realm 读取的查询,如下所示:
RealmResults<Note> results = realm.where(Note.class).findAll();
完整的方法将如下所示:
private void updateAdapter() {
RealmResults<Note> results = realm.where(Note.class).findAll();
myDataSet.clear();
myDataSet.addAll(SharedPreferencesUtils.getAllNotes(this));
mAdapter.setListNote(myDataSet);
mAdapter.notifyDataSetChanged();
}
Realm 数据库提供了许多查询,这些查询为存储的数据建立了一对一和一对多的关系,对于这个项目,前面的查询完成了所需的所有神奇工作。
在createNote()
方法中,我们将更改代码以使用 Realm 而不是SharedPreference
,如下所示:
private Note createNote(String id, String note) {
if (id == null) {
id = String.valueOf(System.currentTimeMillis());
realm.beginTransaction();
Note notes = realm.createObject(Note.class);
notes.setId(id);
notes.setNotes(note);
realm.commitTransaction();
}
return new Note(id, note);
}
为了删除记录,让我们创建一个新方法,并将其命名为deleteRecord()
。在这个方法中,我们将传递笔记的 ID,并从 Realm 中删除该笔记:
public void deleteRecord(String id){
RealmResults<Note> results = realm.where(Note.class).equalTo("id",
id).findAll();
realm.beginTransaction();
results.deleteAllFromRealm();
realm.commitTransaction();
}
现在,让我们在updateData()
方法中调用删除记录的方法,如下所示:
private void updateData(Note note, int action) {
if (action == Constants.ACTION_ADD) {
ConfirmationUtils.showMessage(getString(R.string.note_saved),
this);
} else if (action == Constants.ACTION_DELETE) {
deleteRecord(note.getId());
ConfirmationUtils.showMessage(getString(R.string.note_removed),
this);
}
updateAdapter();
}
完整的MainActivity
代码如下所示:
public class MainActivity extends WearableActivity implements RecyclerViewAdapter.ItemSelectedListener {
private static final String TAG = "MainActivity";
private static final int REQUEST_CODE = 1001;
private RecyclerViewAdapter mAdapter;
private List<Note> myDataSet = new ArrayList<>();
//Realm Upgrade
private Realm realm;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
configLayoutComponents();
Realm.init(this);
realm = Realm.getDefaultInstance();
}
private void configLayoutComponents() {
WearableRecyclerView recyclerView = (WearableRecyclerView)
findViewById(R.id.wearable_recycler_view);
recyclerView.setHasFixedSize(true);
LinearLayoutManager mLayoutManager = new
LinearLayoutManager(this);
recyclerView.setLayoutManager(mLayoutManager);
mAdapter = new RecyclerViewAdapter();
mAdapter.setListNote(myDataSet);
mAdapter.setListener(this);
recyclerView.setAdapter(mAdapter);
EditText editText = (EditText) findViewById(R.id.edit_text);
editText.setOnEditorActionListener(new
TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView textView, int
action, KeyEvent keyEvent) {
if (action == EditorInfo.IME_ACTION_SEND) {
String text = textView.getText().toString();
if (!TextUtils.isEmpty(text)) {
Note note = createNote(null, text);
SharedPreferencesUtils.saveNote(note,
textView.getContext());
updateData(note, Constants.ACTION_ADD);
textView.setText("");
return true;
}
}
return false;
}
});
}
private void updateAdapter() {
RealmResults<Note> results = realm.where(Note.class).findAll();
myDataSet.clear();
myDataSet.addAll(results);
mAdapter.setListNote(myDataSet);
mAdapter.notifyDataSetChanged();
}
@Override
protected void onResume() {
super.onResume();
updateAdapter();
}
@Override
public void onItemSelected(int position) {
Intent intent = new Intent(getApplicationContext(),
DeleteActivity.class);
intent.putExtra(Constants.ITEM_POSITION, position);
startActivityForResult(intent, REQUEST_CODE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (data != null && requestCode == REQUEST_CODE && resultCode
== RESULT_OK) {
if (data.hasExtra(Constants.ITEM_POSITION)) {
int position =
data.getIntExtra(Constants.ITEM_POSITION, -1);
if (position > -1) {
Note note = myDataSet.get(position);
updateData(note, Constants.ACTION_DELETE);
}
}
}
}
private void prepareUpdate(String id, String title, int action) {
if (!(TextUtils.isEmpty(id) && TextUtils.isEmpty(title))) {
Note note = createNote(id, title);
updateData(note, action);
}
}
private void updateData(Note note, int action) {
if (action == Constants.ACTION_ADD) {
ConfirmationUtils.showMessage
(getString(R.string.note_saved), this);
} else if (action == Constants.ACTION_DELETE) {
deleteRecord(note.getId());
ConfirmationUtils.showMessage(getString
(R.string.note_removed), this);
}
updateAdapter();
}
/**
* Notifica a Data Layer API que os dados foram modificados.
*/
private Note createNote(String id, String note) {
if (id == null) {
id = String.valueOf(System.currentTimeMillis());
realm.beginTransaction();
Note notes = realm.createObject(Note.class);
notes.setId(id);
notes.setNotes(note);
realm.commitTransaction();
}
return new Note(id, note);
}
public void deleteRecord(String id){
RealmResults<Note> results = realm.where(Note.class)
.equalTo("id", id).findAll();
realm.beginTransaction();
results.deleteAllFromRealm();
realm.commitTransaction();
}
@Override
protected void onDestroy() {
realm.close();
super.onDestroy();
}
}
现在,从功能上讲,我们已经将 Realmdb 集成到了 Wear-note-2 应用中。让我们编译一下看看结果。
Wear 记事本应用的主屏幕如下所示:
Wear 操作系统处理的 IMF 屏幕,用于获取用户的输入:
通过 Wear 支持库中的默认confirmationActivity
实现的‘保存动画’:
保存了笔记的主屏幕:
现在,我们已经将Sharedpreference
替换成了我们这个时代 Android 的最佳数据库。
在 Wear 用户界面上的工作
在 Wear-note 应用中,我们使用白色背景和黑色文字,并使用默认的 Roboto 字体。在准备一个好的 Wear 应用设计时,谷歌建议使用深色来提高电池效率。典型材料设计中使用的浅色方案在 Wear 设备上并不节能。浅色在 OLED 显示屏上耗能更低。
浅色需要以更亮的强度点亮像素。白色需要将像素中的 RGB 二极管点亮到 100%;应用中白色和浅色越多,应用的电池效率就越低。
浅色在暗光下或夜间使用 Wear 设备时会产生干扰。这种情况在使用深色时可能不会发生。与浅色不同,深色在激活时使屏幕亮度降低,从而在 OLED 显示屏上节省电池。
让我们开始着手 Wear-note-2 用户界面工作
让我们更改应用主题以适应标准设计指南。
在activity_main.xml
文件中,我们将编辑父容器的背景,即BoxInsetLayout
的背景android:background="#01579B"
改为钴蓝色。
在values
目录下添加一个新的color.xml
文件,并加入以下代码:
//Add this color value in the color.xml in res directory
<color name="cobalt_blue">#01579B</color>
添加颜色后,我们可以在生产应用中使用该颜色,如下所示:
<android.support.wearable.view.BoxInsetLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/container"
tools:context="com.ashok.packt.wear_note_1.activity.MainActivity"
tools:deviceIds="wear"
android:background="@color/cobalt_blue"
app:layout_box="all"
android:padding="5dp">
进一步,让我们更改EditText
提示文字的颜色,如下所示:
<EditText
android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_gravity="center"
android:gravity="center"
android:hint="@string/add_a_note"
android:imeOptions="actionSend"
android:inputType="textCapSentences|textAutoCorrect"
android:textColor="@color/white"
android:textColorHint="@color/white"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true" />
在each_item.xml
布局中,按照以下方式修改 XML 代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:layout_gravity="center"
android:clickable="true"
android:background="?android:attr/selectableItemBackground"
android:orientation="vertical">
<TextView
android:id="@+id/note"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:textColor="@color/white"
tools:text="note"/>
</LinearLayout>
现在,在activity_delete.xml
布局容器的相同位置进行更改,改变其背景颜色和TextView
的颜色。使用xmlns
属性可以更改DelayedConfirmationView
的颜色,如下代码所示:
<android.support.wearable.view.DelayedConfirmationView
android:id="@+id/delayed_confirmation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="img/ic_delete"
app:circle_border_color="@color/white"
app:circle_color="@color/white"
app:circle_border_width="8dp"
app:circle_radius="30dp"/>
开发人员无需担心更改DelayedConfirmationView
中的动画颜色,Wear 2.0 会自动适应DelayedConfirmationView
的主题,并改变主色方案,以创建统一的体验。
所有这些更改将如下反映在应用程序中:
IMF 屏幕用于从用户获取输入:
使用 Realm 保存数据后:
从数据库中删除便签:
更好的字体,更好的阅读体验
在数字设计的世界中,让你的应用程序视觉效果对用户的眼睛友好是很重要的。来自谷歌收藏的 Lora 字体拥有平衡的现代衬线,其根源在于书法。这是一种中等对比度的文本字体,非常适合正文字体。设置为 Lora 的段落将因其刷过的曲线与有力的衬线形成对比而令人难忘。Lora 的整体排版风格完美传达了现代故事或艺术论文的情绪。从技术上讲,Lora 针对屏幕显示进行了优化。
要将自定义字体添加到 Android 项目中,我们需要在根目录中创建assets
文件夹。查看以下截图:
我们可以直接将.ttf
文件添加到资产目录中,或者我们可以创建另一个目录和字体。你可以通过这个 URL 下载 Lora 字体:fonts.google.com/download?family=Lora
。
将字体文件添加到资产文件夹后,我们需要创建自定义的Textview
和EditText
。
在utils
包中,让我们创建一个名为LoraWearTextView
和LoraWearEditTextView
的类,如下所示:
现在,将LoraWearTextView
扩展到TextView
类,LoraWearEditTextView
扩展到EditText
类。在两个类中实现构造方法。创建一个名为init()
的新方法。在init
方法内部,实例化Typeface
类,并使用createFromAsset
方法,我们可以设置我们的自定义字体:
public void init() {
Typeface tf = Typeface.createFromAsset(getContext().getAssets(),
"fonts/Lora.ttf");
setTypeface(tf ,1);
}
前面的init
方法在这两个类中是相同的。在两个新自定义类的所有不同参数化构造函数中调用init
方法。
完整的类如下所示:LoraWearTextView.java
public class LoraWearTextView extends TextView {
public LoraWearTextView(Context context) {
super(context);
init();
}
public LoraWearTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public LoraWearTextView(Context context, AttributeSet attrs, int
defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public LoraWearTextView(Context context, AttributeSet attrs, int
defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
public void init() {
Typeface tf = Typeface.createFromAsset(getContext()
.getAssets(), "fonts/Lora.ttf");
setTypeface(tf ,1);
}
}
完整的类如下所示:LoraWearEditTextView.java
public class LoraWearEditTextView extends EditText {
public LoraWearEditTextView(Context context) {
super(context);
init();
}
public LoraWearEditTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public LoraWearEditTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public LoraWearEditTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
public void init() {
Typeface tf = Typeface.createFromAsset(getContext().getAssets(),
"fonts/Lora.ttf");
setTypeface(tf ,1);
}
在我们的 UI 布局中添加这两个类之后,我们可以替换实际的textview
和edittext
:
<com.ashok.packt.wear_note_1.utils.LoraWearEditTextView
android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_gravity="center"
android:gravity="center"
android:hint="@string/add_a_note"
android:imeOptions="actionSend"
android:inputType="textCapSentences|textAutoCorrect"
android:textColor="@color/white"
android:textColorHint="@color/white"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true" />
<com.ashok.packt.wear_note_1.utils.LoraWearTextView
android:id="@+id/note"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="center"
android:textColor="@color/white"
tools:text="note"/>
替换textview
和edittext
之后,编译程序,让我们看看结果:
通过confirmationViewActivity
删除便签动画:
在本章中,我们探讨了如何集成 Realm 流行数据库,理解了可穿戴设备的设计理念,并创建了自定义视图组件以获得更好的应用程序用户体验。
编写自定义布局以获得更好的用户体验
Android 提供了很好的方法来定制我们用作容器的布局。我们可以有复合视图并自定义布局以实现我们自己的目的。可穿戴设备使用与手持 Android 设备相同的布局技术,但需要针对特定的约束和配置进行设计。将手持 Android 设备组件的功能移植到可穿戴应用设计并不是一个好主意。要设计一个优秀的可穿戴应用,请遵循 Google 的设计指南,访问developer.android.com/design/wear/index.html
。让我们创建自定义视图并在可穿戴笔记应用程序中使用它们。
让我们按照加载项目的方式来为我们的布局实现动画。我们将有一个简单的滑动动画,我们将用我们编写的自定义布局来实现这一点。
让我们创建一个名为AnimatedLinearLayout
的类文件,并按照以下方式扩展它到LinearLayout
:
复合视图是将两个或多个视图捆绑为一个组件,例如,可勾选的相对布局。当用户点击布局时,它会像复选框一样突出显示布局。
public class AnimatedLinearLayout extends LinearLayout {
...
}
现在,我们需要从View
获取Animation
类。除了Animation
类,还要为currentChild
视图声明View
实例。由于我们正在编写布局,它可以保存子层次结构,因此我们需要一个视图实例作为引用:
Animation animation;
View currentChild;
当我们扩展LinearLayout
类时,我们会得到一些构造函数回调
,我们必须实现这些回调:
public AnimatedLinearLayout(Context context) {
super(context);
}
public AnimatedLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
...
}
在onWindowFocusChanged()
方法中,我们可以编写自定义动画的逻辑。在这里,本书介绍了SlideDown
、SlideDownMore
、RotateClockWise
、RotateAntiClockWise
和ZoomInAndRotateClockWise
。现在,为了实现这一点,我们需要检查视图是否已膨胀并且有窗口显示,以及布局有多少个childviews
:
if (hasWindowFocus) {
for (int index = 0; index < getChildCount(); index++) {
View child = getChildAt(index);
currentChild=child;
}
检查子元素是否是Viewgroup
的实例;如果视图是完全以其他隔离方式开发的,那么这个自定义布局将无法帮助该视图进行动画处理。使用子视图的标签属性,我们可以为动画分配一个字符串关联,如下所示:
if(!(child instanceof ViewGroup)) {
switch (child.getTag().toString()) {
case SLIDE_DOWN:
// write logic to slide down animation
对于向下滑动动画,使用我们全局创建的动画实例,我们必须设置插值器并通过子视图的高度和数值 2 来减速插值。查看以下代码:
case SLIDE_DOWN:
animation = new TranslateAnimation(0, 0, -
((child.getMeasuredHeight()/2) * (index + 1)), 0);
animation.setInterpolator(new DecelerateInterpolator());
animation.setFillAfter(true);
animation.setDuration(1000);
child.post(new AnimationRunnable(animation,child));
//child.startAnimation(animation);
break;
同样,我们将完成其他情况。让我们开始完成onWindowFocusChanged()
方法:
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
final String SLIDE_DOWN = "SlideDown";
final String SLIDE_DOWN_MORE = "SlideDownMore";
final String ROTATE_CLOCKWISE = "RotateClockWise";
final String ROTATE_ANTI_CLOCKWISE = "RotateAntiClockWise";
final String ZOOMIN_AND_ROTATE_CLOCKWISE =
"ZoomInAndRotateClockWise";
if (hasWindowFocus) {
for (int index = 0; index < getChildCount(); index++) {
View child = getChildAt(index);
currentChild=child;
if(!(child instanceof ViewGroup)) {
switch (child.getTag().toString()) {
case SLIDE_DOWN:
animation = new TranslateAnimation(0, 0, -
((child.getMeasuredHeight()/2) *
(index + 1)), 0);
animation.setInterpolator(new
DecelerateInterpolator());
animation.setFillAfter(true);
animation.setDuration(1000);
child.post(new
AnimationRunnable(animation,child));
//child.startAnimation(animation);
break;
case SLIDE_DOWN_MORE:
animation = new TranslateAnimation(0, 0, -
(child.getMeasuredHeight() * (index + 25)), 0);
animation.setInterpolator(new
DecelerateInterpolator());
animation.setFillAfter(true);
animation.setDuration(1000);
child.post(new
AnimationRunnable(animation,child));
//child.startAnimation(animation);
break;
case ROTATE_CLOCKWISE:
animation = new RotateAnimation(0, 360,
child.getMeasuredWidth() / 2,
child.getMeasuredHeight() / 2);
animation.setInterpolator(new
DecelerateInterpolator());
animation.setFillAfter(true);
animation.setDuration(1000);
child.post(new
AnimationRunnable(animation,child));
//child.startAnimation(animation);
break;
case ROTATE_ANTI_CLOCKWISE:
animation = new RotateAnimation(0, -360,
child.getMeasuredWidth() / 2,
child.getMeasuredHeight() / 2);
animation.setInterpolator(new
DecelerateInterpolator());
animation.setFillAfter(true);
animation.setDuration(1000);
child.post(new
AnimationRunnable(animation,child));
//child.startAnimation(animation);
break;
case ZOOMIN_AND_ROTATE_CLOCKWISE:
AnimationSet animationSet = new
AnimationSet(true);
animationSet.setInterpolator(new
DecelerateInterpolator());
animation = new ScaleAnimation(0, 1, 0, 1,
child.getMeasuredWidth() / 2,
child.getMeasuredHeight() / 2);
animation.setStartOffset(0);
animation.setFillAfter(true);
animation.setDuration(1000);
animationSet.addAnimation(animation);
animation = new RotateAnimation(0, 360,
child.getMeasuredWidth() / 2,
child.getMeasuredHeight() / 2);
animation.setStartOffset(0);
animation.setFillAfter(true);
animation.setDuration(1000);
animationSet.addAnimation(animation);
child.post(new
AnimationSetRunnable(animationSet,child));
//child.startAnimation(animationSet);
break;
}
}
}
}
}
现在,我们需要创建一个AnimationRunnable
类,它实现了Runnable
接口以启动动画:
private class AnimationRunnable implements Runnable{
private Animation animation;
private View child;
AnimationRunnable(Animation animation, View child) {
this.animation=animation;
this.child=child;
}
@Override
public void run() {
child.startAnimation(animation);
}
}
我们将实现另一个AnimationSetRunnable
类到可运行接口以设置动画:
private class AnimationSetRunnable implements Runnable{
private AnimationSet animation;
private View child;
AnimationSetRunnable(AnimationSet animation, View child) {
this.animation=animation;
this.child=child;
}
@Override
public void run() {
child.startAnimation(animation);
}
}
现在,我们已经完成了自己的自定义布局,该布局具有几种动画方法,可以应用于布局中的所有视图子项。这个自定义布局的完整类代码如下所示:
public class AnimatedLinearLayout extends LinearLayout {
Animation animation;
View currentChild;
public AnimatedLinearLayout(Context context) {
super(context);
}
public AnimatedLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
final String SLIDE_DOWN = "SlideDown";
final String SLIDE_DOWN_MORE = "SlideDownMore";
final String ROTATE_CLOCKWISE = "RotateClockWise";
final String ROTATE_ANTI_CLOCKWISE = "RotateAntiClockWise";
final String ZOOMIN_AND_ROTATE_CLOCKWISE =
"ZoomInAndRotateClockWise";
if (hasWindowFocus) {
for (int index = 0; index < getChildCount(); index++) {
View child = getChildAt(index);
currentChild=child;
if(!(child instanceof ViewGroup)) {
switch (child.getTag().toString()) {
case SLIDE_DOWN:
animation = new TranslateAnimation(0, 0, -
((child.getMeasuredHeight()/2) *
(index + 1)), 0);
animation.setInterpolator(new
DecelerateInterpolator());
animation.setFillAfter(true);
animation.setDuration(1000);
child.post(new
AnimationRunnable(animation,child));
//child.startAnimation(animation);
break;
case SLIDE_DOWN_MORE:
animation = new TranslateAnimation(0, 0, -
(child.getMeasuredHeight() *
(index + 25)), 0);
animation.setInterpolator(new
DecelerateInterpolator());
animation.setFillAfter(true);
animation.setDuration(1000);
child.post(new
AnimationRunnable(animation,child));
//child.startAnimation(animation);
break;
case ROTATE_CLOCKWISE:
animation = new RotateAnimation(0, 360,
child.getMeasuredWidth() / 2,
child.getMeasuredHeight() / 2);
animation.setInterpolator(new
DecelerateInterpolator());
animation.setFillAfter(true);
animation.setDuration(1000);
child.post(new
AnimationRunnable(animation,child));
//child.startAnimation(animation);
break;
case ROTATE_ANTI_CLOCKWISE:
animation = new RotateAnimation(0, -360,
child.getMeasuredWidth() / 2,
child.getMeasuredHeight() / 2);
animation.setInterpolator(new
DecelerateInterpolator());
animation.setFillAfter(true);
animation.setDuration(1000);
child.post(new
AnimationRunnable(animation,child));
//child.startAnimation(animation);
break;
case ZOOMIN_AND_ROTATE_CLOCKWISE:
AnimationSet animationSet = new
AnimationSet(true);
animationSet.setInterpolator(new
DecelerateInterpolator());
animation = new ScaleAnimation(0, 1, 0, 1,
child.getMeasuredWidth() / 2,
child.getMeasuredHeight() / 2);
animation.setStartOffset(0);
animation.setFillAfter(true);
animation.setDuration(1000);
animationSet.addAnimation(animation);
animation = new RotateAnimation(0, 360,
child.getMeasuredWidth() / 2,
child.getMeasuredHeight() / 2);
animation.setStartOffset(0);
animation.setFillAfter(true);
animation.setDuration(1000);
animationSet.addAnimation(animation);
child.post(new
AnimationSetRunnable(animationSet,child));
//child.startAnimation(animationSet);
break;
}
}
}
}
}
private class AnimationRunnable implements Runnable{
private Animation animation;
private View child;
AnimationRunnable(Animation animation, View child) {
this.animation=animation;
this.child=child;
}
@Override
public void run() {
child.startAnimation(animation);
}
}
private class AnimationSetRunnable implements Runnable{
private AnimationSet animation;
private View child;
AnimationSetRunnable(AnimationSet animation, View child) {
this.animation=animation;
this.child=child;
}
@Override
public void run() {
child.startAnimation(animation);
}
}
}
现在,在完全编写完这个类之后,是时候在我们的项目中使用它了。我们可以将这个类名作为一个 XML 标签,并在recyclerview
的行项布局each_item.xml
中使用它:
<com.ashok.packt.wear_note_1.utils.AnimatedLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical">
</com.ashok.packt.wear_note_1.utils.AnimatedLinearLayout>
使用新的AnimatedLinearLayout
替换布局代码。我们需要在childviews
中传递标签以实现动画效果。以下代码详细解释了这一点:
<?xml version="1.0" encoding="utf-8"?>
<com.ashok.packt.wear_note_1.utils.AnimatedLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:gravity="center"
android:orientation="vertical">
<com.ashok.packt.wear_note_1.utils.AnimatedLinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="center"
android:orientation="horizontal">
<com.ashok.packt.wear_note_1.utils.LoraWearTextView
android:id="@+id/note"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:tag="ZoomInAndRotateClockWise" android:textColor="@color/white"
tools:text="note" />
</com.ashok.packt.wear_note_1.utils.AnimatedLinearLayout>
</com.ashok.packt.wear_note_1.utils.AnimatedLinearLayout>
这种布局将通过小型动画绘制所有视图并显示列表项。
ZoomInAndRotateClockWise
动画,尝试将字符串更改为Custom
类中给出的完全相同的字符串:
概述
在本章中,我们了解了暗色主题在穿戴设备上的重要性。我们使用字体资源更改了自定义的TextView
。我们已经了解了如何编写自定义布局并在其中定义了一些动画。在后续的项目中,我们将进一步探索 Wear 2.0 独家引入的设计指南和组件,如Wearable action drawer
和wearable navigation drawer
。