今回は Xamarin.Forms で回転の速度が取れるジャイロセンサー値を取得してみます
ソースコードの一式は下記にあります!
細かい実装などはこちらを参照ください
※ 順次改修していく予定なので、この記事の内容が現時点のソースより古い可能性があります
まずはデータのいれもの
/// <summary> /// iOS 用角速度データ /// </summary> class NativeBandGyroscopeReading : IBandGyroscopeReading { /// <summary> /// コンストラクタ /// </summary> /// <param name="data">センサーデータ</param> public NativeBandGyroscopeReading(Native.Sensors.BandSensorGyroscopeData data) { this.Timestamp = DateTime.Now; this.AngularVelocityX = data.X; this.AngularVelocityY = data.Y; this.AngularVelocityZ = data.Z; } /// <summary> /// 検出日時 /// </summary> public DateTimeOffset Timestamp { get; private set; } /// <summary> /// X 軸角速度 /// </summary> public double AngularVelocityX { get; private set; } /// <summary> /// Y 軸角速度 /// </summary> public double AngularVelocityY { get; private set; } /// <summary> /// Z 軸角速度 /// </summary> public double AngularVelocityZ { get; private set; } /// <summary> /// X 軸角加速度 /// </summary> [Obsolete("Acceleration by Gyroscope is not supported for iOS.")] public double AccelerationX { get; private set; } /// <summary> /// Y 軸角加速度 /// </summary> [Obsolete("Acceleration by Gyroscope is not supported for iOS.")] public double AccelerationY { get; private set; } /// <summary> /// Z 軸角加速度 /// </summary> [Obsolete("Acceleration by Gyroscope is not supported for iOS.")] public double AccelerationZ { get; private set; } }
Android は大丈夫なのに iOS の場合は角加速度が取得できないようです・・・仕方がないので Obsolete 属性で非推奨にしました
次にセンサークラス
/// <summary> /// iOS 用ジャイロセンサー /// </summary> public class NativeBandGyroscope : NativeBandSensorBase<IBandGyroscopeReading> { /// <summary> /// ジャイロセンサー /// </summary> private Native.Sensors.GyroscopeSensor sensor = null; /// <summary> /// センサー値変更イベント /// </summary> public override event EventHandler<BandSensorReadingEventArgs<IBandGyroscopeReading>> ReadingChanged; /// <summary> /// コンストラクタ /// </summary> /// <param name="manager">Band センサー管理クラス</param> public NativeBandGyroscope(Native.Sensors.IBandSensorManager manager) : base(manager) { this.sensor = Native.Sensors.BandSensorManagerExtensions.CreateGyroscopeSensor(manager); this.sensor.ReadingChanged += this.OnReadingChanged; } /// <summary> /// センサー値変更イベントハンドラ /// </summary> /// <param name="sender">イベント発行者</param> /// <param name="e">イベント引数</param> protected void OnReadingChanged(object sender, Native.Sensors.BandSensorDataEventArgs<Native.Sensors.BandSensorGyroscopeData> e) { if (this.ReadingChanged == null) { return; } this.ReadingChanged.Invoke( this, new BandSensorReadingEventArgs<IBandGyroscopeReading>(new NativeBandGyroscopeReading(e.SensorReading))); } /// <summary> /// センサー検知を開始する /// </summary> /// <returns>Task</returns> public override Task StartReadingsAsync() { return Task.Run(() => this.sensor.StartReadings()); } /// <summary> /// センサー検知を停止する /// </summary> /// <returns>Task</returns> public override Task StopReadingsAsync() { return Task.Run(() => this.sensor.StopReadings()); } }
NativeBandGyroscope は最低限の記述で済ませることができました
あとは画面側も対応していきます
/// <summary> /// センサー情報 ViewModel /// </summary> public class SensorReadingViewModel : BindableBase { ~ 中略 ~ /// <summary> /// センサー監視切替 /// </summary> /// <param name="detecting">センサー監視フラグ</param> /// <returns>Task</returns> private async Task ChangeDetectSensors(bool detecting) { if (detecting) { // 加速度センサーの検知開始 if (this.client.SensorManager.Accelerometer.IsSupported) { await this.client.SensorManager.Accelerometer.StartReadingsAsync(); this.client.SensorManager.Accelerometer.ReadingChanged += this.OnAccelerometerReadingChanged; } // ジャイロセンサーの検知開始 if (this.client.SensorManager.Gyroscope.IsSupported) { await this.client.SensorManager.Gyroscope.StartReadingsAsync(); this.client.SensorManager.Gyroscope.ReadingChanged += this.OnGyroscopeReadingChanged; } } else { // 加速度センサーの検知終了 if (this.client.SensorManager.Accelerometer.IsSupported) { await this.client.SensorManager.Accelerometer.StopReadingsAsync(); this.AccelerationX = 0d; this.AccelerationY = 0d; this.AccelerationZ = 0d; } // ジャイロセンサーの検知終了 if (this.client.SensorManager.Gyroscope.IsSupported) { await this.client.SensorManager.Gyroscope.StopReadingsAsync(); this.client.SensorManager.Gyroscope.ReadingChanged -= this.OnGyroscopeReadingChanged; this.AngularVelocityX = 0d; this.AngularVelocityY = 0d; this.AngularVelocityZ = 0d; this.GyroAccelerationX = 0d; this.GyroAccelerationY = 0d; this.GyroAccelerationZ = 0d; } } } ~ 中略 ~ /// <summary> /// 角速度変更イベントハンドラ /// </summary> /// <param name="sender">イベント発行者</param> /// <param name="e">イベント引数</param> private void OnGyroscopeReadingChanged(object sender, BandSensorReadingEventArgs<IBandGyroscopeReading> e) { if (e == null) { return; } this.AngularVelocityX = e.SensorReading.AngularVelocityX; this.AngularVelocityY = e.SensorReading.AngularVelocityY; this.AngularVelocityZ = e.SensorReading.AngularVelocityZ; this.GyroAccelerationX = e.SensorReading.AccelerationX; this.GyroAccelerationY = e.SensorReading.AccelerationY; this.GyroAccelerationZ = e.SensorReading.AccelerationZ; } }
ViewModel ではセンサー監視切替の処理を入れて、イベントハンドラ追加するだけで OK
最後に XAML
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:cv="clr-namespace:XamarinBandSample.Converters;assembly=XamarinBandSample" xmlns:t="clr-namespace:XamarinBandSample.Triggers;assembly=XamarinBandSample" xmlns:prismmvvm="clr-namespace:Prism.Mvvm;assembly=XamarinBandSample" prismmvvm:ViewModelLocator.AutoWireViewModel="true" x:Class="XamarinBandSample.Views.TopPage"> ~ 中略 ~ <!-- Sensor Info Pain--> <ScrollView IsVisible="{Binding ShowSensors}" Orientation="Vertical"> <Grid Padding="10" RowSpacing="10" ColumnSpacing="10" BindingContext="{Binding SensorReading}"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="100"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Label Text="Detecting" Grid.Column="0" Grid.Row="0" VerticalOptions="Center" FontSize="Medium"/> <Switch IsToggled="{Binding IsSensorDetecting}" Grid.Column="1" Grid.Row="0"> <Switch.Triggers> <EventTrigger Event="Toggled"> <t:InvokeCommandAction Command="{Binding ChangeDetectSensorsCommand}" CommandParameter="{Binding IsSensorDetecting}"/> </EventTrigger> </Switch.Triggers> </Switch> <StackLayout Orientation="Horizontal" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1" Spacing="5"> <Label Text="Accelometer:" FontSize="Medium"/> <StackLayout Orientation="Vertical" Padding="10,0,0,0" Spacing="10"> <Label Text="{Binding AccelerationX, StringFormat='x={0}'}" FontSize="Small"/> <Label Text="{Binding AccelerationY, StringFormat='y={0}'}" FontSize="Small"/> <Label Text="{Binding AccelerationZ, StringFormat='z={0}'}" FontSize="Small"/> </StackLayout> </StackLayout> <StackLayout Orientation="Horizontal" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="2" Spacing="5"> <Label Text="Gyroscope:" FontSize="Medium"/> <StackLayout Orientation="Vertical" Padding="10,0,0,0" Spacing="10"> <Label Text="{Binding AngularVelocityX, StringFormat='x={0}'}" FontSize="Small"/> <Label Text="{Binding AngularVelocityY, StringFormat='y={0}'}" FontSize="Small"/> <Label Text="{Binding AngularVelocityZ, StringFormat='z={0}'}" FontSize="Small"/> <Label Text="{Binding GyroAccelerationX, StringFormat='dx={0}'}" FontSize="Small"/> <Label Text="{Binding GyroAccelerationY, StringFormat='dy={0}'}" FontSize="Small"/> <Label Text="{Binding GyroAccelerationZ, StringFormat='dz={0}'}" FontSize="Small"/> </StackLayout> </StackLayout> </Grid> </ScrollView> ~ 中略 ~ </ContentPage>
ジャイロセンサーの値を表示領域を追加し、表示領域の縦幅が長くなってきたので、画面スクロールできるように ContentView から ScrollView に置き換えました
いつものおためし
やはり iOS だと角加速度は取れませんが角速度は取れるみたいですね