Android-Studio-学习手册-五-

68 阅读35分钟

Android Studio 学习手册(五)

原文:Learn Android Studio

协议:CC BY-NC-SA 4.0

十五、Android Wear Lab

Android Wear 是谷歌的最新技术创新之一,为更亲密的用户体验创造了机会。在撰写本文时,只有少数设备支持 Android Wear,但这个列表正在增长。目前只支持手表,但随着技术的成熟,可穿戴设备可以包括从项链到实际衣服的任何东西。这些设备中有来自三大制造商的手表:三星、摩托罗拉和索尼。在本章中,您将学习如何构建一个可穿戴的应用,它可以从 Android Studio 以有线和无线方式部署和运行。

Note

我们邀请您使用 Git 克隆这个项目,以便跟进,尽管您将从头开始使用它自己的 Git 库重新创建这个项目。如果您的计算机上没有安装 Git,请参见第七章。在 Windows 中打开一个 Git-bash 会话(或者在 Mac 或 Linux 中打开一个终端)并导航到 C:\androidBook\reference(如果没有参考目录,请创建一个。在 Mac 上,导航到/your-labs-parent-dir/reference/)并发出以下 git 命令:git clone https://bitbucket.org/csgerber/megadroid MegaDroid。

设置您的可穿戴环境

在你开始开发可穿戴应用之前,你需要采取一些步骤来准备你的工作环境。虽然可以只使用模拟器进行开发,但最好手边有一个实际的穿戴设备。确保您的设备运行的是最新版本的操作系统,如果您使用的是 Windows PC,请下载并安装所有必要的驱动程序。连接您的可穿戴设备,并在 Android DDMS 工具窗口的设备列表中查找它。如果出现,请跳过下一部分。

安装设备驱动程序

在 Windows 上,如果您计划通过 USB 部署应用,可能需要为某些设备安装驱动程序。请注意,只有在连接设备时设备未被识别时,才安装驱动程序。如果您计划使用蓝牙部署应用,可以跳过这一部分。第一次连接设备时,Windows 将尝试自动安装驱动程序,但会失败。打开 Windows 设备管理器,在列表中的“其他设备”下找到您的设备。图 15-1 说明了你所看到的。

A978-1-4302-6602-0_15_Fig1_HTML.jpg

图 15-1。

The Samsung Gear Live as listed in the Device Manager without drivers

右键单击已卸载的设备,然后从上下文菜单中单击更新驱动程序软件。在弹出窗口中选择浏览我的电脑中的驱动软件,如图 15-2 所示。

A978-1-4302-6602-0_15_Fig2_HTML.jpg

图 15-2。

Browse your computer for the driver

点击“让我从我电脑的设备驱动列表中选择”,如图 15-3 所示。

A978-1-4302-6602-0_15_Fig3_HTML.jpg

图 15-3。

Click the “Let me pick from a list” option

点击 Android 设备,如图 15-4 所示。

A978-1-4302-6602-0_15_Fig4_HTML.jpg

图 15-4。

Select Android Device

从下一个弹出窗口中选择复合驱动程序,然后单击下一步。您的驱动程序的供应商会有所不同,可能与图 15-5 中的驱动程序不匹配。您可以安全地使用任何供应商提供的复合驱动程序。驱动程序将会安装,您将一切就绪。

A978-1-4302-6602-0_15_Fig5_HTML.jpg

图 15-5。

Select the Composite ADB Interface from any vendor

设置您的 SDK 工具

在开始开发之前,下载并安装 SDK 平台 5.0.1 或更高版本,并将 SDK 工具更新到 24.0.2 或更高版本。Android Wear 支持从平台版本 4.4W.2 和 SDK tools 23.0 开始提供。然而,本章中使用的示例使用了在更高版本的 SDK 平台中发现的特性。您还需要在 Extras 下安装更新的 SDK 平台和 Google 存储库的示例。

设置穿戴虚拟设备

使用工具栏按钮或通过单击工具➤安卓➤ AVD 管理器启动 AVD 管理器,然后单击创建虚拟设备。在左侧的类别窗格中选择 Wear,在可用硬件配置文件列表中选择 Android Wear Square 或 Android Wear Round,如图 15-6 所示。选择 5.0.1 或更高的 API 级别,因为本章中的示例需要 API 5.0.1。根据开发计算机的功能,您可能希望选择一个 x86 系统映像。这些映像使用较少的 CPU 周期,通常运行速度更快,因为它们不必模拟 CPU。但是,这些系统映像需要安装 HAXM,这是英特尔开发的硬件加速执行管理器。HAXM 是从 SDK 管理器安装的。HAXM 依赖于英特尔虚拟化技术(VT)支持,而该技术在某些机器上可能不可用。单击“下一步”按钮,并在向导的最后一页保留默认值。确保选择“使用主机 GPU”以获得最佳速度。

A978-1-4302-6602-0_15_Fig6_HTML.jpg

图 15-6。

Select the Wear category

选择最新的 API 级别(撰写本文时是 Lollipop)。然后点击下一步,如图 15-7 所示。

A978-1-4302-6602-0_15_Fig7_HTML.jpg

图 15-7。

Select Lollipop for the system image

在下一个屏幕中给你的 AVD 命名为 Android Wear Square API 21,如图 15-8 所示。

A978-1-4302-6602-0_15_Fig8_HTML.jpg

图 15-8。

Give your AVD the name Android Wear Square API 21

单击 Finish 构建 AVD。一旦建立,你可以点击列表中 Wear AVD 旁边的下拉箭头,复制它,如图 15-9 所示。如果您创建了方形 AVD,请将您的副本更改为使用圆形。否则,将其更改为使用方形外形。您希望使用这两种类型的外形来测试您的应用是否能在尽可能多的变化中正常工作。

A978-1-4302-6602-0_15_Fig9_HTML.jpg

图 15-9。

Duplicate your AVD to create Android Wear Round API 21

设置您的 Android Wear 硬件

如果你有一个可穿戴设备,你需要设置它以允许开发。可穿戴应用是通过 Android 智能手机或平板电脑部署和管理的,因此您将需要其中一个来实现可穿戴应用的开发。在 Android 智能手机上,在 Google Play 上安装 Android Wear 应用。启动应用,使用它将智能手机与可穿戴设备配对。

有两种方法可以将应用部署到可穿戴设备上:有线或蓝牙。有线是最简单的选择,但是如果你缺少 USB 端口或者你不想和设备驱动程序较劲,蓝牙是一个不错的选择。当您支持智能手机、平板电脑以及只有几个端口的电脑上的 Wear 时,可能会出现这种情况。

启用开发者模式

如果您从未启用开发人员模式,或者您的设备是全新的,请遵循以下步骤,您将能够设置许多选项,如始终开启模式、蓝牙调试、调试布局等:

Open the Settings app on your wearable device by pressing and holding the button on the side for 2 seconds.   Scroll to the bottom and tap the About option.   When the About screen opens, tap the build number seven times. Afterward, you will find the Developer option under the About option in the Settings list.   Open the developer options and enable ADB debugging.  

使用蓝牙调试

如果您希望无线工作,请在开发者选项屏幕中启用蓝牙调试。接下来,打开命令终端并运行以下两个 ADB 命令:

adb forward tcp:4444 localabstract:/adb-hub

adb connect localhost:4444

观察移动设备上运行的 Android Wear 应用的状态。它应该更改为以下内容:

Host: connected

Target: connected

此时,您的可穿戴设备就可以安装应用了。

创建巨型机器人项目

这一节演示了如何基于一个名为 MegaDroid 的虚拟视频游戏角色创建一个定制的 watch-face 项目。你可以把 MegaDroid 想象成两个流行的 80 年代电子游戏角色的混搭物(这个名字将保持不变)。表盘将体现一个用双剑与敌人战斗的太空战士的形象。该应用将作为一个额外的实际游戏部署。图 15-10 展示了练习的最终结果。你可以用这个例子作为秘诀,让你的品牌深入你的目标受众的手腕。对自定义手表表面的支持是 Lollipop 中引入的新特性。这一功能使您的应用能够作为设备的实际表面运行,并为新型用户体验创造了机会。您的应用可以显示各种来源的信息,包括但不限于互联网、GPS、配对移动设备的日历或联系人列表等。由于 watch face 是一个完整的、持续运行的 Android 应用,因此它可以作为您的应用的整体扩展。这个示例只涵盖了绘制用户界面和从运行时接收更新以推进时间的基础知识。

A978-1-4302-6602-0_15_Fig10_HTML.jpg

图 15-10。

The final result of the MegaDroid watch face

使用第一章中描述的新建项目向导开始您的新 Android Wear 项目。在向导的第二页,选中磨损复选框,选择 SDK 5.0 或更高版本,如图 15-11 所示。保留其余屏幕的默认值,为移动应用选择一个空白活动,为磨损组件选择一个空白磨损活动。完成最后一页上的向导,等待项目生成。点击运行按钮,在可穿戴设备或 AVD 上测试您的项目。

A978-1-4302-6602-0_15_Fig11_HTML.jpg

图 15-11。

Select Wear in the New Project Wizard

为穿戴设备设计应用时,掌握 Android 布局和设计至关重要。对于一个最佳的产品来说,你最初开发的大部分时间最好花在你选择的图形编辑器上,主要关注设计方法、尺寸、颜色等等。每个表盘都是独一无二的,你的方法会根据你想要完成的目标而有所不同。设计一个简单的数字钟面需要不同的方法,比设计一个模拟的花费更少。Android 网站上的在线开发者文档可能会让一些没有太多设计经验的人感到有些害怕。一般来说,该网站建议您应该为方形和圆形模型进行设计,决定如何或是否集成附加数据,允许系统 UI 元素保持可见,并支持不同的显示模式。这些显示模式将在后面的章节中解释。您还可以考虑提供一个配置屏幕。这个例子试图简化这个过程,并有意忽略其中的一些考虑因素。

针对屏幕技术进行优化

Wear 运行时将在两种显示模式下执行您的应用:环境模式和交互模式。观看或使用应用时,手表会在这些模式之间切换。环境模式由系统自动启用,以节省电池寿命。因此,您的 Wear 应用应该会检测到这种模式,并通过将其显示输出更改为使用暗淡的颜色来做出相应的响应。在这种模式下,更新每分钟发送一次,因此降低屏幕绘制的次数也是有意义的。此示例将在此模式下移除秒针,并将绘制速度从每秒更改为每分钟一次。

某些设备支持低位环境模式。在这种模式下,设备屏幕退回到有限的调色板。这有助于减少电池使用并防止屏幕老化。您可以检测此模式,并将图形调整为仅使用黑色、白色、蓝色、红色、洋红色、绿色、青色和黄色。使用你的画的轮廓而不是整个图像也是好的。在低位模式下,你的背景应该以黑色为主。非黑色像素不应超过总像素的 10 %,而彩色像素不应超过屏幕的 5%。这适用于支持这种特殊绘图模式的设备。在此模式下绘图时,还应该禁用抗锯齿功能。抗锯齿是一种模糊绘图边缘的技术,使它们看起来不那么像素化,如图 15-12 和 15-13 所示。

A978-1-4302-6602-0_15_Fig13_HTML.gif

图 15-13。

An anti-aliased image

A978-1-4302-6602-0_15_Fig12_HTML.gif

图 15-12。

An image without anti-aliasing

在我们的例子中,为了简单起见,我们将在环境模式下使用灰度版本的图形,如图 15-14 所示。将参考克隆中的所有图像复制到当前项目中。在 windows 上,导航到 C:\ Android book \ reference \ mega droid \ wear \ src \ main 文件夹,右键单击,然后复制 res 目录。导航到 C:\ Android book \ mega droid \ wear \ src \ main \ res 文件夹,然后右键单击并将复制的文件夹粘贴到现有的 RES 文件夹上。在 Mac 或 Linux 上,从终端运行以下命令:

A978-1-4302-6602-0_15_Fig14_HTML.jpg

图 15-14。

Grayscale artwork

CP-r≤Android book/reference/mega droid/wear/src/main/RES≤Android book/mega droid/wear/src/main/

构建 WatchFace 服务

一个表盘服务负责创建一个WatchFaceService.Engine,它是表盘的核心。WatchFaceService.Engine响应系统回调,负责更新时间和绘制人脸。创建一个新的MegaDroidWatchFaceService类来扩展CanvasWatchFaceService类。用清单 15-1 中的代码填充。

Listing 15-1. The MegaDroidWatchFaceService Class

public class MegaDroidWatchFaceService extends CanvasWatchFaceService {

private static final String TAG = "MegaDroidWatchSvc";

@Override

public Engine onCreateEngine() {

// create and return the watch face engine

return new MegaDroidEngine(this);

}

/* implement service callback methods */

private class MegaDroidEngine extends CanvasWatchFaceService.Engine {

private final Service service;

public MegaDroidEngine(Service service) {

this.service = service;

}

/**

* initialize your watch face

*/

@Override

public void onCreate(SurfaceHolder holder) {

super.onCreate(holder);

}

/**

* called when system properties are changed

* use this to capture low-bit ambient.

*/

@ Override

public void onPropertiesChanged(Bundle properties) {

super.onPropertiesChanged(properties);

}

/**

* This is called by the runtime on every minute tick

*/

@Override

public void onTimeTick() {

super.onTimeTick();

}

/**

* Called when there's a switched in/out of ambient mode

*/

@Override

public void onAmbientModeChanged(boolean inAmbientMode) {

super.onAmbientModeChanged(inAmbientMode);

}

@Override

public void onDraw(Canvas canvas, Rect bounds) {

//Draw the watch face here

}

/**

* Called when the watch face becomes visible or invisible

*/

@Override

public void onVisibilityChanged(boolean visible) {

super.onVisibilityChanged(visible);

}

}

}

注册服务

将以下内容添加到 Android 清单中的结束标签application之前:

<service

android:name=".MegaDroidWatchFaceService"

android:label="@string/mega_droid_service_name"

android:allowEmbedded="true"

android:taskAffinity=""

android:permission="android.permission.BIND_WALLPAPER" >

<meta-data

android:name="android.service.wallpaper"

android:resource="@xml/watch_face" />

<meta-data

android:name="com.google.android.wearable.watchface.preview"

android:resource="@drawable/preview_analog" />

<meta-data

android:name="com.google.android.wearable.watchface.preview_circular"

android:resource="@drawable/preview_analog_circular" />

<intent-filter>

<action android:name="android.service.wallpaper.WallpaperService" />

<category

android:name=

"com.google.android.wearable.watchface.category.WATCH_FACE" />

</intent-filter>

</service>

按 F2 键浏览错误。按 Alt+Enter 可以显示建议来逐个修复错误。第一个建议将让您为新服务生成一个名称。这将是显示在您的设备上的手表面孔图片和姓名列表中的姓名。下一个建议是为壁纸元数据标签生成一个 XML 描述符。创建xml/watch_face.xml并用以下代码填充它:

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

<wallpaper xmlns:android="``http://schemas.android.com/apk/res/android

IntelliJ 建议不应该修复接下来的两个错误。这些是对可绘制资源的引用,这些资源将成为面选取器中您的手表面的预览。您将需要使用图形编辑器来创建它们。或者,你可以截取应用的截图,但这有点本末倒置的感觉。如果你不熟悉图像编辑器或图形设计程序,你可以在这里临时添加一些虚拟图像来让应用运行。然后你可以在你的应用看起来已经完成后返回,截取手表运行的截图,并使用这些截图。

初始化可绘制资源和样式

onCreate()方法中,添加以下逻辑来设置样式并初始化可绘制资源:

public void onCreate(SurfaceHolder holder) {

super.onCreate(holder);

setWatchFaceStyle(new WatchFaceStyle.Builder(service)

.setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)

.setStatusBarGravity(Gravity.RIGHT | Gravity.TOP)

.setHotwordIndicatorGravity(Gravity.LEFT | Gravity.TOP)

.setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)

.setShowSystemUiTime(false)

.build());

Resources resources = service.getResources();

Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg);

this.backgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();

this.character = ((BitmapDrawable) resources.getDrawable(

R.drawable.character_standing)).getBitmap();

this.logo = ((BitmapDrawable) resources.getDrawable(

R.drawable.megadroid_logo)).getBitmap();

this.minuteHand = ((BitmapDrawable) resources.getDrawable(

R.drawable.minute_hand)).getBitmap();

this.hourHand = ((BitmapDrawable) resources.getDrawable(

R.drawable.hour_hand)).getBitmap();

this.secondPaint = new Paint();

secondPaint.setARGB(255, 255, 0, 0);

secondPaint.setStrokeWidth(2.f);

secondPaint.setAntiAlias(true);

secondPaint.setStrokeCap(Paint.Cap.ROUND);

this.time = new Time();

}

管理观察更新

将以下两个静态字段添加到封闭的 MegaDroid 中:

private static final long INTERACTIVE_UPDATE_RATE_MS =

TimeUnit.SECONDS.toMillis(1);

private static final int MSG_UPDATE_TIME = 0;

现在定义一个更新处理程序,它将基于前面定义的INTERACTIVE_UPDATE_RATE_MS常量触发观察器更新:

/** Handler to update the time once a second in interactive mode. */

final Handler mUpdateTimeHandler = new Handler() {

@Override

public void handleMessage(Message message) {

switch (message.what) {

case MSG_UPDATE_TIME:

if (Log.isLoggable(TAG, Log.VERBOSE)) {

Log.v(TAG, "updating time");

}

invalidate();

if (shouldTimerBeRunning()) {

long timeMs = System.currentTimeMillis();

long delayMs = INTERACTIVE_UPDATE_RATE_MS

- (timeMs % INTERACTIVE_UPDATE_RATE_MS);

mUpdateTimeHandler.sendEmptyMessageDelayed(

MSG_UPDATE_TIME, delayMs);

}

break;

}

}

};

private boolean shouldTimerBeRunning() {

return isVisible() && !isInAmbientMode();

}

该处理程序将在最初被调用后继续根据更新间隔安排更新。它只是使显示无效,这触发了一个隐式的onDraw调用。然后,它检查表盘是否可见,并在重新安排未来更新之前处于环境模式。当服务被运行时垃圾收集时,实现如下的onDestroy方法来清理更新处理程序:

@Override

public void onDestroy() {

mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);

super.onDestroy();

}

实现onPropertiesChanged方法来跟踪lowBitAmbient模式。设置一个布尔成员变量来跟踪环境模式是否正在运行。这就是你决定何时降低拉伸速率的方法。在实现中使用以下代码:

public void onPropertiesChanged(Bundle properties) {

super.onPropertiesChanged(properties);

this.lowBitAmbient = properties.getBoolean(

PROPERTY_LOW_BIT_AMBIENT, false);

if (Log.isLoggable(TAG, Log.DEBUG)) {

Log.d(TAG, "onPropertiesChanged: low-bit ambient = " + lowBitAmbient);

}

}

您还需要定义lowBitAmbient成员字段:

private boolean lowBitAmbient;

onTimeTick方法中添加对 invalidate 的调用。这里您调用了invalidate,它触发了屏幕的重绘:

@Override

public void onTimeTick() {

super.onTimeTick();

if (Log.isLoggable(TAG, Log.DEBUG)) {

Log.d(TAG, "onTimeTick: ambient = " + isInAmbientMode());

}

invalidate();

}

现在实现onAmbientModeChanged回调。这里你在环境模式下使用黑白插图。作为一种优化,您还将关闭秒针的反走样绘制,这在前面已经解释过了。

public void onAmbientModeChanged(boolean inAmbientMode) {

super.onAmbientModeChanged(inAmbientMode);

if (Log.isLoggable(TAG, Log.DEBUG)) {

Log.d(TAG, "onAmbientModeChanged: " + inAmbientMode);

}

if(inAmbientMode) {

character = ((BitmapDrawable) service.getResources().getDrawable(

R.drawable.character_standing_greyscale)).getBitmap();

logo = ((BitmapDrawable) service.getResources().getDrawable(

R.drawable.megadroid_logo_bw)).getBitmap();

hourHand = ((BitmapDrawable) service.getResources().getDrawable(

R.drawable.hour_hand_bw)).getBitmap();

minuteHand = ((BitmapDrawable) service.getResources()

.getDrawable(R.drawable.minute_hand_bw)).getBitmap();

} else {

character = ((BitmapDrawable) service.getResources()

.getDrawable(R.drawable.character_standing)).getBitmap();

logo = ((BitmapDrawable) service.getResources()

.getDrawable(R.drawable.megadroid_logo)).getBitmap();

hourHand = ((BitmapDrawable) service.getResources()

.getDrawable(R.drawable.hour_hand)).getBitmap();

minuteHand = ((BitmapDrawable) service.getResources()

.getDrawable(R.drawable.minute_hand)).getBitmap();

}

if (lowBitAmbient) {

boolean antiAlias = !inAmbientMode;

secondPaint.setAntiAlias(antiAlias);

}

invalidate();

// Whether the timer should be running depends on whether

//we're in ambient mode (as well

// as whether we're visible), so we may need to start

//or stop the timer.

updateTimer();

}

这调用了updateTimer方法,您将在接下来定义它。updateTimer方法将向mUpdateTimeHandler发送空的更新消息。您希望仅在表盘可见且不处于环境模式时发送更新。使用以下代码片段作为您的实现:

private void updateTimer() {

if (Log.isLoggable(TAG, Log.DEBUG)) {

Log.d(TAG, "updateTimer");

}

mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);

if (shouldTimerBeRunning()) {

mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);

}

}

使用清单 15-2 定义一个广播接收器,以响应时区的变化。register 和 unregister 方法将用于使接收器能够听到时区更改事件。

Listing 15-2. The Time-Zone BroadcastReceiver

final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {

@Override

public void onReceive(Context context, Intent intent) {

time.clear(intent.getStringExtra("time-zone"));

time.setToNow();

}

};

boolean mRegisteredTimeZoneReceiver = false;

private void registerReceiver() {

if (mRegisteredTimeZoneReceiver) {

return;

}

mRegisteredTimeZoneReceiver = true;

IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);

service.registerReceiver(mTimeZoneReceiver, filter);

}

private void unregisterReceiver() {

if (!mRegisteredTimeZoneReceiver) {

return;

}

mRegisteredTimeZoneReceiver = false;

service.unregisterReceiver(mTimeZoneReceiver);

}

每当接收到消息时,接收器将更新时间。它用一个IntentFilter注册,这个IntentFilterACTION_TIMEZONE_CHANGED动作相关联。使用IntentFilter是一种将活动或BroadcastReceiver绑定到特定类型意图动作的编程方式。

现在定义onVisibilityChanged回调来注册接收者并启动更新处理程序:

@Override

public void onVisibilityChanged(boolean visible) {

super.onVisibilityChanged(visible);

if (Log.isLoggable(TAG, Log.DEBUG)) {

Log.d(TAG, "onVisibilityChanged: " + visible);

}

if (visible) {

registerReceiver();

// Update time zone in case it changed while we weren't visible.

time.clear(TimeZone.getDefault().getID());

time.setToNow();

} else {

unregisterReceiver();

}

// Whether the timer should be running depends on whether

//we're visible (as well as

// whether we're in ambient mode), so we may need to start

//or stop the timer.

updateTimer();

}

画脸

绘制表盘是你的大部分逻辑会发生的地方。对invalidate的调用最终导致对onDraw方法的调用。在这个方法中,您将集合用于渲染面部的各种图形和艺术品。每次更新都会根据经过的小时、分钟和秒来旋转和绘制表针。覆盖onDraw方法,使用清单 15-3 中的代码。

Listing 15-3. The Full onDraw Method Implementation

public void onDraw(Canvas canvas, Rect bounds) {

time.setToNow();

int width = bounds.width();

int height = bounds.height();

// Draw the background, scaled to fit.

if (backgroundScaledBitmap == null

|| backgroundScaledBitmap.getWidth() != width

|| backgroundScaledBitmap.getHeight() != height) {

backgroundScaledBitmap = Bitmap.createScaledBitmap(backgroundBitmap,

width, height, true /* filter */);

}

canvas.drawBitmap(backgroundScaledBitmap, 0, 0, null);

canvas.drawBitmap(character, (width- character.getWidth())/2,

((height- character.getHeight())/2)+ 20, null);

canvas.drawBitmap(logo, (width- logo.getWidth())/2,

(logo.getHeight()*2), null);

float secRot = time.second / 30f * (float) Math.PI;

int minutes = time.minute;

float minRot = minutes / 30f * (float) Math.PI;

float hrRot = ((time.hour + (minutes / 60f)) / 6f ) * (float) Math.PI;

// Find the center. Ignore the window insets so that, on round

//watches with a "chin", the watch face is centered on the

//entire screen, not just the usable portion.

float centerX = width / 2f;

float centerY = height / 2f;

Matrix matrix = new Matrix();

int minuteHandX = ((width - minuteHand.getWidth()) / 2)

- (minuteHand.getWidth() / 2);

int minuteHandY = (height - minuteHand.getHeight()) / 2;

matrix.setTranslate(minuteHandX-20, minuteHandY);

float degrees = minRot * (float) (180.0 / Math.PI);

matrix.postRotate(degrees+90, centerX,centerY);

canvas.drawBitmap(minuteHand, matrix, null);

matrix = new Matrix();

int rightArmX = ((width - hourHand.getWidth()) / 2)

+ (hourHand.getWidth() / 2);

int rightArmY = (height - hourHand.getHeight()) / 2;

matrix.setTranslate(rightArmX + 20, rightArmY);

degrees = hrRot * (float) (180.0 / Math.PI);

matrix.postRotate(degrees-90, centerX,centerY);

canvas.drawBitmap(hourHand, matrix, null);

float secLength = centerX - 20;

if (!isInAmbientMode()) {

float secX = (float) Math.sin(secRot) * secLength;

float secY = (float) -Math.cos(secRot) * secLength;

canvas.drawLine(centerX, centerY, centerX + secX,

centerY + secY, secondPaint);

}

}

一次看一部分代码将有助于您理解整个流程。首先捕获传递给方法的bounds对象的宽度和高度:

int width = bounds.width();

int height = bounds.height();

接下来,您检查背景的缩放版本,并将其绘制到屏幕上。背景需要缩放到给定的宽度和高度。您只需要缩放一次,因为每次重绘时边界都是相同的。

// Draw the background, scaled to fit.

if (backgroundScaledBitmap == null

|| backgroundScaledBitmap.getWidth() != width

|| backgroundScaledBitmap.getHeight() != height) {

backgroundScaledBitmap = Bitmap

.createScaledBitmap(backgroundBitmap,

width, height, true /* filter */);

}

canvas.drawBitmap(backgroundScaledBitmap, 0, 0, null);

现在你画一个字符,后面跟着一个标志。一个数学扭曲被用来水平居中字符,但是给他一个从垂直中心 20 像素的偏移量。要居中,取边界宽度和字符宽度之差,并将其一分为二。高度也是如此,但偏移增加了 20 个像素:

canvas.drawBitmap(character,(width- character.getWidth())/2,

((height- character.getHeight())/2)+ 20, null);

canvas.drawBitmap(logo, (width- logo.getWidth())/2,

(logo.getHeight()*2), null);

下一节使用一点几何学来寻找分针、时针和秒针的旋转角度。它使用一个公式将经过的秒数或分钟数除以 30π。对于小时,这是一个更复杂的问题。在加上经过分钟的微小偏移后,将小时除以 6。然后将结果乘以π。分钟偏移量是通过将经过的分钟分成 60 个小片段来计算的,因为每一分钟是一个小时的 1/60。偏移量是可选的。如果您希望时针直接与当前小时对齐而不逐渐前进,可以省略这部分计算:

float secRot = time.second / 30f * (float) Math.PI;

int minutes = time.minute;

float minRot = minutes / 30f * (float) Math.PI;

float hrRot = ((time.hour + (minutes / 60f)) / 6f ) * (float) Math.PI;

接下来,您找到屏幕的中心来定位时针、分针和秒针:

float centerX = width / 2f;

float centerY = height / 2f;

使用中心点,在绘制分针之前,围绕屏幕中心执行平移和旋转。所用角度的计算方法是将分钟乘以 180/π。示例中使用的图形直接指向 9 点钟方向,因此需要将旋转角度增加 90 度:

Matrix matrix = new Matrix();

int minuteHandX = ((width - minuteHand.getWidth()) / 2)

- (minuteHand.getWidth() / 2);

int minuteHandY = (height - minuteHand.getHeight()) / 2;

matrix.setTranslate(minuteHandX-20, minuteHandY);

float degrees = minRot * (float) (180.0 / Math.PI);

matrix.postRotate(degrees+90, centerX,centerY);

canvas.drawBitmap(minuteHand, matrix, null);

时针使用几乎相同的操作,但由于图形指向分针的对面,您必须从旋转角度减去 90 °:

matrix = new Matrix();

int rightArmX = ((width - hourHand.getWidth()) / 2) + (hourHand.getWidth() / 2);

int rightArmY = (height - hourHand.getHeight()) / 2;

matrix.setTranslate(rightArmX + 20, rightArmY);

degrees = hrRot * (float) (180.0 / Math.PI);

matrix.postRotate(degrees-90, centerX,centerY);

canvas.drawBitmap(hourHand, matrix, null);

最后,你画秒针,它只是一条从屏幕中心延伸出来的红线。你通过从中心减去 20 个像素来计算秒针的长度。您将绘制逻辑放在一个条件块中,当设备处于环境模式时,该条件块不会触发。终点 x 坐标由旋转角度的正弦值乘以长度确定。y 坐标是通过取旋转角度余弦的倒数并乘以长度来确定的。使用这些坐标,您在画布上调用drawLine方法,向其传递中心 X 和 Y,并将计算出的secXsecY添加到中心,以确定线条的终点。我们使用在onCreate方法中创建的 paint 对象将它们联系在一起:

float secLength = centerX - 20;

if (!isInAmbientMode()) {

float secX = (float) Math.sin(secRot) * secLength;

float secY = (float) -Math.cos(secRot) * secLength;

canvas.drawLine(centerX, centerY, centerX + secX,

centerY + secY, secondPaint);

}

现在构建并运行您的 watch 服务,并将其部署到设备上。图 15-15 显示了我们在 Galaxy Gear 实时手表上运行的示例。

A978-1-4302-6602-0_15_Fig15_HTML.jpg

图 15-15。

MegaDroid watch face running on the device

摘要

通过本练习,您学习了如何设计定制的表盘。您了解了如何通过 USB 和蓝牙部署可穿戴应用。您学习了如何响应环境模式来优化电池。您发现了设计一个表盘所涉及的各种组件,包括服务和引擎,以及控制重绘速率的自定义计时器。虽然这一章仅仅是作为一个介绍,但在可穿戴应用的世界中存在着一些机会。Watch faces 可以完全访问系统服务,并可以检索日历条目以进行自定义显示、地址簿联系人、电池寿命信息等。

十六、定制 Android Studio

Android Studio 所基于的 IntelliJ IDEA 已经发展了很多年。这种演变的一部分是随着每个软件版本而激增的许多定制特性。这些众多的可定制特性,加上数百个第三方插件,使得 IntelliJ,以及现在的 Android Studio,成为市场上最可定制、最灵活的 ide 之一。事实上,几乎所有你能想到的在 IDE 中定制的东西都很有可能在 Android Studio 中定制。Android Studio 中的可定制功能如此之多,以至于我们无法一一涵盖。在本书中,我们已经讨论了 Android Studio 的一些最重要的可定制功能,包括工具按钮和默认布局(第二章),以及实时模板、代码生成和代码样式(第三章)。

本章展示了那些我们认为对 Android 开发者最有用的可定制特性的平衡。为了利用 Android Studio 中的可定制功能,您应该熟悉设置键盘快捷命令(Ctrl+Alt+S | Cmd+逗号)。此操作也可从主菜单的“文件”“➤设置”中找到(如果您使用的是 Mac,请选择“文件”“➤偏好设置”)。键盘快捷键和菜单操作都会激活“设置”对话框。

在设置对话框中,你可以找到 Android Studio 中大多数可定制的特性,我们将在本章中向你展示如何导航它的许多标签和子面板。当我们讨论设置对话框的功能时,请参考图 16-1 。左窗格包含可定制功能的列表。该列表分为两部分:项目设置和 IDE 设置。您对前者中的项目所做的任何更改可能会应用于您当前的项目或所有项目,而您对后者所做的任何更改将应用于现在和将来的所有项目。“设置”对话框的左上角还包含一个过滤栏。当您在过滤栏中键入文本时,下面的列表将只显示与文本匹配的条目。

A978-1-4302-6602-0_16_Fig1_HTML.jpg

图 16-1。

The Settings dialog box showing Java ➤ Code Generation

代码风格

尽管第三章提到了代码风格,我们还是会在这里更详细地讨论这个重要的话题。通过按 Ctrl+Alt+S | Cmd+逗号激活设置对话框。在左窗格中切换打开代码样式目录,并选择 Java。然后在右窗格中选择代码生成选项卡。

如果您按照第三章中的步骤操作,您应该在字段和静态字段文本区域看到小写的 m 和 s。如果您没有看到这些字母,请现在将它们键入各自的字段中。假设您遵循 Android 中的命名约定,即命名您的类成员时以 m(代表成员—例如,mCurrencies)或 s(代表静态成员—例如,sName)为前缀,这些前缀允许 Android Studio 在您自动生成 getters、setters、constructors 和其他代码时生成有意义的方法名。我们强烈建议您遵循这个命名约定;因此,这个特殊的设置是您可以设置的最重要的设置之一。

选择位于代码生成页签左侧的排列页签,如图 16-3 所示。“排列”选项卡的作用是对源文件中的代码元素进行排序。Java 软件开发人员希望成员声明首先出现,然后是构造函数,然后是方法,最后是内部类,依此类推。“排列”选项卡允许您设置代码元素的顺序。作为一名软件开发人员,你应该维护一个整洁有序的代码库。然而,您不需要关心以正确的顺序插入代码元素,因为 Android Studio 会自动为您重新排列它们。若要应用排列设置,请键入 Ctrl+Alt+L | Cmd+Alt+L,并确保选中“重新排列条目”复选框。然后点击运行,如图 16-2 所示。生成的源文件元素将根据您在“排列”标签中选择的顺序重新排列。您也可以通过从主菜单中选择代码➤重排代码来执行相同的功能。

A978-1-4302-6602-0_16_Fig3_HTML.jpg

图 16-3。

Reformat Code dialog box with Rearrange Entries selected

A978-1-4302-6602-0_16_Fig2_HTML.jpg

图 16-2。

The Settings dialog box showing Java ➤ Arrangement

选择包装和支撑标签,如图 16-4 所示。此选项卡的目的是允许您设置代码包装的方式和时间。关于包装 Java 代码,没有一成不变的规则。我们更喜欢在代码块的第一个语句的末尾看到左花括号,而其他人更喜欢垂直对齐的左花括号和右花括号的对称性。如果你是喜欢对齐花括号的人,你可以在图 16-4 中突出显示的括号放置部分改变这个设置(以及许多其他设置),只需将设置从行尾改为下一行。当您更改设置时,请注意右边子面板上的示例代码是如何变化的,以反映这些设置。

A978-1-4302-6602-0_16_Fig4_HTML.jpg

图 16-4。

The Settings dialog box showing Java ➤ Wrapping and Braces

花点时间探索代码样式➤ Java 窗格的其他选项卡,并根据您自己的偏好定制代码样式。您不仅可以对 Java 应用代码样式更改,还可以对 HTML、Groovy 和 XML 的代码样式应用类似的更改。XML 设置对 Android 布局特别有用。对代码样式设置感到满意后,可以通过单击位于代码样式窗格顶部的管理按钮来保存它们。在出现的名为代码风格模式的对话框中,如图 16-5 所示,点击另存为按钮,给你的代码风格起一个名字,如 android1。如果将代码样式更改应用于默认方案,这些设置将成为所有项目的默认设置。如果您将代码样式的更改应用到项目方案中,那么只有当前项目会受到影响。如你所见,Android Studio 在配置代码风格时给了你很大的灵活性。

A978-1-4302-6602-0_16_Fig5_HTML.jpg

图 16-5。

The Code Style Schemes dialog box enables you to save your code styles

外观、颜色和字体

许多程序员喜欢反转 IDE 的颜色主题,使背景为深色。有证据表明反转(深色背景)的颜色主题可以减少眼睛疲劳,但是需要一段时间来适应反转的主题,特别是如果你已经在白色背景上写了一段时间的代码。Android Studio 附带了三个预打包的主题:IntelliJ、Darcula 和 Windows。IntelliJ 是默认的浅色背景主题;Darcula 是暗背景主题;而 Windows 是复古的 Windows 主题。你也可以从 ideacolorthemes.org 下载更多的主题。

通过调用 Ctrl+Alt+S | Cmd+逗号打开设置对话框。在过滤器栏中键入 appearance,并在列表中选择外观的第一个实例。将主题字段更改为 Darcula。然后点击应用和确定,如图 16-6 所示。Android Studio 将请求重启自己,您应该允许。一旦 Android Studio 重启,你的 IDE 应该类似于图 16-7 。

A978-1-4302-6602-0_16_Fig7_HTML.jpg

图 16-7。

The Android Studio IDE with the Darcula theme applied

A978-1-4302-6602-0_16_Fig6_HTML.jpg

图 16-6。

Settings dialog box with Appearance Theme set to Darcula

使用 Android Studio 自带的预打包主题是改变颜色主题最简单的方法,但是如果你倾向于进一步定制颜色和字体,你可以通过调整编辑器➤颜色和字体下的设置来实现,如图 16-8 所示。如果您想保存您的新配色方案,您可以通过单击另存为按钮并为您的配色方案命名来完成。

A978-1-4302-6602-0_16_Fig8_HTML.jpg

图 16-8。

Settings ➤ Colors and Fonts ➤ Java

键盘映射

如果您对任何键盘快捷键不确定,您可以通过导航到“帮助➤默认键盘映射参考”来参考快速参考。该命令在位于 JetBrains 服务器上的浏览器中打开一个 PDF 文件。

如果您想修改默认的键映射,您可以通过激活设置对话框(Ctrl+Alt+S | Cmd+逗号)然后在过滤栏中键入键映射来完成。按键映射条目按菜单组织,如图 16-9 所示。在开始自定义键映射之前,单击子面板顶部的复制按钮,将默认的所有键映射设置复制到一个新的键映射模式中,现在您可以随意命名该模式。双击任何条目都可以让您修改或添加任何您喜欢的键盘或鼠标快捷键,只要这些新命令不与任何现有命令冲突。如果您经常使用某些没有快捷键的命令,请为它们创建一个键盘或鼠标快捷键。

A978-1-4302-6602-0_16_Fig9_HTML.jpg

图 16-9。

From Settings ➤ Keymap, double-click entries to modify or create keyboard shortcuts

宏指令

当您考虑前面的“Keymap”一节中提供的现有命令时,您可能想知道是否有一种方法可以自定义命令本身,而不仅仅是激活该命令的键盘或鼠标快捷键。嗯,有——它叫做宏。宏让你有机会在 Android Studio 中记录任何事件或事件序列,并随意回放。

让我们创建一个发出 Monkey 命令的宏。通过单击 IDE 底部空白处的“终端工具”按钮,打开一个终端会话。导航到编辑➤宏➤开始宏记录。在终端会话中键入 ADB shell monkey–v 2000。沿着状态栏的右下角,您会看到一个绿色的消息框,上面写着“宏记录已开始”,如图 16-10 所示。单击左侧的红色停止按钮。在弹出的对话框中,输入 monkey,点击 OK,如图 16-11 所示。您现在可以通过导航到编辑➤宏➤猴子选择这个宏。您还可以按照上一节中的说明为该宏指定键盘或鼠标快捷键。

A978-1-4302-6602-0_16_Fig11_HTML.jpg

图 16-11。

Enter monkey as the macro name

A978-1-4302-6602-0_16_Fig10_HTML.jpg

图 16-10。

Macro recording status in the status bar

文件和代码模板

文件和代码模板对于创建经常使用的文件或代码块非常有用。每当您在 Android Studio 中创建新的活动时,都会使用一个文件模板来生成代码。在本节中,您将基于您在第九章中完成的代码创建您自己的名为 CurrencyLayout 的定制代码模板。

通过按 Ctrl+Alt+S | Cmd+逗号激活设置对话框。在过滤栏中键入文件和代码模板。单击模板选项卡,然后单击文件和代码模板子面板顶部的绿色加号箭头。将清单 9-1 (在第九章的中)中的代码复制并粘贴到右边的子面板中,或者如果你正在阅读这本书的印刷版,将清单 9-1 中的代码键入到右边的子面板中,如图 16-12 所示。在 Name 字段中将模板命名为 CurrencyLayout,并在 Extension 字段中键入 xml。Description 字段描述了如何在文件和代码模板中使用变量,尽管在这个简单的例子中我们不会使用任何变量。单击应用,然后单击确定。在项目工具窗口中,右击res/layout目录,选择【新➤当前布局】,如图 16-13 所示。在出现的对话框中,将文件命名为 activity_currency,然后单击 OK。Android Studio 将为您生成一个名为activity_currency.xml的新 XML 布局文件,其中包含您在文件模板定义中使用的代码。CurrencyLayout文件模板提供了一个很好的框架,从中我们可以创建全新的 XML 布局。您不局限于 XML 文件;您也可以轻松地为 Java 或 HTML 文件创建文件模板。

A978-1-4302-6602-0_16_Fig13_HTML.jpg

图 16-13。

Right-click the res/layout directory and choose New ➤ CurrencyLayout

A978-1-4302-6602-0_16_Fig12_HTML.jpg

图 16-12。

From Settings ➤ File and Code Templates, create the CurrencyLayout file template

菜单和工具栏

Android Studio 中很少有超出定制的内容。如果你不喜欢菜单和工具栏,你也可以修改它们。在本节中,您将向 Analyze 菜单添加 Monkey 命令。

通过按 Ctrl+Alt+S | Cmd+逗号激活设置对话框。在筛选器栏中键入菜单和工具栏,并在列表中选择菜单和工具栏项目。从主菜单中,选择分析➤分析操作以切换打开目录。选择分析模块依赖,点击添加后按钮,如图 16-14 所示。在出现的对话框中,导航到所有操作➤主菜单➤编辑➤宏➤猴子。如图 16-15 所示,点击确定退出该对话框,再次点击确定保存更改。要查看并激活您刚才所做的更改,请打开终端会话并导航至分析➤猴子。

A978-1-4302-6602-0_16_Fig15_HTML.jpg

图 16-15。

Select Monkey from the Macros options

A978-1-4302-6602-0_16_Fig14_HTML.jpg

图 16-14。

From Settings ➤ Menus and Toolbars, create the Monkey action item in the Analyze menu

外挂程式

许多第三方插件可用于 Android Studio。在本节中,您将安装 Bitbucket 插件,它是 Android Studio 数百个插件中的一个。要查看插件的完整列表,请将浏览器指向:plugins.jetbrains.com/?androidstudio

通过按 Ctrl+Alt+S | Cmd+逗号激活设置对话框。在过滤栏中键入插件,然后在下面的列表中选择插件项目。点击插件子面板底部的浏览存储库按钮,如图 16-16 所示。在弹出的对话框顶部的搜索栏中输入 bitbucket,如图 16-17 所示。点击安装按钮,Android Studio 将安装 Bitbucket 插件。点击关闭按钮,Android Studio 将请求重启,您应该允许。如果您从版本控制菜单浏览 VCS ➤结帐,以及 VCS ➤导入到版本控制菜单,您会注意到一些与 Bitbucket 相关的新菜单项。Bitbucket 插件有助于 Git 存储库的远程管理。参见第七章对 Git 的全面讨论。

A978-1-4302-6602-0_16_Fig17_HTML.jpg

图 16-17。

Search for the Bitbucket plug-in and install it

A978-1-4302-6602-0_16_Fig16_HTML.jpg

图 16-16。

From Settings ➤ Plugins, select Browse Repositories

摘要

本章讲述了设置对话框。您还回顾了代码风格,这在第三章中有介绍。您学习了如何保存新的代码样式方案,如何更改 Android Studio 的外观,以及调整现有的主题并保存该主题。您还学习了如何修改键映射并保存您自己的键映射模式。您创建了一个宏,然后将该宏插入到菜单栏中。您还使用了文件和代码模板,修改了菜单和工具栏,并最终学会了管理插件。

别忘了我们已经讨论过定制工具按钮(第二章)、默认布局(第二章)和动态模板(第三章)。特别是,动态模板是 Android Studio 中最重要的定制特性之一。在 Android Studio 中,比我们在本书中介绍的更多的定制是可能的,并且许多定制特性有助于自我发现。重新访问“设置”对话框(Ctrl+Alt+S | Cmd+逗号),并探索其中可用的许多自定义功能。