Skip to content

Instantly share code, notes, and snippets.

@Nia-TN1012
Last active December 3, 2016 14:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Nia-TN1012/1586ca830d0609f4d6f0706b077fe476 to your computer and use it in GitHub Desktop.
Save Nia-TN1012/1586ca830d0609f4d6f0706b077fe476 to your computer and use it in GitHub Desktop.
Xamarin+C#で作るWatch Faceのコードです。
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
#region Javaのimport構文とC#のusingディレクティブとの関係
/*
基本的に、
・Javaのimport構文のパッケージ名
・C#のusingディレクティブの名前空間
が相互に対応しています。
(但し、パッケージ名は全て小文字で、名前空間はパスカルケース(略称の場合はすべて大文字)です)
◆ Android.Content
・Java
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
・C#
using Android.Content;
◆ Android.Graphics
・Java
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
・C#
using Android.Graphics;
◆ Android.Graphics.Drawable
・Java
import android.graphics.drawable.BitmapDrawable;
・C#
using Android.Graphics.Drawable;
◆ Android.OS
・Java
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
・C#
using Android.OS;
◆ Android.Support.Wearable.Watchface
・Java
import android.support.wearable.watchface.CanvasWatchFaceService;
import android.support.wearable.watchface.WatchFaceStyle;
・C#
using Android.Support.Wearable.Watchface;
◆ Android.Support.V4.Content
・Java
import android.support.v4.content.ContextCompat;
・C#
using Android.Support.V4.Content;
◆ Android.Text.Format
・Java
import android.text.format.Time;
・C#
using Android.Text.Format;
◆ Android.View
・Java
import android.view.SurfaceHolder;
・C#
using Android.Views;
*/
#endregion
using Android.Content;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.OS;
using Android.Support.V4.Content;
using Android.Support.Wearable.Watchface;
using Android.Text.Format;
using Android.Views;
// Service属性、Metadata属性で使用します。
using Android.App;
// WallpaperServiceクラスで使用します。
using Android.Service.Wallpaper;
using Android.Util;
using Chronoir_net.Chronoface.Utility;
namespace WatchFace {
// ウォッチフェイスサービス要素の内容を構成します。
// Labelプロパティには、ウォッチフェイスの選択に表示する名前を設定します。
// 注:Nameプロパティについて :
// Xamarinでは、「.」から始まる AndroidのShortcut が利用できません。
//  なお、省略した場合、Nameには「md[GUID].クラス名」となります。
[Service( Label = "@string/my_watch_name", Permission = "android.permission.BIND_WALLPAPER" )]
// 壁紙にバインドするパーミッションを宣言します。
[MetaData( "android.service.wallpaper", Resource = "@xml/watch_face" )]
// プレビューに表示する画像を指定します。
// previewが四角形用、preview_circularが丸形用です。
[MetaData( "com.google.android.wearable.watchface.preview", Resource = "@drawable/preview" )]
[MetaData( "com.google.android.wearable.watchface.preview_circular", Resource = "@drawable/preview_circular" )]
// このWatch Faceで使用するIntentFilterを宣言します。
[IntentFilter( new[] { "android.service.wallpaper.WallpaperService" }, Categories = new[] { "com.google.android.wearable.watchface.category.WATCH_FACE" } )]
/// <summary>
/// アナログ時計のウォッチフェイスサービスを提供します。
/// </summary>
/// <remarks>
/// <see cref="CanvasWatchFaceService"/>クラスを継承して実装します。
/// </remarks>
public class MyWatchFaceService : CanvasWatchFaceService {
/// <summary>
/// ログ出力用のタグを表します。
/// </summary>
private const string logTag = nameof( MyWatchFaceService );
/// <summary>
/// インタラクティブモードにおける更新間隔(ミリ秒単位)を表します。
/// </summary>
/// <remarks>
/// アンビエントモードの時は、このフィールドの設定にかかわらず、1分間隔で更新されます。
/// </remarks>
/// <example>
/// // 更新間隔を1秒に設定する場合
/// // Java.Util.Concurrent.TimeUnitを使用する場合
/// Java.Util.Concurrent.TimeUnit.Seconds.ToMillis( 1 )
/// // System.TimeSpanを使用する場合( 戻り値はdouble型なので、long型にキャストします )
/// ( long )System.TimeSpan.FromSeconds( 1 ).TotalMilliseconds
/// // 注 : TimeSpanのMillisecondsは時間の内、ミリ秒の部分のみです。ミリ秒単位の合計を取得するには、TotalMillisecondsを使用します。
/// </example>
private static readonly long InteractiveUpdateRateMilliseconds = Java.Util.Concurrent.TimeUnit.Seconds.ToMillis( 1 );
/// <summary>
/// インタラクティブモードにて、定期的に時刻を更新するための、ハンドラー用のメッセージのIDを表します。
/// </summary>
private const int MessageUpdateTime = 0;
/// <summary>
/// <see cref="CanvasWatchFaceService.Engine"/>オブジェクトが作成される時に実行します。
/// </summary>
/// <returns><see cref="CanvasWatchFaceService.Engine"/>クラスを継承したオブジェクト</returns>
public override WallpaperService.Engine OnCreateEngine() {
// CanvasWatchFaceService.Engineクラスを継承したMyWatchFaceEngineのコンストラクターに、
// MyWatchFaceオブジェクトの参照を指定します。
return new MyWatchFaceEngine( this );
}
/// <summary>
/// アナログ時計のウォッチフェイスの<see cref="CanvasWatchFaceService.Engine"/>機能を提供します。
/// </summary>
/// <remarks>
/// <see cref="CanvasWatchFaceService.Engine"/>クラスを継承して実装します。※<see cref="CanvasWatchFaceService"/>は省略可能です。
/// </remarks>
private class MyWatchFaceEngine : Engine {
/// <summary>
/// <see cref="CanvasWatchFaceService"/>オブジェクトの参照を表します。
/// </summary>
private CanvasWatchFaceService owner;
#region タイマー系
/// <summary>
/// 時刻を更新した時の処理を表します。
/// </summary>
/// <remarks>
/// Android Studio( Java系 )側でいう、EngineHandlerの役割を持ちます。
/// </remarks>
private readonly Handler updateTimeHandler;
/// <summary>
/// 時刻を格納するオブジェクトを表します。
/// </summary>
// Time ( Android )
//private Time nowTime;
// Calendar ( Java )
private Java.Util.Calendar nowTime;
// DateTime ( C# )
//private DateTime nowTime;
#endregion
#region 現在時刻の取得に、どのライブラリを使用すればよいのですか?
/*
* 1. Android.Text.Format.Timeクラス
*   AndroidのAPIで用意されている日付・時刻のクラスです。
*   Timeオブジェクト.SetToNowメソッドで、現在のタイムゾーンに対する現在時刻にセットすることができます。
*   Timeオブジェクト.Clearメソッドで、指定したタイムゾーンのIDに対するタイムゾーンを設定します。
*   ※2032年までしか扱えない問題があるため、Android API Level 22以降では旧形式となっています。
*   
* 2. Java.Util.Calendarクラス
*   Javaで用意されている日付・時刻のクラスです。
*   Calendar.GetInstanceメソッドで、現在のタイムゾーンに対する現在時刻にセットすることができます。
*   Calendarオブジェクト.TimeZoneプロパティで、タイムゾーンを設定することができます。
*
* 3. System.DateTime構造体
*   .NET Frameworkで用意されている日付・時刻のクラスです。
*   DateTime.Nowで、現在のタイムゾーンに対する現在時刻を取得することができます。
*   ※タイムゾーンは、Android Wearデバイスとペアリングしているスマートフォンのタイムゾーンとなります。
*/
#endregion
#region グラフィックス系
/// <summary>
/// 背景用のペイントオブジェクトを表します。
/// </summary>
private Paint backgroundPaint;
/// <summary>
/// 時針用のオブジェクトを表します。
/// </summary>
private HourAnalogHandStroke hourHand;
/// <summary>
/// 分針用のオブジェクトを表します。
/// </summary>
private MinuteAnalogHandStroke minuteHand;
/// <summary>
/// 秒針用のオブジェクトを表します。
/// </summary>
private SecondAnalogHandStroke secondHand;
#endregion
#region モード系
/// <summary>
/// アンビエントモードであるかどうかを表します。
/// </summary>
private bool isAmbient;
/// <summary>
/// デバイスがLowBitアンビエントモードを必要としているかどうかを表します。
/// </summary>
/// <remarks>
/// <para>デバイスがLowBitアンビエントモードを使用する場合、アンビエントモードの時は、以下の2点の工夫が必要になります。</para>
/// <para>・使用できる色が8色( ブラック、ホワイト、ブルー、レッド、マゼンタ、グリーン、シアン、イエロー )のみとなります。</para>
/// <para>・アンチエイリアスが無効となります。</para>
/// </remarks>
private bool isRequiredLowBitAmbient;
/// <summary>
/// デバイスがBurn-in-protection(焼き付き防止)を必要としているかどうかを表します。
/// </summary>
/// <remarks>
/// <para> ディスプレイが有機ELなど、Burn-in-protectionが必要な場合、アンビエントモードの時は、以下の2点の工夫が必要になります。</para>
/// <para>・画像はなるべく輪郭のみにします。</para>
/// <para>・ディスプレイの端から数ピクセルには描画しないようにします。</para>
/// </remarks>
private bool isReqiredBurnInProtection;
/// <summary>
/// ミュート状態であるかどうかを表します。
/// </summary>
private bool isMute;
#endregion
#region レシーバー
/// <summary>
/// タイムゾーンを変更した時に通知を受け取るレシーバーを表します。
/// </summary>
private ActionReservedBroadcastReceiver timeZoneReceiver;
#endregion
/// <summary>
/// <see cref="MyWatchFaceEngine"/>クラスの新しいインスタンスを生成します。
/// </summary>
/// <param name="owner"><see cref="CanvasWatchFaceService"/>クラスを継承したオブジェクトの参照</param>
public MyWatchFaceEngine( CanvasWatchFaceService owner ) : base( owner ) {
// CanvasWatchFaceServiceクラスを継承したオブジェクトの参照をセットします。
this.owner = owner;
// 時刻を更新した時の処理を構成します。
updateTimeHandler = new Handler(
message => {
#if DEBUG
if( Log.IsLoggable( logTag, LogPriority.Info ) ) {
Log.Info( logTag, $"Updating timer: Message = {message.What}" );
}
#endif
// Whatプロパティでメッセージを判別します。
switch( message.What ) {
case MessageUpdateTime:
// TODO : 時刻の更新のメッセージの時の処理を入れます。
// ウォッチフェイスを再描画します。
Invalidate();
// タイマーを動作させるかどうかを判別します。
if( ShouldTimerBeRunning ) {
/*
Javaでは、System.currentTimeMillisメソッドで世界協定時(ミリ秒)を取得します。
一方C#では、DateTime.UtcNow.Ticksプロパティで世界協定時(100ナノ秒)取得し、
TimeSpan.TicksPerMillisecondフィールドで割って、ミリ秒の値を求めます。
*/
long timeMillseconds = DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond;
// delayMs = 更新間隔 - ( 現在時刻(ミリ秒) % 更新間隔) -> 更新間隔との差
long delayMilliseconds = InteractiveUpdateRateMilliseconds - ( timeMillseconds % InteractiveUpdateRateMilliseconds );
// UpdateTimeHandlerにメッセージをセットします。
// SendEmptyMessageDelayedメソッドは指定した時間後にメッセージを発行します。
updateTimeHandler.SendEmptyMessageDelayed( MessageUpdateTime, delayMilliseconds );
}
break;
}
}
);
// TimeZoneReceiverのインスタンスを生成します。
timeZoneReceiver = new ActionReservedBroadcastReceiver(
intent => {
// TODO : ブロードキャストされた Intent.ActionTimezoneChanged のIntentオブジェクトを受け取った時に実行する処理を入れます。
// IntentからタイムゾーンIDを取得して、Timeオブジェクトのタイムゾーンに設定し、現在時刻を取得します。
// intent.GetStringExtra( "time-zone" )の戻り値はタイムゾーンのIDです。
// Time ( Android )
//nowTime.Clear( intent.GetStringExtra( "time-zone" ) );
//nowTime.SetToNow();
// Calendar ( Java )
nowTime = Java.Util.Calendar.GetInstance( Java.Util.TimeZone.GetTimeZone( intent.GetStringExtra( "time-zone" ) ) );
// DateTime ( C# )
//nowTime = DateTime.Now;
},
// Intentフィルターに「ActionTimezoneChanged」を指定します。
Intent.ActionTimezoneChanged
);
}
/// <summary>
/// <see cref="MyWatchFaceEngine"/>のインスタンスが生成された時に実行します。
/// </summary>
/// <param name="holder">ディスプレイ表面を表すオブジェクト</param>
public override void OnCreate( ISurfaceHolder holder ) {
// TODO : ここでは主に、以下の処理を行います。
// ・リソースから画像の読み込み
// ・Paintなどのグラフィックスオブジェクトを生成
// ・時刻を格納するオブジェクトの作成
// ・システムのUI(インジケーターやOK Googleの表示など)の設定
// システムのUIの配置方法を設定します。
SetWatchFaceStyle(
new WatchFaceStyle.Builder( owner )
// ユーザーからのタップイベントを有効にします。
//.SetAcceptsTapEvents( true )
// 通知が来た時の通知カードの高さを設定します。
.SetCardPeekMode( WatchFaceStyle.PeekModeShort )
//
.SetBackgroundVisibility( WatchFaceStyle.BackgroundVisibilityInterruptive )
// システムUIのデジタル時計を表示するするかどうかを設定します。(使用している例として、デフォルトで用意されている「シンプル」があります。)
.SetShowSystemUiTime( false )
// ビルドします。
.Build()
);
// ベースクラスのOnCreateメソッドを実行します。
base.OnCreate( holder );
var resources = owner.Resources;
// 背景用のグラフィックスオブジェクトを生成します。
backgroundPaint = new Paint();
// リソースから背景色を読み込みます。
backgroundPaint.Color = WatchfaceUtility.ConvertARGBToColor( ContextCompat.GetColor( owner, Resource.Color.background ) );
// 時針用のPaintグラフィックスオブジェクトを生成します。
var hourHandPaint = new Paint();
hourHandPaint.Color = WatchfaceUtility.ConvertARGBToColor( ContextCompat.GetColor( owner, Resource.Color.analog_hands ) );
// 時針の幅を設定します。
hourHandPaint.StrokeWidth = resources.GetDimension( Resource.Dimension.hour_hand_stroke );
// アンチエイリアスを有効にします。
hourHandPaint.AntiAlias = true;
// 線端の形は丸形を指定します。
hourHandPaint.StrokeCap = Paint.Cap.Round;
// 時針オブジェクトを初期化します。
hourHand = new HourAnalogHandStroke( hourHandPaint );
// 分針用のPaintグラフィックスオブジェクトを生成します。
var minuteHandPaint = new Paint();
minuteHandPaint.Color = WatchfaceUtility.ConvertARGBToColor( ContextCompat.GetColor( owner, Resource.Color.analog_hands ) );
minuteHandPaint.StrokeWidth = resources.GetDimension( Resource.Dimension.minute_hand_stroke );
minuteHandPaint.AntiAlias = true;
minuteHandPaint.StrokeCap = Paint.Cap.Round;
minuteHand = new MinuteAnalogHandStroke( minuteHandPaint );
// 秒針用のPaintグラフィックスオブジェクトを生成します。
var secondHandPaint = new Paint();
secondHandPaint.Color = WatchfaceUtility.ConvertARGBToColor( ContextCompat.GetColor( owner, Resource.Color.analog_sec_hand ) );
secondHandPaint.StrokeWidth = resources.GetDimension( Resource.Dimension.second_hand_stroke );
secondHandPaint.AntiAlias = true;
secondHandPaint.StrokeCap = Paint.Cap.Round;
secondHand = new SecondAnalogHandStroke( secondHandPaint );
// 時刻を格納するオブジェクトを生成します。
// Time ( Android )
//nowTime = new Time();
// Calendar ( Java )
nowTime = Java.Util.Calendar.GetInstance( Java.Util.TimeZone.Default );
// DateTime ( C# )
// DateTime構造体は値型なので、オブジェクトの生成はは不要です。
#region 最新のAndroid SDKにおける、Android.Content.Res.Resources.GetColorメソッドについて
/*
Android.Content.Res.Resources.GetColorメソッドは、Android SDK Level 23以降で非推奨(Deprecated)となっています。
代わりの方法として、Android.Support.V4.Content.ContextCompat.GetColorメソッドを使用します。
[CanvasWatchFaceServiceオブジェクト].Resources.GetColor( Resource.Color.[リソース名] );
ContextCompat.GetColor( [CanvasWatchFaceServiceオブジェクト], Resource.Color.[リソース名] );
※CanvasWatchFaceServiceクラスはContextクラスを継承しています。
なお、ContextCompat.GetColorの戻り値はColor型でなく、ARGB値を格納したint型となります。
Chronoir_net.Chronoface.Utility.WatchfaceUtility.ConvertARGBToColor( int )メソッドで、Color型に変換することができます。
*/
#endregion
}
/// <summary>
/// このウォッチフェイスサービスが破棄される時に実行します。
/// </summary>
/// <remarks>
/// 例えば、このウォッチフェイスから別のウォッチフェイスに切り替えた時に呼び出されます。
/// </remarks>
public override void OnDestroy() {
// UpdateTimeHandlerにセットされているメッセージを削除します。
updateTimeHandler.RemoveMessages( MessageUpdateTime );
// ベースクラスのOnDestroyメソッドを実行します。
base.OnDestroy();
}
/// <summary>
/// <see cref="WindowInsets"/>を適用する時に実行します。
/// </summary>
/// <param name="insets">適用される<see cref="WindowInsets"/>オブジェクト</param>
/// <remarks>Android Wearが丸形がどうかを、このメソッド内で判別することができます。</remarks>
public override void OnApplyWindowInsets( WindowInsets insets ) {
base.OnApplyWindowInsets( insets );
#if DEBUG
if( Log.IsLoggable( logTag, LogPriority.Info ) ) {
Log.Info( logTag, $"{nameof( OnApplyWindowInsets )}: Round = {insets.IsRound}" );
}
#endif
// Android Wearが丸形かどうかを判別します。
//bool isRound = insets.IsRound;
}
/// <summary>
/// ウォッチフェイスのプロパティが変更された時に実行します。
/// </summary>
/// <param name="properties">プロパティ値を格納したバンドルオブジェクト</param>
public override void OnPropertiesChanged( Bundle properties ) {
// ベースクラスのOnPropertiesChangedメソッドを実行します。
base.OnPropertiesChanged( properties );
// LowBitアンビエントモードを使用するかどうかの値を取得します。
isRequiredLowBitAmbient = properties.GetBoolean( PropertyLowBitAmbient, false );
// Burn-in-protectionモードを使用するかどうかの値を取得します。
isReqiredBurnInProtection = properties.GetBoolean( PropertyBurnInProtection, false );
#if DEBUG
if( Log.IsLoggable( logTag, LogPriority.Info ) ) {
Log.Info( logTag, $"{nameof( OnPropertiesChanged )}: Low-bit ambient = {isRequiredLowBitAmbient}" );
Log.Info( logTag, $"{nameof( OnPropertiesChanged )}: Burn-in-protection = {isReqiredBurnInProtection}" );
}
#endif
}
/// <summary>
/// 時間を更新した時に実行します。
/// </summary>
/// <remarks>
/// 画面の表示・非表示やモードに関わらず、1分ごとに呼び出されます。
/// </remarks>
public override void OnTimeTick() {
// ベースクラスのOnTimeTickメソッドを実行します。
base.OnTimeTick();
if( Log.IsLoggable( logTag, LogPriority.Info ) ) {
Log.Info( logTag, $"OnTimeTick" );
}
// ウォッチフェイスを再描画します。
Invalidate();
}
/// <summary>
/// アンビエントモードが変更された時に実行されます。
/// </summary>
/// <param name="inAmbientMode">アンビエントモードであるかどうかを示す値</param>
public override void OnAmbientModeChanged( bool inAmbientMode ) {
// ベースクラスのOnAmbientModeChangedメソッドを実行します。
base.OnAmbientModeChanged( inAmbientMode );
// アンビエントモードが変更されたかどうかを判別します。
if( isAmbient != inAmbientMode ) {
#if DEBUG
if( Log.IsLoggable( logTag, LogPriority.Info ) ) {
Log.Info( logTag, $"{nameof( OnAmbientModeChanged )}: Ambient-mode = {isAmbient} -> {inAmbientMode}" );
}
#endif
// 現在のアンビエントモードをセットします。
isAmbient = inAmbientMode;
// デバイスがLowBitアンビエントモードをサポートしているかどうかを判別します。
if( isRequiredLowBitAmbient ) {
bool antiAlias = !inAmbientMode;
// TODO : LowBitアンビエントモードがサポートされている時の処理を入れます。
// アンビエントモードの時は、針のPaintオブジェクトのアンチエイリアスを無効にし、
// そうでなければ有効にします。
hourHand.Paint.AntiAlias = antiAlias;
minuteHand.Paint.AntiAlias = antiAlias;
secondHand.Paint.AntiAlias = antiAlias;
// ウォッチフェイスを再描画します。
Invalidate();
}
// タイマーを更新します。
UpdateTimer();
}
}
/// <summary>
/// Interruptionフィルターが変更された時に実行します。
/// </summary>
/// <param name="interruptionFilter">Interruptionフィルター</param>
public override void OnInterruptionFilterChanged( int interruptionFilter ) {
// ベースクラスのOnInterruptionFilterChangedメソッドを実行します。
base.OnInterruptionFilterChanged( interruptionFilter );
// Interruptionフィルターが変更されたかどうか判別します。
bool inMuteMode = ( interruptionFilter == InterruptionFilterNone );
// ミュートモードが変更されたかどうか判別します。
if( isMute != inMuteMode ) {
#if DEBUG
if( Log.IsLoggable( logTag, LogPriority.Info ) ) {
Log.Info( logTag, $"{nameof( OnInterruptionFilterChanged )}: Mute-mode = {isMute} -> {inMuteMode}" );
}
#endif
isMute = inMuteMode;
// TODO : 通知状態がOFFの時の処理を入れます。
// ウォッチフェイスを再描画します。
Invalidate();
}
}
/// <summary>
/// ユーザーがウォッチフェイスをタップした時に実行されます。
/// </summary>
/// <param name="tapType">タップの種類</param>
/// <param name="xValue">タップのX位置</param>
/// <param name="yValue">タップのY位置</param>
/// <param name="eventTime">画面をタッチしている時間?</param>
/// <remarks>
/// Android Wear 1.3以上に対応しています。
/// このメソッドが呼び出させるには、<see cref="WatchFaceStyle.Builder"/>の生成において、SetAcceptsTapEvents( true )を呼び出す必要があります。
/// インタラクティブモードのみ有効です。
/// </remarks>
public override void OnTapCommand( int tapType, int xValue, int yValue, long eventTime ) {
//var resources = owner.Resources;
#if DEBUG
if( Log.IsLoggable( logTag, LogPriority.Info ) ) {
Log.Info( logTag, $"{nameof( OnTapCommand )}: Type = {tapType}, ( x, y ) = ( {xValue}, {yValue} ), Event time = {eventTime}" );
}
#endif
// タップの種類を判別します。
switch( tapType ) {
case TapTypeTouch:
// TODO : ユーザーが画面をタッチした時の処理を入れます。
break;
case TapTypeTouchCancel:
// TODO : ユーザーが画面をタッチしたまま、指を動かした時の処理を入れます。
break;
case TapTypeTap:
// TODO : ユーザーがタップした時の処理を入れます。
break;
}
}
/// <summary>
/// ウォッチフェイスの描画時に実行されます。
/// </summary>
/// <param name="canvas">ウォッチフェイスに描画するためのキャンバスオブジェクト</param>
/// <param name="bounds">画面のサイズを格納するオブジェクト</param>
public override void OnDraw( Canvas canvas, Rect bounds ) {
// TODO : 現在時刻を取得し、ウォッチフェイスを描画する処理を入れます。
// 現在時刻にセットします。
// Time ( Android )
//nowTime.SetToNow();
// Calendar ( Java )
nowTime = Java.Util.Calendar.GetInstance( nowTime.TimeZone );
// DateTime ( C# )
//nowTime = DateTime.Now;
#if DEBUG
if( Log.IsLoggable( logTag, LogPriority.Info ) ) {
Log.Info( logTag, $"{nameof( OnDraw )}: Now time = {WatchfaceUtility.ConvertToDateTime( nowTime ):yyyy/MM/dd HH:mm:ss K}" );
}
#endif
// 背景を描画します。
// アンビエントモードであるかどうか判別します。
if( IsInAmbientMode ) {
// アンビエントモードの時は、黒色で塗りつぶします。
canvas.DrawColor( Color.Black );
}
else {
// そうでない時は、背景画像を描画します。
canvas.DrawRect( 0, 0, canvas.Width, canvas.Height, backgroundPaint );
}
// 中心のXY座標を求めます。
float centerX = bounds.Width() / 2.0f;
float centerY = bounds.Height() / 2.0f;
// 針の長さを求めます。
secondHand.Length = centerX - 20;
minuteHand.Length = centerX - 40;
hourHand.Length = centerX - 80;
hourHand.SetTime( nowTime );
// 時針を描画します。
canvas.DrawLine( centerX, centerY, centerX + hourHand.X, centerY + hourHand.Y, hourHand.Paint );
minuteHand.SetTime( nowTime );
// 分針を描画します。
canvas.DrawLine( centerX, centerY, centerX + minuteHand.X, centerY + minuteHand.Y, minuteHand.Paint );
// アンビエントモードでないかどうかを判別します。
if( !IsInAmbientMode ) {
secondHand.SetTime( nowTime );
// 分針を描画します。
canvas.DrawLine( centerX, centerY, centerX + secondHand.X, centerY + secondHand.Y, secondHand.Paint );
}
}
/// <summary>
/// ウォッチフェイスの表示・非表示が切り替わった時に実行します。
/// </summary>
/// <param name="visible">ウォッチフェイスの表示・非表示</param>
public override void OnVisibilityChanged( bool visible ) {
// ベースクラスのOnVisibilityChangedメソッドを実行します。
base.OnVisibilityChanged( visible );
#if DEBUG
if( Log.IsLoggable( logTag, LogPriority.Info ) ) {
Log.Info( logTag, $"{nameof( OnVisibilityChanged )}: Visible = {visible}" );
}
#endif
// ウォッチフェイスの表示・非表示を判別します。
if( visible ) {
if( timeZoneReceiver == null ) {
timeZoneReceiver = new ActionReservedBroadcastReceiver(
intent => {
// Time ( Android )
//nowTime.Clear( intent.GetStringExtra( "time-zone" ) );
//nowTime.SetToNow();
// Calendar ( Java )
nowTime = Java.Util.Calendar.GetInstance( Java.Util.TimeZone.GetTimeZone( intent.GetStringExtra( "time-zone" ) ) );
// DateTime ( C# )
//nowTime = DateTime.Now;
},
Intent.ActionTimezoneChanged
);
}
// タイムゾーン用のレシーバーを登録します。
timeZoneReceiver.IsRegistered = true;
// ウォッチフェイスが非表示の時にタイムゾーンが変化した場合のために、タイムゾーンを更新します。
// Time ( Android )
//nowTime.Clear( Java.Util.TimeZone.Default.ID );
//nowTime.SetToNow();
// Calendar ( Java )
nowTime = Java.Util.Calendar.GetInstance( Java.Util.TimeZone.Default );
// DateTime ( C# )
//nowTime = DateTime.Now;
}
else {
// タイムゾーン用のレシーバーを登録解除します。
timeZoneReceiver.IsRegistered = false;
}
// タイマーの動作を更新します。
UpdateTimer();
}
/// <summary>
/// タイマーの動作を更新します。
/// </summary>
private void UpdateTimer() {
#if DEBUG
if( Log.IsLoggable( logTag, LogPriority.Info ) ) {
Log.Info( logTag, $"{nameof( UpdateTimer )}" );
}
#endif
// UpdateTimeHandlerからMsgUpdateTimeメッセージを取り除きます。
updateTimeHandler.RemoveMessages( MessageUpdateTime );
// タイマーを動作させるかどうかを判別します。
if( ShouldTimerBeRunning ) {
// UpdateTimeHandlerにMsgUpdateTimeメッセージをセットします。
updateTimeHandler.SendEmptyMessage( MessageUpdateTime );
}
}
/// <summary>
/// タイマーを動作させるかどうかを表す値を取得します。
/// </summary>
private bool ShouldTimerBeRunning =>
IsVisible && !IsInAmbientMode;
}
}
}
#region Veision Info.
/**
* @file WatchFaceUtility.cs
* @brief Provides functions to make Watchface development more convenient.
*
* @par Version
* 0.6.0
* @par Author
* Nia Tomonaka
* @par Copyright
* Copyright (C) 2016 Chronoir.net
* @par Released Day
* 2016/12/03
* @par Last modified Day
* 2016/12/03
* @par Licence
* MIT Licence
* @par Contact
* @@nia_tn1012( https://twitter.com/nia_tn1012/ )
* @par Homepage
* - http://chronoir.net/ (Homepage)
* - https://github.com/Nia-TN1012/Chrooir_net.Chronoface.Utility/ (GitHub)
* - https://www.nuget.org/packages/Chronoir_net.Chronoface.Utility/ (NuGet Gallery)
*/
#endregion
using System;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.Text.Format;
namespace Chronoir_net.Chronoface.Utility {
/// <summary>
/// Provides functions to make Watchface development more convenient.
/// </summary>
public static class WatchfaceUtility {
/// <summary>
/// Converts the integer value containing the ARGB value to <see cref="Color"/> type.
/// </summary>
/// <param name="argb">ARGB value</param>
/// <returns><See cref = "Color" /> type object equivalent to ARGB value</returns>
public static Color ConvertARGBToColor( int argb ) =>
Color.Argb( ( argb >> 24 ) & 0xFF, ( argb >> 16 ) & 0xFF, ( argb >> 8 ) & 0xFF, argb & 0xFF );
/// <summary>
/// Converts the <see cref="Time"/> type object to <see cref="DateTime"/> type.
/// </summary>
/// <param name="time"><see cref="Time"/> type object</param>
/// <returns><see cref="DateTime"/> type object equivalent to argment</returns>
#if __ANDROID_22__
[Obsolete( "This method is obsoleted in this android platform." )]
#endif
public static DateTime ConvertToDateTime( Time time ) =>
new DateTime( time.Year, time.Month, time.MonthDay, time.Hour, time.Minute, time.Second, DateTimeKind.Local );
/// <summary>
/// Converts the <see cref="Java.Lang.Character"/> type object to <see cref="DateTime"/> type.
/// </summary>
/// <param name="time"><see cref="Java.Util.Calendar"/> type object</param>
/// <returns><see cref="DateTime"/> type object equivalent to argment</returns>
public static DateTime ConvertToDateTime( Java.Util.Calendar time ) =>
new DateTime(
time.Get( Java.Util.CalendarField.Year ), time.Get( Java.Util.CalendarField.Month ), time.Get( Java.Util.CalendarField.Date ),
time.Get( Java.Util.CalendarField.HourOfDay ), time.Get( Java.Util.CalendarField.Minute ), time.Get( Java.Util.CalendarField.Second ),
DateTimeKind.Local
);
/// <summary>
/// Returns the <see cref="DateTime"/> type object specified as an argument as it is.
/// </summary>
/// <param name="time"><see cref="DateTime"/> type object</param>
/// <returns>Specified argument itself</returns>
/// <remarks>
/// This is defined to ensure compatibility with <see cref="ConvertToDateTime(Time)"/> method and <see cref="ConvertToDateTime(Java.Util.Calendar)"/> method.
/// </remarks>
public static DateTime ConvertToDateTime( DateTime time ) => time;
}
#region BroadcastReciever Extention
/// <summary>
/// Provides a receiver class that receives the broadcasted <see cref="Intent"/> object and performs preset processing.
/// </summary>
public class ActionReservedBroadcastReceiver : BroadcastReceiver {
/// <summary>
/// Represents a delegate executed when a <see cref="Intent"/> object is received.
/// </summary>
private Action<Intent> receiver;
/// <summary>
/// Represents the MIME type for identifying <see cref="Intent"/> information.
/// </summary>
private IntentFilter intentFilter;
/// <summary>
/// Indicates whether <see cref="BroadcastReceiver"/> object is registered in <see cref="Application.Context"/>.
/// </summary>
private bool isRegistered = false;
/// <summary>
/// Gets and sets ( register to <see cref="Application.Context"/> or unregister ) registration state of <see cref="BroadcastReceiver"/> object to <see cref="Application.Context"/>.
/// </summary>
public bool IsRegistered {
get { return isRegistered; }
set {
if( value != isRegistered ) {
if( value ) {
Application.Context.RegisterReceiver( this, intentFilter );
}
else {
Application.Context.UnregisterReceiver( this );
}
isRegistered = value;
}
}
}
/// <summary>
/// Creates a new instance of <see cref="ActionReservedBroadcastReceiver"/> class from the specified delegate and <see cref="Intent"/> MIME type.
/// </summary>
/// <param name="action">Delegate to execute when receiving <see cref="Intent"/> object</param>
/// <param name="filter">MIME type for identifying <see cref="Intent"/> information</param>
public ActionReservedBroadcastReceiver( Action<Intent> action, string filter ) {
receiver = action;
intentFilter = new IntentFilter( filter );
}
/// <summary>
/// Invoked when receives a broadcast <see cref="Intent"/> object.
/// </summary>
/// <param name="context"><see cref="Context"/> object registering receiver</param>
/// <param name="intent">Broadcasted <see cref="Intent"/> object</param>
public override void OnReceive( Context context, Intent intent ) {
receiver?.Invoke( intent );
}
}
#endregion
#region Analog hands stroke
/// <summary>
/// Represents the basis of the analog meter stroke function stored a <see cref="Android.Graphics.Paint"/> object, a XY coordinate and length of a hand tip.
/// </summary>
public abstract class AnalogHandStroke {
/// <summary>
/// Gets the <see cref="Android.Graphics.Paint"/> object of the hand.
/// </summary>
public Paint Paint { get; private set; }
/// <summary>
/// Gets the X coordinate of the hand tip.
/// </summary>
public float X { get; protected set; } = 0.0f;
/// <summary>
/// Gets the Y coordinate of the hand tip.
/// </summary>
public float Y { get; protected set; } = 0.0f;
/// <summary>
/// Gets the length of the hand.
/// </summary>
public float Length { get; set; }
/// <summary>
/// Creates a new instance of <see cref="AnalogHandStroke"/> class from the specified <see cref="Android.Graphics.Paint"/> object and hand length.
/// </summary>
/// <param name="paint"><see cref="Android.Graphics.Paint"/> object of the hand</param>
/// <param name="length">Hand lendth</param>
public AnalogHandStroke( Paint paint, float length = 0.0f ) {
Paint = paint ?? new Paint();
Length = length;
}
}
/// <summary>
/// Provides analog meter stroke function for second hand.
/// </summary>
public class SecondAnalogHandStroke : AnalogHandStroke {
/// <summary>
/// Creates a new instance of <see cref="SecondAnalogHandStroke"/> class from the specified <see cref="Paint"/> object and hand length.
/// </summary>
/// <param name="paint"><see cref="Paint"/> object of the hand</param>
/// <param name="length">Hand lendth</param>
public SecondAnalogHandStroke( Paint paint, float length = 0.0f ) : base( paint, length ) { }
/// <summary>
/// Calculates the XY coordinates of the second hand for the specified seconds.
/// </summary>
/// <param name="second">Second ( 0~59 )</param>
public void SetTime( int second ) {
// Because 2π = 360 degrees is divided into 60, the angle per second is 6 degrees.
// θ_sec = second / 30 * π
float handRotation = second / 30f * ( float )Math.PI;
// Converts from polar coordinates to XY coordinates at the second hand tip coordinates.
X = ( float )Math.Sin( handRotation ) * Length;
Y = ( float )-Math.Cos( handRotation ) * Length;
}
/// <summary>
/// Calculates the XY coordinates of the second hand for the seconds of the specified <see cref="Time"/> object.
/// </summary>
/// <param name="time"><see cref="Time"/> object storing the time</param>
#if __ANDROID_22__
[Obsolete( "This method is obsoleted in this android platform." )]
#endif
public void SetTime( Time time ) =>
SetTime( time.Second );
/// <summary>
/// Calculates the XY coordinates of the second hand for the seconds of the specified <see cref="Java.Util.Calendar"/> object.
/// </summary>
/// <param name="time"><see cref="Java.Util.Calendar"/> object storing the time</param>
public void SetTime( Java.Util.Calendar time ) =>
SetTime( time.Get( Java.Util.CalendarField.Second ) );
/// <summary>
/// Calculates the XY coordinates of the second hand for the seconds of the specified <see cref="DateTime"/> object.
/// </summary>
/// <param name="time"><see cref="DateTime"/> object storing the time</param>
public void SetTime( DateTime time ) =>
SetTime( time.Second );
}
/// <summary>
/// Provides analog meter stroke function for minute hand.
/// </summary>
public class MinuteAnalogHandStroke : AnalogHandStroke {
/// <summary>
/// Creates a new instance of <see cref="MinuteAnalogHandStroke"/> class from the specified <see cref="Paint"/> object and hand length.
/// </summary>
/// <param name="paint"><see cref="Paint"/> object of the hand</param>
/// <param name="length">Hand lendth</param>
public MinuteAnalogHandStroke( Paint paint, float length = 0.0f ) : base( paint, length ) { }
/// <summary>
/// Calculates the XY coordinates of the minute hand for the specified minutes.
/// </summary>
/// <param name="minute">Minute ( 0~59 )</param>
public void SetTime( int minute ) {
// As with the second hand, the angle per minute is 6 degrees.
float handRotation = minute / 30f * ( float )Math.PI;
X = ( float )Math.Sin( handRotation ) * Length;
Y = ( float )-Math.Cos( handRotation ) * Length;
}
/// <summary>
/// Calculates the XY coordinates of the minute hand for the minutes of the specified <see cref="Time"/> object.
/// </summary>
/// <param name="time"><see cref="Time"/> object storing the time</param>
#if __ANDROID_22__
[Obsolete( "This method is obsoleted in this android platform." )]
#endif
public void SetTime( Time time ) =>
SetTime( time.Minute );
/// <summary>
/// Calculates the XY coordinates of the minute hand for the minutes of the specified <see cref="Java.Util.Calendar"/> object.
/// </summary>
/// <param name="time"><see cref="Java.Util.Calendar"/> object storing the time</param>
public void SetTime( Java.Util.Calendar time ) =>
SetTime( time.Get( Java.Util.CalendarField.Minute ) );
/// <summary>
/// Calculates the XY coordinates of the minute hand for the minutes of the specified <see cref="DateTime"/> object.
/// </summary>
/// <param name="time"><see cref="DateTime"/> object storing the time</param>
public void SetTime( DateTime time ) =>
SetTime( time.Minute );
}
/// <summary>
/// Provides analog meter stroke function for hour hand.
/// </summary>
public class HourAnalogHandStroke : AnalogHandStroke {
/// <summary>
/// Creates a new instance of <see cref="HourAnalogHandStroke"/> class from the specified <see cref="Paint"/> object and hand length.
/// </summary>
/// <param name="paint"><see cref="Paint"/> object of the hand</param>
/// <param name="length">Hand lendth</param>
public HourAnalogHandStroke( Paint paint, float length = 0.0f ) : base( paint, length ) { }
/// <summary>
/// Calculates the XY coordinates of the hour hand for the specified hours and minutes.
/// </summary>
/// <param name="minute">Hour ( 0~23 )</param>
/// <param name="minute">Minute ( 0~59 )</param>
public void SetTime( int hour, int minute ) {
float handRotation = ( ( hour + ( minute / 60f ) ) / 6f ) * ( float )Math.PI;
X = ( float )Math.Sin( handRotation ) * Length;
Y = ( float )-Math.Cos( handRotation ) * Length;
}
/// <summary>
/// Calculates the XY coordinates of the hour hand for the hours and minutes of the specified <see cref="Time"/> object.
/// </summary>
/// <param name="time"><see cref="Time"/> object storing the time</param>
#if __ANDROID_22__
[Obsolete( "This method is obsoleted in this android platform." )]
#endif
public void SetTime( Time time ) =>
SetTime( time.Hour, time.Minute );
/// <summary>
/// Calculates the XY coordinates of the hour hand for the hours and minutes of the specified <see cref="Java.Util.Calendar"/> object.
/// </summary>
/// <param name="time"><see cref="Java.Util.Calendar"/> object storing the time</param>
public void SetTime( Java.Util.Calendar time ) =>
SetTime( time.Get( Java.Util.CalendarField.Hour ), time.Get( Java.Util.CalendarField.Minute ) );
/// <summary>
/// Calculates the XY coordinates of the hour hand for the hours and minutes of the specified <see cref="DateTime"/> object.
/// </summary>
/// <param name="time"><see cref="DateTime"/> object storing the time</param>
public void SetTime( DateTime time ) =>
SetTime( time.Hour, time.Minute );
}
#endregion
#region Text style for digital
/// <summary>
/// Represents the text style stored the <see cref="Android.Graphics.Paint"/> object of the text and the top left XY coordinates.
/// </summary>
public class DigitalTextStyle {
/// <summary>
/// Gets the text <see cref="Android.Graphics.Paint"/> object.
/// </summary>
public Paint Paint { get; private set; }
/// <summary>
/// Gets and sets the left top X coordinate.
/// </summary>
public float XOffset { get; set; } = 0.0f;
/// <summary>
/// Gets and sets the left top Y coordinate.
/// </summary>
public float YOffset { get; set; } = 0.0f;
/// <summary>
/// Creates a new instance of the <see cref="DigitalTextStyle"/> class from the specified <see cref="Android.Graphics.Paint"/> object and the left top XY coordinates.
/// </summary>
/// <param name="paint">Text <see cref="Android.Graphics.Paint"/> object</param>
/// <param name="xOffset">Left top X coordinate</param>
/// <param name="yOffset">Left top Y coordinate</param>
public DigitalTextStyle( Paint paint, float xOffset = 0.0f, float yOffset = 0.0f ) {
Paint = paint ?? new Paint();
XOffset = xOffset;
YOffset = yOffset;
}
}
#endregion
}
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
#region Javaのimport構文とC#のusingディレクティブとの関係
/*
基本的に、
・Javaのimport構文のパッケージ名
・C#のusingディレクティブの名前空間
が相互に対応しています。
(但し、パッケージ名は全て小文字で、名前空間はパスカルケース(略称の場合はすべて大文字)です)
◆ Android.Content
・Java
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
・C#
using Android.Content;
◆ Android.Graphics
・Java
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
・C#
using Android.Graphics;
◆ Android.Graphics.Drawable
・Java
import android.graphics.drawable.BitmapDrawable;
・C#
using Android.Graphics.Drawable;
◆ Android.OS
・Java
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
・C#
using Android.OS;
◆ Android.Support.Wearable.Watchface
・Java
import android.support.wearable.watchface.CanvasWatchFaceService;
import android.support.wearable.watchface.WatchFaceStyle;
・C#
using Android.Support.Wearable.Watchface;
◆ Android.Support.V4.Content
・Java
import android.support.v4.content.ContextCompat;
・C#
using Android.Support.V4.Content;
◆ Android.Text.Format
・Java
import android.text.format.Time;
・C#
using Android.Text.Format;
◆ Android.View
・Java
import android.view.SurfaceHolder;
・C#
using Android.Views;
*/
#endregion
using Android.Content;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.OS;
using Android.Support.V4.Content;
using Android.Support.Wearable.Watchface;
using Android.Text.Format;
using Android.Views;
// Service属性、Metadata属性で使用します。
using Android.App;
// WallpaperServiceクラスで使用します。
using Android.Service.Wallpaper;
using Android.Util;
using Chronoir_net.Chronoface.Utility;
namespace WatchFace {
// ウォッチフェイスサービス要素の内容を構成します。
// Labelプロパティには、ウォッチフェイスの選択に表示する名前を設定します。
// 注:Nameプロパティについて :
// Xamarinでは、「.」から始まる AndroidのShortcut が利用できません。
//  なお、省略した場合、Nameには「md[GUID].クラス名」となります。
[Service( Label = "@string/my_watch_name", Permission = "android.permission.BIND_WALLPAPER" )]
// 壁紙にバインドするパーミッションを宣言します。
[MetaData( "android.service.wallpaper", Resource = "@xml/watch_face" )]
// プレビューに表示する画像を指定します。
// previewが四角形用、preview_circularが丸形用です。
[MetaData( "com.google.android.wearable.watchface.preview", Resource = "@drawable/preview" )]
[MetaData( "com.google.android.wearable.watchface.preview_circular", Resource = "@drawable/preview_circular" )]
// このWatch Faceで使用するIntentFilterを宣言します。
[IntentFilter( new[] { "android.service.wallpaper.WallpaperService" }, Categories = new[] { "com.google.android.wearable.watchface.category.WATCH_FACE" } )]
/// <summary>
/// デジタル時計のウォッチフェイスサービスを提供します。
/// </summary>
/// <remarks>
/// <see cref="CanvasWatchFaceService"/>クラスを継承して実装します。
/// </remarks>
public class MyWatchFaceService : CanvasWatchFaceService {
/// <summary>
/// ログ出力用のタグを表します。
/// </summary>
private const string logTag = nameof( MyWatchFaceService );
/// <summary>
/// インタラクティブモードにおける更新間隔(ミリ秒単位)を表します。
/// </summary>
/// <remarks>
/// アンビエントモードの時は、このフィールドの設定にかかわらず、1分間隔で更新されます。
/// </remarks>
/// <example>
/// // 更新間隔を1秒に設定する場合
/// // Java.Util.Concurrent.TimeUnitを使用する場合
/// Java.Util.Concurrent.TimeUnit.Seconds.ToMillis( 1 )
/// // System.TimeSpanを使用する場合( 戻り値はdouble型なので、long型にキャストします )
/// ( long )System.TimeSpan.FromSeconds( 1 ).TotalMilliseconds
/// // 注 : TimeSpanのMillisecondsは時間の内、ミリ秒の部分のみです。ミリ秒単位の合計を取得するには、TotalMillisecondsを使用します。
/// </example>
private static readonly long InteractiveUpdateRateMilliseconds = Java.Util.Concurrent.TimeUnit.Seconds.ToMillis( 1 );
/// <summary>
/// インタラクティブモードにて、定期的に時刻を更新するための、ハンドラー用のメッセージのIDを表します。
/// </summary>
private const int MessageUpdateTime = 0;
/// <summary>
/// <see cref="CanvasWatchFaceService.Engine"/>オブジェクトが作成される時に実行します。
/// </summary>
/// <returns><see cref="CanvasWatchFaceService.Engine"/>クラスを継承したオブジェクト</returns>
public override WallpaperService.Engine OnCreateEngine() {
// CanvasWatchFaceService.Engineクラスを継承したMyWatchFaceEngineのコンストラクターに、
// MyWatchFaceオブジェクトの参照を指定します。
return new MyWatchFaceEngine( this );
}
/// <summary>
/// デジタル時計のウォッチフェイスの<see cref="CanvasWatchFaceService.Engine"/>機能を提供します。
/// </summary>
/// <remarks>
/// <see cref="CanvasWatchFaceService.Engine"/>クラスを継承して実装します。※<see cref="CanvasWatchFaceService"/>は省略可能です。
/// </remarks>
private class MyWatchFaceEngine : Engine {
/// <summary>
/// <see cref="CanvasWatchFaceService"/>オブジェクトの参照を表します。
/// </summary>
private CanvasWatchFaceService owner;
#region タイマー系
/// <summary>
/// 時刻を更新した時の処理を表します。
/// </summary>
/// <remarks>
/// Android Studio( Java系 )側でいう、EngineHandlerの役割を持ちます。
/// </remarks>
private readonly Handler updateTimeHandler;
/// <summary>
/// 時刻を格納するオブジェクトを表します。
/// </summary>
// Time ( Android )
//private Time nowTime;
// Calendar ( Java )
private Java.Util.Calendar nowTime;
// DateTime ( C# )
//private DateTime nowTime;
#endregion
#region 現在時刻の取得に、どのライブラリを使用すればよいのですか?
/*
* 1. Android.Text.Format.Timeクラス
*   AndroidのAPIで用意されている日付・時刻のクラスです。
*   Timeオブジェクト.SetToNowメソッドで、現在のタイムゾーンに対する現在時刻にセットすることができます。
*   Timeオブジェクト.Clearメソッドで、指定したタイムゾーンのIDに対するタイムゾーンを設定します。
*   ※2032年までしか扱えない問題があるため、Android API Level 22以降では旧形式となっています。
*   
* 2. Java.Util.Calendarクラス
*   Javaで用意されている日付・時刻のクラスです。
*   Calendar.GetInstanceメソッドで、現在のタイムゾーンに対する現在時刻にセットすることができます。
*   Calendarオブジェクト.TimeZoneプロパティで、タイムゾーンを設定することができます。
*
* 3. System.DateTime構造体
*   .NET Frameworkで用意されている日付・時刻のクラスです。
*   DateTime.Nowで、現在のタイムゾーンに対する現在時刻を取得することができます。
*   ※タイムゾーンは、Android Wearデバイスとペアリングしているスマートフォンのタイムゾーンとなります。
*/
#endregion
#region グラフィックス系
/// <summary>
/// 背景用のペイントオブジェクトを表します。
/// </summary>
private Paint backgroundPaint;
/// <summary>
/// 時刻表示のテキスト用のオブジェクトを表します。
/// </summary>
private DigitalTextStyle digitalTimeText;
#endregion
#region モード系
/// <summary>
/// アンビエントモードであるかどうかを表します。
/// </summary>
private bool isAmbient;
/// <summary>
/// デバイスがLowBitアンビエントモードを必要としているかどうかを表します。
/// </summary>
/// <remarks>
/// <para>デバイスがLowBitアンビエントモードを使用する場合、アンビエントモードの時は、以下の2点の工夫が必要になります。</para>
/// <para>・使用できる色が8色( ブラック、ホワイト、ブルー、レッド、マゼンタ、グリーン、シアン、イエロー )のみとなります。</para>
/// <para>・アンチエイリアスが無効となります。</para>
/// </remarks>
private bool isRequiredLowBitAmbient;
/// <summary>
/// デバイスがBurn-in-protection(焼き付き防止)を必要としているかどうかを表します。
/// </summary>
/// <remarks>
/// <para> ディスプレイが有機ELなど、Burn-in-protectionが必要な場合、アンビエントモードの時は、以下の2点の工夫が必要になります。</para>
/// <para>・画像はなるべく輪郭のみにします。</para>
/// <para>・ディスプレイの端から数ピクセルには描画しないようにします。</para>
/// </remarks>
private bool isReqiredBurnInProtection;
/// <summary>
/// ミュート状態であるかどうかを表します。
/// </summary>
private bool isMute;
#endregion
#region レシーバー
/// <summary>
/// タイムゾーンを変更した時に通知を受け取るレシーバーを表します。
/// </summary>
private ActionReservedBroadcastReceiver timeZoneReceiver;
#endregion
/// <summary>
/// <see cref="MyWatchFaceEngine"/>クラスの新しいインスタンスを生成します。
/// </summary>
/// <param name="owner"><see cref="CanvasWatchFaceService"/>クラスを継承したオブジェクトの参照</param>
public MyWatchFaceEngine( CanvasWatchFaceService owner ) : base( owner ) {
// CanvasWatchFaceServiceクラスを継承したオブジェクトの参照をセットします。
this.owner = owner;
// 時刻を更新した時の処理を構成します。
updateTimeHandler = new Handler(
message => {
#if DEBUG
if( Log.IsLoggable( logTag, LogPriority.Info ) ) {
Log.Info( logTag, $"Updating timer: Message = {message.What}" );
}
#endif
// Whatプロパティでメッセージを判別します。
switch( message.What ) {
case MessageUpdateTime:
// TODO : 時刻の更新のメッセージの時の処理を入れます。
// ウォッチフェイスを再描画します。
Invalidate();
// タイマーを動作させるかどうかを判別します。
if( ShouldTimerBeRunning ) {
/*
Javaでは、System.currentTimeMillisメソッドで世界協定時(ミリ秒)を取得します。
一方C#では、DateTime.UtcNow.Ticksプロパティで世界協定時(100ナノ秒)取得し、
TimeSpan.TicksPerMillisecondフィールドで割って、ミリ秒の値を求めます。
*/
long timeMillseconds = DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond;
// delayMs = 更新間隔 - ( 現在時刻(ミリ秒) % 更新間隔) -> 更新間隔との差
long delayMilliseconds = InteractiveUpdateRateMilliseconds - ( timeMillseconds % InteractiveUpdateRateMilliseconds );
// UpdateTimeHandlerにメッセージをセットします。
// SendEmptyMessageDelayedメソッドは指定した時間後にメッセージを発行します。
updateTimeHandler.SendEmptyMessageDelayed( MessageUpdateTime, delayMilliseconds );
}
break;
}
}
);
// TimeZoneReceiverのインスタンスを生成します。
timeZoneReceiver = new ActionReservedBroadcastReceiver(
intent => {
// TODO : ブロードキャストされた Intent.ActionTimezoneChanged のIntentオブジェクトを受け取った時に実行する処理を入れます。
// IntentからタイムゾーンIDを取得して、Timeオブジェクトのタイムゾーンに設定し、現在時刻を取得します。
// intent.GetStringExtra( "time-zone" )の戻り値はタイムゾーンのIDです。
// Time ( Android )
//nowTime.Clear( intent.GetStringExtra( "time-zone" ) );
//nowTime.SetToNow();
// Calendar ( Java )
nowTime = Java.Util.Calendar.GetInstance( Java.Util.TimeZone.GetTimeZone( intent.GetStringExtra( "time-zone" ) ) );
// DateTime ( C# )
//nowTime = DateTime.Now;
},
// Intentフィルターに「ActionTimezoneChanged」を指定します。
Intent.ActionTimezoneChanged
);
}
/// <summary>
/// <see cref="MyWatchFaceEngine"/>のインスタンスが生成された時に実行します。
/// </summary>
/// <param name="holder">ディスプレイ表面を表すオブジェクト</param>
public override void OnCreate( ISurfaceHolder holder ) {
// TODO : ここでは主に、以下の処理を行います。
// ・リソースから画像の読み込み
// ・Paintなどのグラフィックスオブジェクトを生成
// ・時刻を格納するオブジェクトの作成
// ・システムのUI(インジケーターやOK Googleの表示など)の設定
// システムのUIの配置方法を設定します。
SetWatchFaceStyle(
new WatchFaceStyle.Builder( owner )
// ユーザーからのタップイベントを有効にします。
//.SetAcceptsTapEvents( true )
// 通知が来た時の通知カードの高さを設定します。
.SetCardPeekMode( WatchFaceStyle.PeekModeShort )
//
.SetBackgroundVisibility( WatchFaceStyle.BackgroundVisibilityInterruptive )
// システムUIのデジタル時計を表示するするかどうかを設定します。(使用している例として、デフォルトで用意されている「シンプル」があります。)
.SetShowSystemUiTime( false )
// ビルドします。
.Build()
);
// ベースクラスのOnCreateメソッドを実行します。
base.OnCreate( holder );
// 背景用のグラフィックスオブジェクトを生成します。
backgroundPaint = new Paint();
// リソースから背景色を読み込みます。
backgroundPaint.Color = WatchfaceUtility.ConvertARGBToColor( ContextCompat.GetColor( owner, Resource.Color.background ) );
//
var digitalTimeTextPaint = new Paint();
digitalTimeTextPaint.Color = WatchfaceUtility.ConvertARGBToColor( ContextCompat.GetColor( owner, Resource.Color.foreground ) );
digitalTimeTextPaint.SetTypeface( Typeface.Default );
digitalTimeTextPaint.AntiAlias = true;
var yOffset = owner.Resources.GetDimension( Resource.Dimension.digital_y_offset );
digitalTimeText = new DigitalTextStyle( paint:digitalTimeTextPaint, yOffset:yOffset );
// 時刻を格納するオブジェクトを生成します。
// Time ( Android )
//nowTime = new Time();
// Calendar ( Java )
nowTime = Java.Util.Calendar.GetInstance( Java.Util.TimeZone.Default );
// DateTime ( C# )
// DateTime構造体は値型なので、オブジェクトの生成はは不要です。
#region 最新のAndroid SDKにおける、Android.Content.Res.Resources.GetColorメソッドについて
/*
Android.Content.Res.Resources.GetColorメソッドは、Android SDK Level 23以降で非推奨(Deprecated)となっています。
代わりの方法として、Android.Support.V4.Content.ContextCompat.GetColorメソッドを使用します。
[CanvasWatchFaceServiceオブジェクト].Resources.GetColor( Resource.Color.[リソース名] );
ContextCompat.GetColor( [CanvasWatchFaceServiceオブジェクト], Resource.Color.[リソース名] );
※CanvasWatchFaceServiceクラスはContextクラスを継承しています。
なお、ContextCompat.GetColorの戻り値はColor型でなく、ARGB値を格納したint型となります。
Chronoir_net.Chronoface.Utility.WatchfaceUtility.ConvertARGBToColor( int )メソッドで、Color型に変換することができます。
*/
#endregion
}
/// <summary>
/// このウォッチフェイスサービスが破棄される時に実行します。
/// </summary>
/// <remarks>
/// 例えば、このウォッチフェイスから別のウォッチフェイスに切り替えた時に呼び出されます。
/// </remarks>
public override void OnDestroy() {
// UpdateTimeHandlerにセットされているメッセージを削除します。
updateTimeHandler.RemoveMessages( MessageUpdateTime );
// ベースクラスのOnDestroyメソッドを実行します。
base.OnDestroy();
}
/// <summary>
/// <see cref="WindowInsets"/>を適用する時に実行します。
/// </summary>
/// <param name="insets">適用される<see cref="WindowInsets"/>オブジェクト</param>
/// <remarks>Android Wearが丸形がどうかを、このメソッド内で判別することができます。</remarks>
public override void OnApplyWindowInsets( WindowInsets insets ) {
base.OnApplyWindowInsets( insets );
#if DEBUG
if( Log.IsLoggable( logTag, LogPriority.Info ) ) {
Log.Info( logTag, $"{nameof( OnApplyWindowInsets )}: Round = {insets.IsRound}" );
}
#endif
// Android Wearが丸形かどうかを判別します。
bool isRound = insets.IsRound;
var xOffset = owner.Resources.GetDimension( isRound ? Resource.Dimension.digital_x_offset_round : Resource.Dimension.digital_x_offset );
var textSize = owner.Resources.GetDimension( isRound ? Resource.Dimension.digital_text_size_round : Resource.Dimension.digital_text_size );
digitalTimeText.XOffset = xOffset;
digitalTimeText.Paint.TextSize = textSize;
}
/// <summary>
/// ウォッチフェイスのプロパティが変更された時に実行します。
/// </summary>
/// <param name="properties">プロパティ値を格納したバンドルオブジェクト</param>
public override void OnPropertiesChanged( Bundle properties ) {
// ベースクラスのOnPropertiesChangedメソッドを実行します。
base.OnPropertiesChanged( properties );
// LowBitアンビエントモードを使用するかどうかの値を取得します。
isRequiredLowBitAmbient = properties.GetBoolean( PropertyLowBitAmbient, false );
// Burn-in-protectionモードを使用するかどうかの値を取得します。
isReqiredBurnInProtection = properties.GetBoolean( PropertyBurnInProtection, false );
#if DEBUG
if( Log.IsLoggable( logTag, LogPriority.Info ) ) {
Log.Info( logTag, $"{nameof( OnPropertiesChanged )}: Low-bit ambient = {isRequiredLowBitAmbient}" );
Log.Info( logTag, $"{nameof( OnPropertiesChanged )}: Burn-in-protection = {isReqiredBurnInProtection}" );
}
#endif
}
/// <summary>
/// 時間を更新した時に実行します。
/// </summary>
/// <remarks>
/// 画面の表示・非表示やモードに関わらず、1分ごとに呼び出されます。
/// </remarks>
public override void OnTimeTick() {
// ベースクラスのOnTimeTickメソッドを実行します。
base.OnTimeTick();
if( Log.IsLoggable( logTag, LogPriority.Info ) ) {
Log.Info( logTag, $"OnTimeTick" );
}
// ウォッチフェイスを再描画します。
Invalidate();
}
/// <summary>
/// アンビエントモードが変更された時に実行されます。
/// </summary>
/// <param name="inAmbientMode">アンビエントモードであるかどうかを示す値</param>
public override void OnAmbientModeChanged( bool inAmbientMode ) {
// ベースクラスのOnAmbientModeChangedメソッドを実行します。
base.OnAmbientModeChanged( inAmbientMode );
// アンビエントモードが変更されたかどうかを判別します。
if( isAmbient != inAmbientMode ) {
#if DEBUG
if( Log.IsLoggable( logTag, LogPriority.Info ) ) {
Log.Info( logTag, $"{nameof( OnAmbientModeChanged )}: Ambient-mode = {isAmbient} -> {inAmbientMode}" );
}
#endif
// 現在のアンビエントモードをセットします。
isAmbient = inAmbientMode;
// デバイスがLowBitアンビエントモードをサポートしているかどうかを判別します。
if( isRequiredLowBitAmbient ) {
bool antiAlias = !inAmbientMode;
// TODO : LowBitアンビエントモードがサポートされている時の処理を入れます。
// アンビエントモードの時は、テキストのPaintオブジェクトのアンチエイリアスを無効にし、
// そうでなければ有効にします。
digitalTimeText.Paint.AntiAlias = antiAlias;
// ウォッチフェイスを再描画します。
Invalidate();
}
// タイマーを更新します。
UpdateTimer();
}
}
/// <summary>
/// Interruptionフィルターが変更された時に実行します。
/// </summary>
/// <param name="interruptionFilter">Interruptionフィルター</param>
public override void OnInterruptionFilterChanged( int interruptionFilter ) {
// ベースクラスのOnInterruptionFilterChangedメソッドを実行します。
base.OnInterruptionFilterChanged( interruptionFilter );
// Interruptionフィルターが変更されたかどうか判別します。
bool inMuteMode = ( interruptionFilter == InterruptionFilterNone );
// ミュートモードが変更されたかどうか判別します。
if( isMute != inMuteMode ) {
#if DEBUG
if( Log.IsLoggable( logTag, LogPriority.Info ) ) {
Log.Info( logTag, $"{nameof( OnInterruptionFilterChanged )}: Mute-mode = {isMute} -> {inMuteMode}" );
}
#endif
isMute = inMuteMode;
// TODO : 通知状態がOFFの時の処理を入れます。
// ウォッチフェイスを再描画します。
Invalidate();
}
}
/// <summary>
/// ユーザーがウォッチフェイスをタップした時に実行されます。
/// </summary>
/// <param name="tapType">タップの種類</param>
/// <param name="xValue">タップのX位置</param>
/// <param name="yValue">タップのY位置</param>
/// <param name="eventTime">画面をタッチしている時間?</param>
/// <remarks>
/// Android Wear 1.3以上に対応しています。
/// このメソッドが呼び出させるには、<see cref="WatchFaceStyle.Builder"/>の生成において、SetAcceptsTapEvents( true )を呼び出す必要があります。
/// インタラクティブモードのみ有効です。
/// </remarks>
public override void OnTapCommand( int tapType, int xValue, int yValue, long eventTime ) {
//var resources = owner.Resources;
#if DEBUG
if( Log.IsLoggable( logTag, LogPriority.Info ) ) {
Log.Info( logTag, $"{nameof( OnTapCommand )}: Type = {tapType}, ( x, y ) = ( {xValue}, {yValue} ), Event time = {eventTime}" );
}
#endif
// タップの種類を判別します。
switch( tapType ) {
case TapTypeTouch:
// TODO : ユーザーが画面をタッチした時の処理を入れます。
break;
case TapTypeTouchCancel:
// TODO : ユーザーが画面をタッチしたまま、指を動かした時の処理を入れます。
break;
case TapTypeTap:
// TODO : ユーザーがタップした時の処理を入れます。
break;
}
}
/// <summary>
/// ウォッチフェイスの描画時に実行されます。
/// </summary>
/// <param name="canvas">ウォッチフェイスに描画するためのキャンバスオブジェクト</param>
/// <param name="bounds">画面のサイズを格納するオブジェクト</param>
public override void OnDraw( Canvas canvas, Rect bounds ) {
// TODO : 現在時刻を取得し、ウォッチフェイスを描画する処理を入れます。
// 現在時刻にセットします。
// Time ( Android )
//nowTime.SetToNow();
// Calendar ( Java )
nowTime = Java.Util.Calendar.GetInstance( nowTime.TimeZone );
// DateTime ( C# )
//nowTime = DateTime.Now;
#if DEBUG
if( Log.IsLoggable( logTag, LogPriority.Info ) ) {
Log.Info( logTag, $"{nameof( OnDraw )}: Now time = {WatchfaceUtility.ConvertToDateTime( nowTime ):yyyy/MM/dd HH:mm:ss K}" );
}
#endif
// 背景を描画します。
// アンビエントモードであるかどうか判別します。
if( IsInAmbientMode ) {
// アンビエントモードの時は、黒色で塗りつぶします。
canvas.DrawColor( Color.Black );
}
else {
// そうでない時は、背景画像を描画します。
canvas.DrawRect( 0, 0, canvas.Width, canvas.Height, backgroundPaint );
}
canvas.DrawText(
WatchfaceUtility.ConvertToDateTime( nowTime ).ToString( isAmbient ? "HH:mm" : "HH:mm:ss" ),
digitalTimeText.XOffset, digitalTimeText.YOffset, digitalTimeText.Paint
);
}
/// <summary>
/// ウォッチフェイスの表示・非表示が切り替わった時に実行します。
/// </summary>
/// <param name="visible">ウォッチフェイスの表示・非表示</param>
public override void OnVisibilityChanged( bool visible ) {
// ベースクラスのOnVisibilityChangedメソッドを実行します。
base.OnVisibilityChanged( visible );
#if DEBUG
if( Log.IsLoggable( logTag, LogPriority.Info ) ) {
Log.Info( logTag, $"{nameof( OnVisibilityChanged )}: Visible = {visible}" );
}
#endif
// ウォッチフェイスの表示・非表示を判別します。
if( visible ) {
if( timeZoneReceiver == null ) {
timeZoneReceiver = new ActionReservedBroadcastReceiver(
intent => {
// Time ( Android )
//nowTime.Clear( intent.GetStringExtra( "time-zone" ) );
//nowTime.SetToNow();
// Calendar ( Java )
nowTime = Java.Util.Calendar.GetInstance( Java.Util.TimeZone.GetTimeZone( intent.GetStringExtra( "time-zone" ) ) );
// DateTime ( C# )
//nowTime = DateTime.Now;
},
Intent.ActionTimezoneChanged
);
}
// タイムゾーン用のレシーバーを登録します。
timeZoneReceiver.IsRegistered = true;
// ウォッチフェイスが非表示の時にタイムゾーンが変化した場合のために、タイムゾーンを更新します。
// Time ( Android )
//nowTime.Clear( Java.Util.TimeZone.Default.ID );
//nowTime.SetToNow();
// Calendar ( Java )
nowTime = Java.Util.Calendar.GetInstance( Java.Util.TimeZone.Default );
// DateTime ( C# )
//nowTime = DateTime.Now;
}
else {
// タイムゾーン用のレシーバーを登録解除します。
timeZoneReceiver.IsRegistered = false;
}
// タイマーの動作を更新します。
UpdateTimer();
}
/// <summary>
/// タイマーの動作を更新します。
/// </summary>
private void UpdateTimer() {
#if DEBUG
if( Log.IsLoggable( logTag, LogPriority.Info ) ) {
Log.Info( logTag, $"{nameof( UpdateTimer )}" );
}
#endif
// UpdateTimeHandlerからMsgUpdateTimeメッセージを取り除きます。
updateTimeHandler.RemoveMessages( MessageUpdateTime );
// タイマーを動作させるかどうかを判別します。
if( ShouldTimerBeRunning ) {
// UpdateTimeHandlerにMsgUpdateTimeメッセージをセットします。
updateTimeHandler.SendEmptyMessage( MessageUpdateTime );
}
}
/// <summary>
/// タイマーを動作させるかどうかを表す値を取得します。
/// </summary>
private bool ShouldTimerBeRunning =>
IsVisible && !IsInAmbientMode;
}
}
}
#region バージョン情報
/**
* @file WatchFaceUtility.cs
* @brief Watch Faceの開発を便利にする、機能を提供します。
*
* @par バージョン Version
* 1.0.0
* @par 作成者 Author
* 智中ニア(Nia Tomonaka)
* @par コピーライト Copyright
* Copyright (C) 2016 Chronoir.net
* @par 作成日
* 2016/11/30
* @par 最終更新日
* 2016/11/30
* @par ライセンス Licence
* MIT Licence
* @par 連絡先 Contact
* @@nia_tn1012( https://twitter.com/nia_tn1012/ )
* @par ホームページ Homepage
* - http://chronoir.net/ (ホームページ)
*/
#endregion
using System;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.Support.V4.Content;
using Android.Support.Wearable.Watchface;
namespace Chronoir_net.Chronoface.Utility.Wearable.Watchface {
/// <summary>
/// Watch Faceの開発を便利にする、機能を提供します。
/// </summary>
static class WatchfaceUtility {
/// <summary>
/// ARGB値を格納した整数値を<see cref="Color"/>型に変換します。
/// </summary>
/// <param name="argb">ARGB値</param>
/// <returns>ARGB値と等価な<see cref="Color"/>型</returns>
/// <example>
/// <code>
/// <see cref="CanvasWatchFaceService"/> owner;
/// // ...
/// <see cref="Color"/> textColor = <see cref="WatchfaceUtility.ConvertARGBToColor"/>( <see cref="ContextCompat.GetColor"/>( , Resource.Color.text_color ) );
/// </code>
/// </example>
public static Color ConvertARGBToColor( int argb ) =>
Color.Argb( ( argb >> 24 ) & 0xFF, ( argb >> 16 ) & 0xFF, ( argb >> 8 ) & 0xFF, argb & 0xFF );
}
/// <summary>
/// ブロードキャストされた<see cref="Intent"/>オブジェクトを受け取り、あらかじめ設定した処理を行うレシーバークラスを定義します。
/// </summary>
public class BroadcastReceiverWithAction : BroadcastReceiver {
/// <summary>
/// <see cref="Intent"/>オブジェクトを受け取った時に実行するデリゲートを表します。
/// </summary>
private Action<Intent> receiver;
/// <summary>
/// <see cref="Intent"/>を識別するためのMIMEタイプを表します。
/// </summary>
private IntentFilter intentFilter;
/// <summary>
/// <see cref="BroadcastReceiver"/>が<see cref="Application.Context"/>に登録されているかどうかを表します。
/// </summary>
private bool isRegistered = false;
/// <summary>
/// <see cref="BroadcastReceiver"/>が<see cref="Application.Context"/>に登録状態を取得・設定( <see cref="Application.Context"/>への登録 or 登録解除)します。
/// </summary>
public bool IsRegistered {
get { return isRegistered; }
set {
if( value != isRegistered ) {
if( value ) {
Application.Context.RegisterReceiver( this, intentFilter );
}
else {
Application.Context.UnregisterReceiver( this );
}
isRegistered = value;
}
}
}
/// <summary>
/// デリゲートと<see cref="Intent"/>のMIMEタイプから、<see cref="BroadcastReceiverWithAction"/>クラスの新しいインスタンスを生成します。
/// </summary>
/// <param name="action"><see cref="Intent"/>オブジェクトを受け取った時に実行するデリゲート</param>
/// <param name="filter"><see cref="Intent"/>を識別するためのMIMEタイプ</param>
public BroadcastReceiverWithAction( Action<Intent> action, string filter = Intent.ActionDefault ) {
receiver = action;
intentFilter = new IntentFilter( filter );
}
/// <summary>
/// ブロードキャストされた<see cref="Intent"/>オブジェクトを受け取った時に実行します。
/// </summary>
/// <param name="context">レシーバーを登録している<see cref="Context"/>オブジェクト</param>
/// <param name="intent">ブロードキャストされた<see cref="Intent"/>オブジェクト</param>
public override void OnReceive( Context context, Intent intent ) {
receiver?.Invoke( intent );
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment