Flutter製モバイルアプリで加速度センサーを用いて「ポケモンスリープ」みたいに画面を下に向けた状態を検知してみた
最近「ポケモンスリープ」(『Pokémon Sleep』)にハマっている筆者ですが、アプリの機能の一つで実装したことがない機能があったので調査してみました。
こちらのアプリで睡眠データを記録するためにはアプリを起動したまま枕元に置く必要があります。その際画面を下を向けておくと自動で画面が暗くなりますよね。こちらの機能をFlutterで再現してみたいと思います。
使用したパッケージ
今回は以下のパッケージでデバイスの加速度センサーを使用します。他にもジャイロセンサーや磁気センサーなどがあるようです。
パッケージに関する動画
main.dartの編集
下記のメソッドをmain()内に追加します。
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations(
[
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
],
);
void main() {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations(
[
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
],
);
runApp(const MyApp());
}
main.dartすべてのコード共有
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:sensors_plus/sensors_plus.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations(
[
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
],
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Sensors Demo',
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, this.title}) : super(key: key);
final String? title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<double>? _accelerometerValues;
final _streamSubscriptions = <StreamSubscription<dynamic>>[];
@override
Widget build(BuildContext context) {
final accelerometer =
_accelerometerValues?.map((double v) => v.toStringAsFixed(1)).toList();
return Scaffold(
appBar: AppBar(
title: const Text('下向き検知テスト'),
elevation: 4,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text('Accelerometer: $accelerometer'),
],
),
),
],
),
);
}
@override
void dispose() {
super.dispose();
for (final subscription in _streamSubscriptions) {
subscription.cancel();
}
}
@override
void initState() {
super.initState();
_streamSubscriptions.add(
accelerometerEvents.listen(
(AccelerometerEvent event) {
setState(() {
_accelerometerValues = <double>[event.x, event.y, event.z];
});
},
onError: (e) {
showDialog(
context: context,
builder: (context) {
return const AlertDialog(
title: Text("センサーが見つかりません"),
content: Text(
"使用中のデバイスではセンサーが搭載されていない可能性があります"),
);
});
},
cancelOnError: true,
),
);
}
}
main.dartの内容をこちらに書き換えて確認していきます。
まずは以下の画面が表示されることを確認してください。
まずはスマホを画面を上に向けた状態で平面の場所に置いた際にz軸方向に1G(約9.8m/s^2)の重力が働いていることがわかります。
加速度センサーを使用して画面の傾きを検知し、特定の傾きの際に画面上にアクションを起こす仕様に変更します。今回はz軸加速度の値を使用して導入します。
参考記事 マイナビBooks「iPhoneやiPadのモーションセンサの仕組みと役割」加速度と角速度 2023.07.28
z軸に-9.8m/s^2の値が観測されたときにほぼ水平に画面が下を向いているということがわかります。この値が検知された際に「画面は下向きです」という文字を表示するように使用を変更します。(条件はdouble.tryParse(accelerometer![2])! < -9.0としていますが値は調節してください)
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
double.tryParse(accelerometer![2])! < -9.0
? const Text('画面は下向きです')
: Container(),
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text('Accelerometer: $accelerometer'),
],
),
),
],
),
実験
スマホ画面を上に向けたり下に向けたりします。
実際の画面
加速度センサーを用いて画面が下を向いた時を検知できました。とりあえず今回は画面を暗くする機能は導入せずにテキストを表示するだけにしておきます。この記事が好評であれば画面を暗くする機能の紹介もしてみたいです。
まとめ
無事にポケモンスリープで使用されているような機能をFlutterで再現できました。この記事が好評でしたら、発展型の記事又は動画を作成してみたいと思います。
今回はテスト環境ではAndroidを使用しますが、使用するパッケージではiOSにも対応しているとのことですので、iOSでデバッグされる際はドキュメントからiOSの導入方法を参照いただけると幸いです。
補足
後から気づきましたが、ポケモンスリープでは加速度センサーに加えてカメラ横の物体検知センサーも併用で画面を暗くしているようです。電話をしている際に肌が画面に接触して誤タップを防ぐために受話器スピーカー近くに耳を近づけると画面が暗くなるアレです。
今回は加速度計のみで機能を再現しましたが、また別のセンサーを扱った記事を書いてみるのも良いと思いました。