flutter

[플러터] Flutter / 기울기, 움직임(모션) 감지 센서(자이로 센서) 구현하기 (패키지 사용 X)

ch5c 2025. 2. 22. 16:10

오늘은 기울기 감지 센서를 사용해 볼 것이다. 허나 플러그인을 사용하지 않고 코틀린 코드와 연결해서 구현을 해볼 것이다.

바로 만들어 보자!

 

전체 코드(Kotlin)

package com.example.your_packge

import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.os.Bundle
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity : FlutterActivity(), SensorEventListener {

    private val methodChannel = "com.example.your_packge"
    private lateinit var sensorManager: SensorManager
    private var accelerometer: Sensor? = null
    private var tiltData: List<Float> = listOf(0f, 0f, 0f)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
        accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
    }

    override fun onResume() {
        super.onResume()
        accelerometer?.let {
            sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_UI)
        }
    }

    override fun onPause() {
        super.onPause()
        sensorManager.unregisterListener(this)
    }

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(
            flutterEngine.dartExecutor.binaryMessenger,
            methodChannel
        ).setMethodCallHandler { call, result ->
            if (call.method == "getTilt") {
                result.success(tiltData.map { it.toDouble() })
            } else {
                result.notImplemented()
            }
        }
    }

    override fun onSensorChanged(event: SensorEvent?) {
        if (event?.sensor?.type == Sensor.TYPE_ACCELEROMETER) {
            tiltData = listOf(event.values[0], event.values[1], event.values[2])
        }
    }

    override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
}


하나하나 순차적으로 알아보자.

class MainActivity : FlutterActivity(), SensorEventListener {

일단 센서 이벤트를 감지하기 위해선 위와 같이 SensorEventListener를 넣어 줘야 한다.

그다음엔 사용할 친구들을 선언해 준다.

private val methodChannel = "com.example.your_packge" // Flutter와 통신할 MethodChannel의 이름 정의
private lateinit var sensorManager: SensorManager // 센서를 관리할 SensorManager 선언 (나중에 초기화)
private var accelerometer: Sensor? = null // 가속도 센서 객체
private var tiltData: List<Float> = listOf(0f, 0f, 0f) // 기울기 데이터 초기값 (x, y, z)


이제 함수들을 만들어주자.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager // 시스템에서 SensorManager 가져오기
    accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) // 가속도 센서 가져오기
}

override fun onResume() {
    super.onResume()
    accelerometer?.let {
        sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_UI)
        // 센서 리스너 등록 (this = 현재 액티비티, it = accelerometer, 센서 업데이트 속도 설정)
    }
}

override fun onPause() {
    super.onPause()
    sensorManager.unregisterListener(this) // 센서 리스너 해제 (메모리 누수 방지)
}

 

onCreate()에 작성한 코드는 안드로이드 디벨로퍼에서 가져왔다.

https://developer.android.com/develop/sensors-and-location/sensors/sensors_motion#kotlin

 

움직임 감지 센서  |  Sensors and location  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 움직임 감지 센서 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Android 플랫폼은 기기의 동작을 모니

developer.android.com

 

이제 가장 중요한 플러터랑 연결하는 코드를 작성해 주자.

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {  // Flutter 엔진을 설정하는 함수
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(
            flutterEngine.dartExecutor.binaryMessenger,  // Dart에서 보낸 메시지를 받는 메신저
            methodChannel  // MethodChannel의 이름 설정 (Flutter와 동일해야 함)
        ).setMethodCallHandler { call, result ->  // Flutter에서 보낸 메시지 처리
            if (call.method == "getTilt") {  // "getTilt" 메서드가 호출되었을 경우
                result.success(tiltData.map { it.toDouble() })  
                // Float 리스트를 Double 리스트로 변환하여 Flutter로 반환
            } else {
                result.notImplemented()  // 지원하지 않는 메서드일 경우 처리
            }
        }
    }

그리고 없어서는 안 될 코드들까지

override fun onSensorChanged(event: SensorEvent?) { // 센서 값이 변경될 때 호출됨
    if (event?.sensor?.type == Sensor.TYPE_ACCELEROMETER) { // 가속도 센서 이벤트인지 확인
        tiltData = listOf(event.values[0], event.values[1], event.values[2])
        // 센서 데이터를 리스트에 저장 (x, y, z 방향 값)
    }
}

override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {	
    // 센서의 정확도가 변경될 때 호출되지만 여기서는 사용하지 않음 (비워둠)
}

 

요약하자면

  1. 센서 설정
    • sensorManager를 사용하여 가속도 센서(TYPE_ACCELEROMETER)를 가져옴.
    • onResume()에서 센서 리스너 등록 → 센서 값 변경 감지.
    • onPause()에서 센서 리스너 해제 → 메모리 누수 방지.
  2. Flutter와의 통신 (MethodChannel)
    • configureFlutterEngine()에서 Flutter와 MethodChannel 설정.
    • Flutter에서 "getTilt"을 호출하면 tiltData 값을 반환.
  3. 센서 데이터 처리
    • onSensorChanged()에서 기울기 값(x, y, z)을 실시간 업데이트.
    • 값이 변경되면 tiltData에 저장하고, Flutter에서 요청 시 전달.

이제 getTilt를 호출하면 Flutter에서 정상적으로 센서 데이터를 받을 수 있다

 

자 이러면 이제 코틀린 코드는 끝났다. dart 코드를 작성해 주자. (gpt로 요약한 건 안 비밀)

 

전체코드(Dart)

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:async';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: TiltScreen(),
    );
  }
}

class TiltScreen extends StatefulWidget {
  const TiltScreen({super.key});

  @override
  State<TiltScreen> createState() => _TiltScreenState();
}

class _TiltScreenState extends State<TiltScreen> {

  Future<List<double>> getTiltSensor() async {
    final List<dynamic> result = await MethodChannel('com.example.your_packge').invokeMethod('getTilt');
    return result.map((e) => e as double).toList();
  }

  List<double> _tilt = [0, 0, 0];
  late Timer _timer;

  @override
  void initState() {
    super.initState();
    _timer = Timer.periodic(const Duration(milliseconds: 100), (timer) async {
      List<double> tilt = await getTiltSensor();
      setState(() {
        _tilt = tilt;
      });
    });
  }

  @override
  void dispose() {
    _timer.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Tilt Sensor")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text("X: ${_tilt[0].toStringAsFixed(2)}"),
            Text("Y: ${_tilt[1].toStringAsFixed(2)}"),
            Text("Z: ${_tilt[2].toStringAsFixed(2)}"),
            SizedBox(
              width: MediaQuery.sizeOf(context).width,
                height: 400,
                child: CustomPaint(painter: SlopePainter(_tilt[0] * 10),))
          ],
        ),
      ),
    );
  }
}


class SlopePainter extends CustomPainter {
  final double slope;

  SlopePainter(this.slope);

  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint()
      ..color = Colors.blue
      ..style = PaintingStyle.fill;

    double leftHeight = (size.height / 2) + slope;
    double rightHeight = (size.height / 2) - slope;

    Path path = Path()
      ..moveTo(0, leftHeight)
      ..lineTo(size.width, rightHeight)
      ..lineTo(size.width, size.height)
      ..lineTo(0, size.height)
      ..close();

    canvas.drawPath(path, paint);
  }


  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}


다트 코드에서는 간단하게 로직만 알아보자. (커스텀 페인트는 다른 영역이기 때문에)

Future<List<double>> getTiltSensor() async {
  final List<dynamic> result = await MethodChannel('com.example.your_packge').invokeMethod('getTilt');
  return result.map((e) => e as double).toList();
}

 

당연하게도 필요한 메서드 채널 코드이다. 여기서는 안드로이드에서 'getTilt' 메서드를 호출하고 받은 데이터를 List<double> 타입으로 변환시켜 준다.

List<double> _tilt = [0, 0, 0];
late Timer _timer;

센서 데이터를 저장할 리스트 (초기값 [0, 0, 0])와 주기적으로 데이터 업데이트를 위한 타이머 선언.

@override
void initState() {
  super.initState();
  _timer = Timer.periodic(const Duration(milliseconds: 100), (timer) async {
  // 100ms마다 반복 실행 (0.1초 간격)
    List<double> tilt = await getTiltSensor(); // 센서 데이터 가져오기
    setState(() {
      _tilt = tilt; // 가져온 데이터를 _tilt 변수에 업데이트
    });
  });
}

@override
void dispose() {
  _timer.cancel(); // 화면이 사라질 때 타이머 중지 (메모리 누수 방지)
  super.dispose();
}

 

이렇게 iniState() 와 dispose () 까지 작성했다면 로직은 끝났다. 그렇게 받아온 값은 이제 알아서 잘 사용하면 되는데

나는 화면의 기울기에 따라 경사가 변하도록 만들었다.

이렇게 안드로이드 코드와 연결해서 외부 패키지 사용 없이 기울기감지를 구현해 봤다. 도움이 되었길 바라며 포스팅 마치겠다.