🥜

Expo Motion Sensorsを使ってみた

2024/12/14に公開

Expo Motion Sensors Demo

このプロジェクトは、Expoを使用してモバイルデバイスの加速度センサーを視覚的に表示するデモアプリケーションです。デバイスの傾きを矢印で直感的に表示します。

完成品はこちら

機能

  • デバイスの傾きをリアルタイムで検出
  • 傾きの方向と強さを視覚的に表示(SVGを使用)
  • 傾きの角度と強度を数値で表示

必要な環境

  • Node.js (v20以上)
  • Expo CLI
  • iOS/Androidデバイス、またはシミュレータ

使用しているパッケージ

  • expo-sensors: デバイスの加速度センサーにアクセス
  • react-native-svg: 傾きを視覚的に表示するためのSVGコンポーネント

セットアップ手順

  1. プロジェクトをクローン
git clone https://github.com/sakurakotubaki/expo-motion-sensors
cd expo-motion-sensors
  1. 依存パッケージをインストール
bun install
  1. アプリを起動
bun run start
  1. ExpoGoアプリでQRコードをスキャン
    • iOSの場合:カメラアプリでQRコードをスキャン
    • Androidの場合:ExpoGoアプリでQRコードをスキャン

コードの解説

センサーの初期化と購読

const _subscribe = () => {
  setSubscription(
    Accelerometer.addListener(accelerometerData => {
      setData(accelerometerData);
    })
  );
  Accelerometer.setUpdateInterval(100);
};

加速度センサーのデータを100ミリ秒ごとに更新し、状態を更新します。

傾きの計算

const calculateAngle = () => {
  const angle = Math.atan2(y, x);
  return angle * (180 / Math.PI);
};

const calculateTiltStrength = () => {
  return Math.min(Math.sqrt(x * x + y * y), 1);
};
  • calculateAngle: x軸とy軸の値から傾きの角度を計算
  • calculateTiltStrength: 傾きの強さを0-1の範囲で計算

視覚的な表示

SVGを使用して、円と矢印で傾きを表示します:

  • 外側の円:基準となる円
  • 緑の矢印:デバイスの傾きの方向と強さを表示
  • 中心点:基準点

注意事項

  • 実機でのテストを推奨(シミュレータでは加速度センサーの値が正確に取得できない場合があります)
  • デバイスを平らな場所に置いた状態が初期状態となります

デモアプリの作成

bunx create-expo-app expo-motion-sensors -t expo-template-blank-typescript

add package:

bun add expo-sensors
bun add react-native-svg

権限の許可をする。

app.json
{
  "expo": {
    "name": "expo-motion-sensors",
    "slug": "expo-motion-sensors",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "userInterfaceStyle": "light",
    "newArchEnabled": true,
    "splash": {
      "image": "./assets/splash-icon.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "ios": {
      "supportsTablet": true
    },
    "android": {
      "adaptiveIcon": {
        "foregroundImage": "./assets/adaptive-icon.png",
        "backgroundColor": "#ffffff"
      }
    },
    "web": {
      "favicon": "./assets/favicon.png"
    },
    "plugins": [
      [
        "expo-sensors",
        {
          "motionPermission": " モーションセンサーへのアクセスを許可します。"
        }
      ]
    ]
  }
}

example

App.tsx
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View, Dimensions } from 'react-native';
import { Accelerometer } from 'expo-sensors';
import { useEffect, useState } from 'react';
import { Subscription } from 'expo-sensors/build/Pedometer';
import Svg, { Circle, Line, Path } from 'react-native-svg';

const CIRCLE_RADIUS = 120;
const INDICATOR_LENGTH = 100;

export default function App() {
  const [{ x, y, z }, setData] = useState({
    x: 0,
    y: 0,
    z: 0,
  });
  const [subscription, setSubscription] = useState<Subscription | null>(null);

  useEffect(() => {
    _subscribe();
    return () => _unsubscribe();
  }, []);

  const _subscribe = () => {
    setSubscription(
      Accelerometer.addListener(accelerometerData => {
        setData(accelerometerData);
      })
    );
    Accelerometer.setUpdateInterval(100);
  };

  const _unsubscribe = () => {
    subscription?.remove();
    setSubscription(null);
  };

  // 加速度からx,y軸の角度を計算
  const calculateAngle = () => {
    const angle = Math.atan2(y, x);
    return angle * (180 / Math.PI);
  };

  // 傾きの強さを計算(0-1の範囲)
  const calculateTiltStrength = () => {
    return Math.min(Math.sqrt(x * x + y * y), 1);
  };

  const renderCompass = () => {
    const centerX = CIRCLE_RADIUS;
    const centerY = CIRCLE_RADIUS;
    const angle = calculateAngle();
    const strength = calculateTiltStrength();
    
    // 傾きに基づいて線の終点を計算
    const endX = centerX + Math.cos(angle * Math.PI / 180) * INDICATOR_LENGTH * strength;
    const endY = centerY + Math.sin(angle * Math.PI / 180) * INDICATOR_LENGTH * strength;

    // 矢印の頭のサイズ
    const arrowSize = 15;
    
    // 傾き方向の矢印の頭を計算
    const arrowAngle = Math.atan2(endY - centerY, endX - centerX);
    const arrowPoint1X = endX - arrowSize * Math.cos(arrowAngle - Math.PI / 6);
    const arrowPoint1Y = endY - arrowSize * Math.sin(arrowAngle - Math.PI / 6);
    const arrowPoint2X = endX - arrowSize * Math.cos(arrowAngle + Math.PI / 6);
    const arrowPoint2Y = endY - arrowSize * Math.sin(arrowAngle + Math.PI / 6);

    return (
      <Svg height={CIRCLE_RADIUS * 2} width={CIRCLE_RADIUS * 2}>
        {/* 外側の円 */}
        <Circle
          cx={centerX}
          cy={centerY}
          r={CIRCLE_RADIUS - 10}
          stroke="#333"
          strokeWidth="2"
          fill="none"
        />
        
        {/* 中心から傾きを示す線と矢印 */}
        <Line
          x1={centerX}
          y1={centerY}
          x2={endX}
          y2={endY}
          stroke="#4CAF50"
          strokeWidth="3"
        />
        <Path
          d={`M ${endX} ${endY} L ${arrowPoint1X} ${arrowPoint1Y} L ${arrowPoint2X} ${arrowPoint2Y} Z`}
          fill="#4CAF50"
        />
        
        {/* 中心点 */}
        <Circle
          cx={centerX}
          cy={centerY}
          r={5}
          fill="#4CAF50"
        />
      </Svg>
    );
  };

  return (
    <View style={styles.container}>
      <View style={styles.compassContainer}>
        {renderCompass()}
      </View>
      <View style={styles.dataContainer}>
        <Text>傾き強度: {calculateTiltStrength().toFixed(2)}</Text>
        <Text>角度: {calculateAngle().toFixed(0)}°</Text>
      </View>
      <StatusBar style="auto" />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  compassContainer: {
    marginBottom: 20,
  },
  dataContainer: {
    alignItems: 'center',
  },
});

https://x.com/JBOY83062526/status/1867786903338262952

感想

加速度センサーなるものを試したかったがこれはではなかったかも😅
カメラで物体を撮影して速度測るアプリ作りたかったのですが、機械学習の技術が必要らしいのを後で知ったので断念しました。何かパッケージありそうだが。

Discussion