-
Notifications
You must be signed in to change notification settings - Fork 0
DeviceOrientationUitl
조익성 edited this page Nov 14, 2025
·
4 revisions
기기의 방향(방위각, azimuth)을 실시간으로 감지하고 관리하는 유틸리티 클래스입니다.
왜 remember를 사용하는가?
@Composable
fun rememberDeviceOrientation(): State<Double> {
val azimuthState = remember { mutableDoubleStateOf(0.0) }
val history = remember { mutableListOf<Double>() }
// ...
}-
컴포지션 생명주기 동안 상태 유지:
remember를 사용하여 리컴포지션이 발생해도 센서 데이터와 히스토리가 초기화되지 않음 - DisposableEffect와 결합: 센서 리스너가 한 번만 등록되고, 컴포넌트가 제거될 때 자동으로 정리됨
-
실시간 업데이트:
State<Double>타입으로 반환하여 Compose UI가 자동으로 재구성됨
라이프사이클과의 통합
val lifecycleObserver = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_RESUME -> {
// 화면이 활성화되면 센서 시작
sensorManager.registerListener(...)
}
Lifecycle.Event.ON_PAUSE -> {
// 백그라운드로 가면 센서 중지 (배터리 절약)
sensorManager.unregisterListener(...)
}
}
}센서 데이터는 본질적으로 노이즈가 많고 불안정합니다. 이를 해결하기 위해 3단계 평활화 전략을 사용합니다.
1단계: 저주파 필터 (Low-pass Filter)
private const val ALPHA = 0.05f
filteredAccReading[0] = ALPHA * event.values[0] + (1 - ALPHA) * filteredAccReading[0]
filteredMagReading[0] = ALPHA * event.values[0] + (1 - ALPHA) * filteredMagReading[0]- 목적: 센서의 순간적인 노이즈 제거
- 효과: 급격한 센서 값 변화를 95% 억제 (1 - ALPHA = 0.95)
- 원리: 이전 값의 가중치를 높여 급격한 변화를 완만하게 만듦
2단계: 이동 평균 (Moving Average)
val history = remember { mutableListOf<Double>() }
// 최근 5개 값의 평균
val averageAzimuth = history.average()
// 평균과의 차이가 3도 이상일 때만 업데이트
if (kotlin.math.abs(azimuthState.value - averageAzimuth) >= 3f) {
azimuthState.value = averageAzimuth
}- 목적: 시간에 따른 흔들림 완화
- 효과: 5개 데이터 포인트의 평균으로 부드러운 방향 전환
- 장점: 단순 평균보다 응답성 유지하면서도 안정적
3단계: 변화량 임계값 (Threshold)
if (kotlin.math.abs(azimuthState.value - averageAzimuth) >= 3f) {
azimuthState.value = averageAzimuth
}- 목적: 미세한 떨림으로 인한 불필요한 UI 업데이트 방지
- 효과: 3도 미만의 변화는 무시하여 성능 최적화
- 이점: Compose 리컴포지션 횟수 감소 → 배터리 절약
평활화 효과 비교
원본 센서 데이터: 45° → 48° → 44° → 50° → 46° → 52° → 45° → ...
1단계 필터 후: 45° → 45.15° → 45.09° → 45.34° → 45.38° → ...
2단계 평균 후: 45° → 45° → 45° → 45.72° → 45.89° → ...
3단계 임계값 적용: 45° → 45° → 45° → 45° → 45° → 48° (3도 차이 발생 시)
@Composable
fun rememberDeviceOrientation(): State<Double>- Composable 함수에서 직접 사용 가능: 일반적인 Compose State처럼 사용
- 자동 업데이트: 센서 값이 변경되면 UI가 자동으로 재구성
- 라이프사이클 인식: ON_RESUME/ON_PAUSE에 따라 자동으로 센서 등록/해제
| 항목 | 값 | 설명 |
|---|---|---|
| 센서 딜레이 | SENSOR_DELAY_GAME |
빠른 업데이트를 위한 게임 모드 |
| 저주파 필터 계수 | 0.05f |
센서 평활화 정도 조절 |
| 이동 평균 크기 | 5개 |
평활화를 위한 히스토리 크기 |
| 업데이트 임계값 | 3도 |
최소 변화량 |
| 출력 범위 | 0~360도 |
진북 기준 방위각 |
-
회전 행렬 계산
SensorManager.getRotationMatrix(rotationMatrix, null, accelerometerReading, magnetometerReading)
-
방향 각도 추출
SensorManager.getOrientation(rotationMatrix, orientationAngles) -
라디안 → 도 변환
val azimuth = (Math.toDegrees(orientationAngles[0]) + 360) % 360
-
평활화 및 업데이트
- 반올림 (소수점 첫째자리)
- 히스토리에 추가 및 평균 계산
- 임계값 확인 후 State 업데이트
- 센서가 없는 기기에서는 동작하지 않을 수 있음
- 자기장 간섭이 있는 환경에서는 정확도 저하 가능
- 실내에서는 GPS와 병행 사용 권장