Xamarin逆引きTips
Plugins for Xamarinを使いこなすには?(Device Motion ― 磁気センサー/コンパス編)
デバイス固有の機能に簡単にアクセスできるPlugins for Xamarinの一つ、「Device Motion Plugin」プラグインを紹介。今回は、Magnetometer(磁気)センサー、Compass(コンパス)の機能を使う方法を説明する。
前回は「Device Motion Plugin」で使えるセンサー機能のうち、Accelerometer(加速度)センサー、Gyroscope(ジャイロスコープ)センサーを紹介した。今回は引き続きMagnetometer(磁気)センサー、Compass(コンパス)を紹介する。
- ※本連載ではXamarin for Visual Studioの最新版を使用している。2016年3月末現在の最新版4.0.1.147では、[Blank App (Xamarin.Forms Portable)]テンプレートを使って新規プロジェクトを作成すると、PCL/Android/iOS/UWP/Windows 8.1(ストアアプリ)/Windows Phone 8.1の6つのプロジェクトが作成されるが、本連載ではAndroid/iOS/UWPでのスクリーンショットを使用している。ぜひ最新版にアップデートしてUWPアプリの作成も試してもらいたい。
Device Motion Pluginのインストール
Xamarin.FormsのプロジェクトをVisual StudioもしくはXamarin Studioで開き、Visual Studioの場合は[ソリューション エクスプローラー]で一番上のソリューション項目を右クリックして(表示されるコンテキストメニューから)[ソリューションの NuGet パッケージの管理]をクリックする(Xamarin Studioの場合は[ソリューション]ビューの各プロジェクト項目からNuGetパッケージを追加できる)。
これにより表示されたページの[参照]タブで「Xam.Plugin.DeviceMotion」を検索し、Device Motionを全てのプロジェクトに対して[インストール]する。
Device Motion Pluginの概要
前回の繰り返しになるが、Device Motion Pluginの概要だ。CrossDeviceMotion
クラス(DeviceMotion.Plugin
名前空間)のCurrent
プロパティを使用して、Accelerometer(加速度)センサー、Gyroscope(ジャイロスコープ)センサー、Magnetometer(磁気)センサー、Compass(コンパス)にアクセスできる。
センサーの種類を表すMotionSensorType
列挙体にAccelerometer
、Gyroscope
、Magnetometer
、Compass
が含まれており、センサーの応答間隔を表すMotionSensorDelay
列挙体には以下が含まれている。単位はミリ秒だ。
Fastest
= 0,Game
= 20,Ui
= 60,Default
= 200
戻り値の型であるMotionSensorValueType
列挙体はSingle
とVector
があり、Accelerometer
、Gyroscope
、Magnetometer
がVector
値を、Compass
がSingle
値を返す。
APIの詳細はGitHubのReadme(英語)を参照してほしい。
今回は「Magnetometer」と「Compass」を紹介する。
Magnetometer(磁気)センサー
端末に組み込まれた磁気センサーに磁石が近づいた際に大きく反応するセンサーだ(もちろん地磁気にも反応する)。iPhoneやiPadは機種によって磁気センサーの位置が違い、筆者の手持ちの端末で確認したところ、iPhone 5sは右上に、iPhone 6 Plusは左上に、iPad Mini 2は右中央に磁気センサーの存在が確認できた。Android端末のNexus 5とSH-01Fは左上に、GL07Sは中央上にセンサーを確認できた。
MagnetometerセンサーもAccelerometerと同様に、次のコードでX/Y/Z軸方向の磁力を取得できるが、近づける磁石の極や磁力によって取得できる値のS極/N極や大きさが変わる(※そのため、この数値だけを使って処理を分岐するなどは難しいだろう。通常は、他のセンサーの値と組み合わせて、例えば後述するコンパスのような方位計算やデバイスの位置計算などを実現する場合が多い)。
using DeviceMotion.Plugin;
using DeviceMotion.Plugin.Abstractions;
using System.Diagnostics;
……省略……
IDeviceMotion motion = CrossDeviceMotion.Current; // <- 1
motion.Start(MotionSensorType.Magnetometer, MotionSensorDelay.Default); // <- 2
motion.SensorValueChanged += (object sender, SensorValueChangedEventArgs e) => // <- 3
{
//Device.BeginInvokeOnMainThread(() =>
//{
Debug.WriteLine(((MotionVector)e.Value).X); // <- 4
Debug.WriteLine(((MotionVector)e.Value).Y);
Debug.WriteLine(((MotionVector)e.Value).Z);
//}); // <- 5
};
|
Accelerometerと同じ注意点となるが、念のため再掲する。
1 Current
プロパティでCrossDeviceMotion
クラスのインスタンスを取得する。そのインスタンスをメンバー変数に代入して保持しておかないと、ガベージコレクションによりオブジェクトが自動削除されることがあるので注意してほしい。
2 Start
メソッドに、引数としてMotionSensorType
(センサーの種類)とMotionSensorDelay
(センサー値の取得間隔)を与えて、センサー値の取得を開始する。
3 必要に応じてIsActive(MotionSensorType sensorType)
メソッドでセンサーがアクティブなことを確認し、SensorValueChanged
イベントのイベントハンドラーで、磁力が変化した際の処理を記述すればよいだろう。
4 SensorValueChanged
イベントハンドラーの引数として渡されたSensorValueChangedEventArgs
オブジェクトのValue
プロパティ値は、2で指定したMotionSensorType
値に基づき、適切な型にキャストしてほしい。リスト1では、X/Y/Zの値を取得するためにMotionVector
型にキャストしている。
5 センサー値の取得は、バックグラウンドのスレッドで動作している。そのため、Viewの値を変更する場合は、その処理をDevice.BeginInvokeOnMainThread(() =>{ }
でくくる必要があるので注意してほしい(※上記のコードでは、Viewが動作するメインスレッドにアクセスする必要がないのでコメントアウトしている)。BeginInvokeOnMainThread
メソッドは、名前のとおりだが、メインスレッド上のコードを呼び出すためのものである。
注意点
磁気センサーのAPIは現時点ではWindows Store(つまりUWPアプリ)とWindows Phone 8(Silverlight)では動作しない。
Device Motion Pluginを使えば上記のように簡単に磁気センサーを取得できるが、これを使わずに自力で取得する方法も説明しておこう。
Xamarin.iOSでのMagnetometerの取得方法
ざっくりとした説明はAccelerometer(加速度)センサー/Gyroscope(ジャイロスコープ)センサーの説明に記載したのでそちらを参照してほしい。
CMMotionManager
オブジェクトのStartMagnetometerUpdates
メソッドでセンサー値の取得を開始するとよい。CMMagnetometerData
オブジェクトのMagneticField.X
/MagneticField.Y
/MagneticField.Z
プロパティで各値を取得できる。
Xamarin.AndroidでのMagnetometerの取得方法
こちらも、ざっくりとした説明はAccelerometer(加速度)センサー/Gyroscope(ジャイロスコープ)センサーの説明に記載したのでそちらを参照してほしい。
SensorManager
オブジェクトのRegisterListener
メソッドを使用してセンサー監視を登録する。その第2引数に指定するSensor
オブジェクトは、Sensor.GetDefaultSensor
メソッドで取得した値である。このGetDefaultSensor
メソッドの引数には、SensorType
列挙体の(Accelerometer
やGyroscope
の代わりに)MagneticField
を指定するとよい。
Compass(コンパス)
名前そのものでコンパスを使用する。
コンパスのセンサー値を取得するコードは、先ほどのMagnetometerの場合と比較して、MotionSensorType
列挙体のCompass
値を指定する点とSensorValueChanged
イベントハンドラーの引数として渡されたSensorValueChangedEventArgs
オブジェクトのValue
プロパティ値が(MotionVector
型ではなく)MotionValue
型となる点が異なる。キャストも不要でDebug.WriteLine(a.Value.Value);
などのコードで確認できる*1。
- *1 なお、
a.Value.Value
とValueが二重になっているのは、単にa.Value
では自動的にMotionValue
オブジェクトのToString
メソッドが呼ばれて「Value = XXX」という値を取得してしまうため。Value.Value
プロパティ値を参照することで数値を取得できる。
なお、iOSでコンパスセンサーを使用する際は、OSが誤差を自動的に吸収してくれる。誤差があると判断された場合は以下のような画面が表示されるのはOS標準のコンパスアプリと同様だ。
コンパス値についても、Device Motion Pluginを使わずに自力で取得する方法を説明しておこう。
Xamarin.iOSでのCompassの取得方法
iOSでコンパスを使用するにはCLLocationManager
オブジェクトのStartUpdatingHeading
メソッドを使用してセンサー値の取得を開始する。Heading
オブジェクトのMagneticHeading
プロパティで「磁北」*2のdouble
値を取得でき、TrueHeading
プロパティで「真北」*2のdouble
値を取得できる。取得を停止するにはStopUpdateHeading
メソッドを使用する。例えば次のようなコードで取得できる。
using CoreLocation;
locationManager = new CLLocationManager();
locationManager.StartUpdatingHeading();
locationManager.UpdatedHeading += (object s, CLHeadingUpdatedEventArgs a) =>
{
AngleLabel.Text = string.Format($"{locationManager.Heading.MagneticHeading:N0}°");
};
|
ちなみに、前述のDevice Motion Pluginのコンパスで取得される値は、「真北」を基準にした方位角(=TrueHeading
プロパティ値)となり、このコードの実行結果の値とは微妙に異なる可能性がある。
CLHeading
クラスの詳細は「CLHeading Class - Xamarin(英語)」を参照してほしい。
- *2 「磁北」(=方位磁石のN極が指す方向)と「真北」(=北極点を指す方位)の値は微妙にずれている(=磁気偏角)。両者の違いは「国土地理院 地磁気測量:地磁気を知る」を参考にしてほしい。
Xamarin.AndroidでのCompassの取得方法
Androidでは、コンパスセンサーが存在しないため、加速度センサーと磁気センサーを使用して(磁北を基準にした)方位角を算出する必要がある。
ざっくり説明すると、まずSensorManager
クラスのRegisterListener(ISensorEventListener, Sensor, SensorDelay, Int32, Handler) : Boolean
メソッドで第2引数にSensorType.Accelerometer
列挙体値とSensorType.MagneticField
列挙体値をそれぞれ指定して加速度センサーと磁気センサーの取得を開始する。その後OnSensorChanged
イベントハンドラーで、SensorManager
クラスのGetRotationMatrix(Single[], Single[], Single[], Single[]) : Boolean
メソッドで回転行列を求め、RemapCoordinateSystem(Single[], Android.Hardware.Axis, Android.Hardware.Axis, Single[]) : Boolean
メソッドで端末の画面の向きに合わせて変換行列を求め、GetOrientation(Single[], Single[]) : Single[]
メソッドで方位角と傾きを求める*3。センサーの取得停止はUnregisterListener(ISensorEventListener)
で行う。例えば次のようなコードで取得できる。
switch (e.Sensor.Type)
{
case SensorType.Accelerometer:
accelerometerValue = new float[3];
accelerometerValue = e.Values.ToArray();
break;
case SensorType.MagneticField:
magneticFieldValue = new float[3];
magneticFieldValue = e.Values.ToArray();
break;
default:
break;
}
if (magneticFieldValue != null && accelerometerValue != null)
{
float[] Rotate1 = new float[16];
float[] Rotate2 = new float[16];
float[] Inclination = new float[16];
float[] val = new float[3];
// 1 加速度センサーと磁気センサーの値から回転行列を求める
SensorManager.GetRotationMatrix(Rotate1, Inclination, accelerometerValue, magneticFieldValue);
// 2 端末の画面設定に合わせる変換行列を求める(以下は、縦表示で画面を上にした場合)
SensorManager.RemapCoordinateSystem(Rotate1, Android.Hardware.Axis.X, Android.Hardware.Axis.Y, Rotate2);
// 3 方位角および傾きを求める
SensorManager.GetOrientation(Rotate2, val);
// 4 ラジアンを角度に変換
for (var i = 0; i < 3; i++)
{
val[i] = (float)(val[i] * 180 / Math.PI);
}
System.Diagnostics.Debug.WriteLine("{0:F0}°", (val[0] < 0) ? val[0] + 360 : val[0]);
}
|
ちなみに、前述のDevice Motion Pluginのコンパスで取得される値も、同じく「磁北」を基準にした方位角となっているが、執筆時点のソースコードを確認する限り、非推奨のSensor.TYPE_ORIENTATION
を使用しており、結果の値に違いが出る可能性がある(※ちなみにAndroidにおける方位角は、このコードのように、GetRotationMatrix()
/RemapCoordinateSystem()
/GetOrientation()
メソッドを組み合わせて算出することが推奨されている)。ほとんどの場合、この磁方位角で事足りるだろうが、真方位角を取得したい場合には、その計算のために磁気偏角(declination)を求める必要がある。詳しいコード内容は割愛するが、磁気偏角はGeomagneticField
クラス(Android.Hardware
名前空間)のDeclination
プロパティを使って取得でき、磁北+磁気偏角で真北の方位角が得られる。
SensorManager
APIについては「SensorManager Class - Xamarin(英語)」を参照してほしい。
- *3 詳しくは、SIN氏のブログエントリー「Xamarin.Android 方位の取得2(磁気センサー/加速度センサー) - SIN@SAPPOROWORKSの覚書」を参照されたい。
まとめ
Device Motion Pluginを使用することで、iOS/Android/UWP/Windows Phoneなどで簡単に各種センサーの機能を使用できる。皆さんのアプリに、タップ以外のモーションによる操作を追加したい場合にぜひ活用してほしい。
※以下では、本稿の前後を合わせて5回分(第62回~第66回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
62. Plugins for Xamarinを使いこなすには?(ファイルシステム編)
デバイス固有の機能に簡単にアクセスできるPlugins for Xamarinを複数回にわたって紹介していく。今回は、簡単にファイルの入出力が行える「PCL Storage」プラグインを説明する。
63. Plugins for Xamarinを使いこなすには?(GPS編)
デバイス固有の機能に簡単にアクセスできるPlugins for Xamarinを複数回にわたって紹介していく。今回は、GPSの機能を使える「Geolocator」プラグインを説明する。
64. Plugins for Xamarinを使いこなすには?(Device Motion ― 加速度センサー/ジャイロスコープセンサー編)
デバイス固有の機能に簡単にアクセスできるPlugins for Xamarinの一つ、「Device Motion Plugin」プラグインを紹介。今回は、Accelerometer(加速度)センサー、Gyroscope(ジャイロスコープ)センサーの機能を使う方法を説明する。
65. 【現在、表示中】≫ Plugins for Xamarinを使いこなすには?(Device Motion ― 磁気センサー/コンパス編)
デバイス固有の機能に簡単にアクセスできるPlugins for Xamarinの一つ、「Device Motion Plugin」プラグインを紹介。今回は、Magnetometer(磁気)センサー、Compass(コンパス)の機能を使う方法を説明する。
66. Xamarin Workbooksを使用するには?(REPL&リッチテキスト編)
C#のREPLアプリとして対話型でコード実行ができるだけでなく、そのコード実行をコンテンツに含めたリッチ文書が作成できるXamarin Workbooksの基本的な使い方を解説。