65.9K
CodeProject 正在变化。 阅读更多。
Home

SeaHawks Wearable: 第 12 人支持

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.54/5 (3投票s)

2015年10月17日

MIT

9分钟阅读

viewsIcon

20331

适用于 Android 可穿戴设备的 Seahawk-12 手表。

引言

这是我连续第二篇关于 Android 可穿戴设备应用学习的文章。这次,我将为我最喜欢的球队西雅图海鹰队(NFL 球队)制作一个 Android Wear 模拟手表。我是我球队的忠实支持者和第12人。我们在2014年获胜,2015年获得亚军。我想,如果我做一个非常简单的模拟手表来纪念我的球队,当然也为了像我一样的所有第12人,那将是非常有趣的。:D

背景

再次,我使用了 Xamarin 平台,正如我之前提到的。它比使用 Android Studio 或 Eclipse 进行传统的 Android 开发有许多优势。作为一名 .NET 开发者,可以说我对 C# 相当着迷。目前有很多编程语法只有 C# 才能提供,而 Xamarin 平台的优点在于,你可以在移动开发中使用所有这些语法。如果您想了解更多关于 Xamarin 的信息,请点击这里

为什么选择 Xamarin?

这可能是一个很好的问题,为什么选择 Xamarin,如果我们已经有了 Android Studio 或 Eclipse?我将根据我的个人经验给您一个答案。首先,我非常了解 Eclipse,在我开始 Android 开发时,它对我来说非常熟悉,但后来我主要在 Visual Studio 和 C# 上花费时间。这是我的主要语言,可以说我对 C# 语言非常着迷。

由于我对 C# 已经很熟悉,起初我只能开发 Windows 移动应用程序,但直到我开始为最流行的移动平台 Android 和 iOS 制作原生应用程序,我才算得上一个完整的移动开发者。对于 Android,由于 Java 的存在,开发起来很容易,但 Objective-C 对我来说从头开始学习真的非常头疼。

因此,所有的困境都由 Xamarin - Mono 框架解决了。

您不仅可以用 C# 制作原生应用程序,还可以为所有三种设备重用大量组件,这真的很棒。有些人可能会考虑 Mono 的性能问题,但我到目前为止还没有遇到过这样的问题。最近,我使用了 Xamarin Forms,它在未来确实具有巨大的潜力。总之,我将 Xamarin 的优缺点总结如下:

优点

  1. 实时代码更新和应用程序的当天渲染
  2. C# - 伟大的语言
  3. Xamarin Forms – 我喜欢 Xamarin 的这项功能,因为您可以为所有三个主要的移动平台创建跨平台 UI。开发与 WPF 类似。它为制作同一应用程序的多个平台节省了很多时间。
  4. 内置的高级 C# 语法支持
  5. 跨平台 – 您可以在可移植项目中使用业务逻辑的核心,它将共享到所有三个平台 - iOS、Android 和 Windows 移动版。

缺点 - 对我来说,唯一的缺点是它不是免费的。

表盘开发 -

让我们开始编写代码。打开 Visual Studio,在项目浏览器中创建一个可穿戴设备表盘项目,如下图所示:让我们一步步开始表盘开发。

注意 - 正如我在上一篇文章中已经提到的,Xamarin 中的 Android 开发与 Android Studio 或 Eclipse 中的原生 Android 开发完全相同,除了伟大的 C# 语法与 Java 的对比:)

  1. 您的项目结构看起来像这样

  2. 首先,您必须添加一个 XML 文件夹和一个名为 watch_face 的 XML 文件(对于表盘,我们必须添加此 XML 并在 AndroidManifest.xml 中配置)。我目前还不清楚它的具体用途。也许这是 Android 定义的标准。
    <?xml version="1.0" encoding="UTF-8"?>
    <wallpaper xmlns:android="http://schemas.android.com/apk/res/android" />
  3. 现在您必须为圆形和方形手表添加表盘预览图像。我附上了一张预览图,以防您是第12人,并且以不同的方式支持。Preview_analaog 图像用于 Android manifest 文件。当用户想要在 Wear 手表上设置表盘时,此图像将在设置选项中显示预览。您的表盘至少需要一张预览图,这一点很重要。

  4. 现在是配置 Android manifest 以支持手表服务的时候了,在 Xamarin 中,这非常简单,您可以在主类的属性中定义它。
      [Service(Label = "Xamarin Analog Watchface", 
      Permission = "android.permission.BIND_WALLPAPER")]
        [MetaData("android.service.wallpaper", 
        Resource = "@xml/watch_face")]
        [MetaData("com.google.android.wearable.watchface.preview", 
        Resource = "@drawable/preview_analog")]
        [IntentFilter(new[] { "android.service.wallpaper.WallpaperService" }, 
    	Categories = new[] { "com.google.android.wearable.watchface.category.WATCH_FACE" })]
        public class NFLWatch : CanvasWatchFaceService 
        {

    然而,在 Android Studio 或 Eclipse 中,您必须修改 AndroidManifest.xml 并为手表服务添加 service 元素的配置。让我简要描述一下 manifest 中的这些设置。在 Xamarin 中,您可以在 MainActivity 中定义 manifest 属性,这很容易做到。

    正如您所见,Service 标签用于为您的表盘命名。您可以在此处配置您的项目表盘名称。

    接下来,手表服务需要一些权限,例如它需要 android.permission.BIND_WALLPAPER

    IntentFilter 让系统知道您的服务是为了显示手表。在步骤2中,Preview image 用于在 manifest 文件中定义,这是一个非常重要的定义属性。

  5. 现在创建一个手表服务,WatchFaceService 是 Android 手表应用的基础类,可以对其进行扩展以创建自定义表盘。

    因此,基本上,有两种方法可以扩展手表服务。

    1. CanvasWatchFaceService:您可以使用 Canvas 创建自定义视图。这类似于 Android 应用的 Canvas 应用开发。您可以创建多个形状和画笔等。
    2. Gles2WatchFaceService:如果您是 OpenGL 专家,并且更倾向于创建 2D 或 3D UI,那么您可以使用此服务来创建手表。

    为了简单起见,我们将只关注 CanvasWatchService。如果您想了解更多关于手表服务的信息,可以点击这里

    回到项目。

    首先,删除 MainActivity 中的所有代码,并将其重命名为 NFLWatch,它继承自 CanvasWatchFaceService

       [Service(Label = "Xamarin Analog Watchface", 
       Permission = "android.permission.BIND_WALLPAPER")]
        [MetaData("android.service.wallpaper", 
        Resource = "@xml/watch_face")]
        [MetaData("com.google.android.wearable.watchface.preview", 
        Resource = "@drawable/preview_analog")]
        [IntentFilter(new[] { "android.service.wallpaper.WallpaperService" }, 
    	Categories = new[] { "com.google.android.wearable.watchface.category.WATCH_FACE" })]
        public class NFLWatch : CanvasWatchFaceService 
        {
    
            const string Tag = "AnalogNFLWatchFaceService";
    
            /**
            * Update rate in milliseconds for interactive mode. We update once a second to advance the
            * second hand.
            */
            static long InteractiveUpdateRateMs = TimeUnit.Seconds.ToMillis(1);
            public override Android.Service.Wallpaper.WallpaperService.Engine OnCreateEngine()
            {
                return new NFLWatchEngine(this);
            }

    在上面的代码中,我像下面这样使用了一个常量 Tag 来在 Xamarin IDE 中进行日志记录

    Log.Debug (Tag, "update time");

    这里,我特别提到了这个常量,因为如果我真的想使用超过23个字符的长字符串,我会遇到运行时错误。我必须弄清楚这个限制,但我必须说,为了弄清楚 Xamarin 中的这个问题,我花了大量时间。所以,为了避免这个问题,请保持您的字符串简短。

  6. 现在您必须添加另一个子类,它继承自 CanvasWatchFaceService.Engine。这是实际实现表盘的类,它在 Canvas 上进行绘制。您必须实现 onCreateEngine() 来返回您的具体 Engine 实现。
     private class NFLWatchEngine:CanvasWatchFaceService.Engine
            {
                const int MsgUpdateTime = 0;
                // base class object
                CanvasWatchFaceService NFLWatchService;
    
                //Initialize Analogue niddles 
                Paint hourPaint;
                Paint minutePaint;
                Paint secondPaint;
                Paint tickPaint;
                bool mute;
                Time time;
    
                //used for get currenttime
                Timer timerSeconds;
                TimeZoneReceiver timeZoneReceiver;
                bool registeredTimezoneReceiver = false;
    
                // Whether the display supports fewer bits for each color in ambient mode. 
                // When true, we disable anti-aliasing in ambient mode.
                bool lowBitAmbient;
    
                Bitmap backgroundBitmap;
                Bitmap backgroundScaledBitmap;
    
                public NFLWatchEngine(CanvasWatchFaceService NFLWatchService)
                    : base(NFLWatchService)
                {
                    this.NFLWatchService = NFLWatchService;
                }

    在上面的代码中,Timer 对象会跟踪手表引擎启动时的当前时间。lowBitAmbient 值支持在 Ambient 模式下的颜色,如果为 true,我们将显示抗锯齿。

  7. 接下来初始化您的手表引擎,如下所示:
        public override void OnCreate (ISurfaceHolder surfaceHolder)
                {
                    this.SetWatchFaceStyle (new WatchFaceStyle.Builder (this.NFLWatchService)
                        .SetCardPeekMode (WatchFaceStyle.PeekModeShort)
                        .SetBackgroundVisibility (WatchFaceStyle.BackgroundVisibilityInterruptive)
                        .SetShowSystemUiTime (false)
                        .Build ()
                    ); 

    您必须设置 watchfacestyle,以便系统在表盘处于活动状态时如何交互。此外,您还可以定义通知的 peek 模式,以防您的表盘在后台处于活动状态。NFLWatchService 用于定义您的表盘背景,我们在上面的步骤中已经讨论过。

  8. 更重要的是,您必须重写 draw 方法,您可以在其中在 canvas 中创建任何类型的 watch 布局。如果您之前在 Android 应用中绘制过形状,那么这会很容易理解。
      //Actual Draw of analogue niddle in Canvas which will show in watch surface
                public override 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.Width != width
                        || backgroundScaledBitmap.Height != height) {
                        backgroundScaledBitmap = Bitmap.CreateScaledBitmap (backgroundBitmap,
                            width, height, true /* filter */);
                    }
                    canvas.DrawColor (Color.Black);
                    canvas.DrawBitmap (backgroundScaledBitmap, 0, 0, null);
    
                    float centerX = width / 2.0f;
                    float centerY = height / 2.0f;
    
                    // Draw the ticks.
                    float innerTickRadius = centerX - 10;
                    float outerTickRadius = centerX;
                    for (int tickIndex = 0; tickIndex < 12; tickIndex++) {
                        float tickRot = (float)(tickIndex * Math.PI * 2 / 12);
                        float innerX = (float)Math.Sin (tickRot) * innerTickRadius;
                        float innerY = (float)-Math.Cos (tickRot) * innerTickRadius;
                        float outerX = (float)Math.Sin (tickRot) * outerTickRadius;
                        float outerY = (float)-Math.Cos (tickRot) * outerTickRadius;
                        canvas.DrawLine (centerX + innerX, centerY + innerY,
                            centerX + outerX, centerY + outerY, tickPaint);
                    }
    
                    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 secLength = centerX - 20;
                    float minLength = centerX - 40;
                    float hrLength = centerX - 80;
    
                    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);
                    }
    
                    float minX = (float)Math.Sin (minRot) * minLength;
                    float minY = (float)-Math.Cos (minRot) * minLength;
                    canvas.DrawLine (centerX, centerY, centerX + minX, centerY + minY, minutePaint);
    
                    float hrX = (float)Math.Sin (hrRot) * hrLength;
                    float hrY = (float)-Math.Cos (hrRot) * hrLength;
                    canvas.DrawLine (centerX, centerY, centerX + hrX, centerY + hrY, hourPaint);
                }

    在上面的代码中,我使用了标准的模拟图形实现。您可以根据自己的需求进行自定义。您可以在 canvas 上绘制任何内容,从简单的 UI 到高级的。

  9. 此步骤是配置设备状态的一个非常重要的步骤。这意味着当用户切换表盘时,手表将如何表现。您必须在 engine 类中重写一些方法来处理表盘。
        public override void OnVisibilityChanged (bool visible)
                {
                    base.OnVisibilityChanged (visible);
                    if (Log.IsLoggable (Tag, LogPriority.Debug)) {
                        Log.Debug (Tag, "OnVisibilityChanged: " + visible);
                    }
                    if (visible) {
                        RegisterTimezoneReceiver ();
                        time.Clear (Java.Util.TimeZone.Default.ID);
                        time.SetToNow ();
                    } else {
                        UnregisterTimezoneReceiver ();
                    }
    
                    UpdateTimer ();
                }

    正如方法中清楚显示的那样,当表盘可见时,它会调用 RegisterTimezoneReceiver 方法,该方法基本上用于检查 broadcastReceiver 是否已注册。在 Android manifest 文件中,IntentFilter 用于获取手表服务的时区,例如:

    (intent.GetStringExtra ("time-zone")

    如果表盘不可见,此方法将检查 BroadcastReceiver 是否可以取消注册。一旦 BroadcastReceiver 已处理,就会调用 updateTimer 来触发表盘的无效化并重新绘制表盘。updateTimer 会停止任何待处理的 Handler 操作,并检查是否应发送另一个。

    private void UpdateTimer ()
                {
                    if (Log.IsLoggable (Tag, LogPriority.Debug)) {
                        Log.Debug (Tag, "update time");
                    }
    
                    if (timerSeconds == null) {
                        timerSeconds = new Timer ((state) => {
                            Invalidate ();
                        }, null, 
                            TimeSpan.FromMilliseconds (InteractiveUpdateRateMs), 
                            TimeSpan.FromMilliseconds (InteractiveUpdateRateMs));
                    } else {
                        if (ShouldTimerBeRunning ()) {
                            timerSeconds.Change (0, InteractiveUpdateRateMs);
                        } else {
                            timerSeconds.Change (Timeout.Infinite, 0);
                        }
                    }
                }
  10. 我还添加了环境模式,以防 watch 处于非活动状态。它将简单地禁用所有动画以节省电池寿命,一旦唤醒,它将从当前时间重新开始。
                public override void OnAmbientModeChanged (bool inAmbientMode)
                {
                    base.OnAmbientModeChanged (inAmbientMode);
                    if (Log.IsLoggable (Tag, LogPriority.Debug)) {
                        Log.Debug (Tag, "OnAmbientMode");
                    }
                    if (lowBitAmbient) {
                        bool antiAlias = !inAmbientMode;
                        hourPaint.AntiAlias = antiAlias;
                        minutePaint.AntiAlias = antiAlias;
                        secondPaint.AntiAlias = antiAlias;
                        tickPaint.AntiAlias = antiAlias;
                    }
                    Invalidate ();
    
                    UpdateTimer ();
                }
  11. 在完成本文之前,提供有关广播接收器的信息,该接收器处理用户可能正在旅行并更改时区的情况。此接收器将简单地清除保存的时区并重置显示时间。
    public class TimeZoneReceiver: BroadcastReceiver
            {
                public Action<Intent> Receive { get; set; }
    
                public override void OnReceive (Context context, Intent intent)
                {
                    if (Receive != null) {
                        Receive (intent);
                    }
                }
            }

输出

如果您在模拟器中运行附带的代码,它应该会像这样运行:

关注点

  1. 尽管这是一个非常基础的创建手表服务应用程序,但通过为您的球队制作一个手表来支持他们是一件很有趣的事情。您可以制作自己的球队支持手表。我已指明您需要更改 UI 的位置。
  2. 为了简单起见,我没有提供任何设置,但如果用户可以选择多个体育团队进行支持,这将是一个很棒的功能。我将撰写一篇关于可穿戴设备配置设置的文章,这将是另一个有趣和学习的过程。
  3. 您也可以在实现 draw 方法的区域自定义背景颜色。这些 UI 更改的自定义完全取决于您。
  4. 诸如位图缩放和加载、动画、抗锯齿以及持续信息更新等昂贵操作必须非常小心。如果它们可以提前准备好,尽量只执行一次或在非常需要时执行。当某些东西在非交互模式下是不必要时,请确保将其停止或置于休眠模式。Google 的 Android Wear 开发者页面有一些建议,您应该仔细研究并作为指南。
  5. Go Hawks。请喜欢并评价此应用程序,评论和建议随时欢迎。

结论

表盘是最受欢迎也是最重要的用户功能。当然,对于智能手表来说,仅显示当前时间只是非常基本的功能。表盘上还可以显示各种信息或数据。在下一个教程中,我们可以学习如何以交互方式为用户提供一些配置选项,以便他们可以根据自己的喜好设置手表上要显示的内容。作为开发人员,我们还需要了解如何以相当有效的方式检索数据,以便整体性能和电池寿命仅受到最小影响。

历史

  • 版本 1.0
© . All rights reserved.