Compose Sensor , 기기의 방향을 계산하는 방 - YangJJune/U-Compass GitHub Wiki
움직임 감지 센서
- 세 축(x,y,z) 를 따라 가속력과 회전력을 측정
- 가속도계, 중력 센서, 자이로스코프, 회전 벡터
환경 센서
- 주변 기온과 같은 환경 매개변수 측정
- 압력, 조명, 습도, 기압계, 광도계
위치 센서
- 기기의 물리적 위치 측정
- 방향 센서, 자기
SensorManager
- Sensor 서비스의 인스턴스를 생성 가능하게 해주는 클래스.
val sensorManager =
getSystemService(LocalContext.current, SensorManager::class.java) as SensorManager
Sensor
- SensorManager 를 통해 만들 수 있는 특정 센서의 인스턴스.
val sensor : Sensor? = sensorManager.getDefaultSensor(Sensor.SOME_SENSOR)
SensorEvent
- 시스템에서는 이 클래스를 사용해서 event 인스턴스를 생성함. 시스템 내부적으로 해당 Sensor 에서의 이벤트가 감지하면
SensorEvent
인스턴스를 생성
SensorEventListener
- 센서 값이 변경되거나 센서 정확도가 변경될 때
SensorEvent
를 수신하는 callback 메서드를 포함하는 인터페이스
class SensorActivity: Activity(), SensorEventListener() {
// ...
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
// Do something here if sensor accuracy changes.
}
override fun onSensorChanged(event: SensorEvent) {
// The light sensor returns a single value.
// Many sensors return 3 values, one for each axis.
val lux = event.values[0]
// Do something with this sensor value.
}
// ...
}
기기마다, Android 버전마다 존재하는 센서의 종류가 다를 수 있습니다.
해당 기기에 어떤 Sensor 가 존재하는 지 확인하기 위해서는 시스템 서비스에 접근해서 SensorManager
인스턴스를 만들어야 합니다.
private lateinit var sensorManager: SensorManager
...
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
그 다음으로 모든 센서의 종류를 가져오려면 다음과 같이 작성하면 됩니다.
val deviceSensors: List<Sensor> = sensorManager.getSensorList(Sensor.TYPE_ALL)
만약 특정 유형의 센서를 가져오려면 [센서 개요 링크](https://developer.android.com/develop/sensors-and-location/sensors/sensors_overview?hl=ko) 를 참고해 어떤 Sensor Type 이 있는 지 확인하고, getDefaultSensor(Sensor.SOME_SENSOR)
를 호출하면 됩니다.
private lateinit var sensorManager: SensorManager
...
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
val sensor : Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)
if (sensor != null) {
// Success! There's a magnetometer.
} else {
// Failure! No magnetometer.
}
Sensor 데이터를 모니터링하려면 SensorEventListener
인터페이스를 통해 오버라이딩되는 두 개의 callback 메서드를 구현하면 됩니다.
-
onAccuracyChanged()
-
Sensor
객체의 정확도가 변경되었을 때 호출되는 메소드.
// SensorManager.class public static final int SENSOR_STATUS_ACCURACY_HIGH = 3; public static final int SENSOR_STATUS_ACCURACY_LOW = 1; public static final int SENSOR_STATUS_ACCURACY_MEDIUM = 2; public static final int SENSOR_STATUS_NO_CONTACT = -1; public static final int SENSOR_STATUS_UNRELIABLE = 0;
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) { // Do something here if sensor accuracy changes. }
-
-
onSensorChanged()
-
SensorEvent
가 발생했을 때 호출되는 메소드.
// SensorEvent.class public int accuracy; public boolean firstEventAfterDiscontinuity; public Sensor sensor; public long timestamp; public final float[] values = null;
override fun onSensorChanged(event: SensorEvent) { // The light sensor returns a single value. // Many sensors return 3 values, one for each axis. val lux = event.values[0] // Do something with this sensor value. }
-
-
센서, 방위각 선언
val context = LocalContext.current val sensorManager = remember { context.getSystemService(Context.SENSOR_SERVICE) as SensorManager } val accelerometer = // 가속도계 센서 remember { sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) } val magnetometer = // 자기장 센서 remember { sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) } var azimuthDeg by remember { mutableFloatStateOf(0f) } // 방위각 상태 값 (0~360)
-
센서 측정값을 담을 배열 선언
val accelerometerReading = FloatArray(3) val magnetometerReading = FloatArray(3) val rotationMatrix = FloatArray(9) val orientationAngles = FloatArray(3)
-
accelerometerReading
: 가속도계 센서의 측정값을 저장할 배열 -
magentometerReading
: 자기장 센서의 측정값을 저장할 배열 -
rotationMatrix
: 가속도 배열, 자기장 배열 값을 통해 -
orientationAngles
: x,y,-z 축을 중심으로 한 회전 각도가 저장되는 배열
-
-
센서 리스너 등록, 정의
-
센서 값의 변화에 따라 배열에 해당 값을 저장함.
when (event.sensor.type) { Sensor.TYPE_ACCELEROMETER -> { // 가속도계 측정값 복사 accelerometerReading[0] = event.values[0] accelerometerReading[1] = event.values[1] accelerometerReading[2] = event.values[2] } Sensor.TYPE_MAGNETIC_FIELD -> { // 자기장 센서 측정값 복사 magnetometerReading[0] = event.values[0] magnetometerReading[1] = event.values[1] magnetometerReading[2] = event.values[2] } }
-
두 센서 값(가속도, 자기장)을 통해 회전 행렬 계산
// 두 센서 값으로부터 회전 행렬과 방위각 계산 val success = SensorManager.getRotationMatrix( rotationMatrix, null, accelerometerReading, magnetometerReading )
-
방위각 계산
if (success) { SensorManager.getOrientation(rotationMatrix, orientationAngles) // orientationAngles[0] 이 방위각(rad)이며, [1] Pitch, [2] Roll입니다. val azimuthRad = orientationAngles[0] var azimuthDegrees = Math.toDegrees(azimuthRad.toDouble()).toFloat() if (azimuthDegrees < 0) { azimuthDegrees += 360f // 음수 값을 0~360도로 변환 } azimuthDeg = azimuthDegrees // 상태 업데이트 }
-
센서 리스너 등록, 해제
// 센서 리스너 등록 (실행 속도: NORMAL) sensorManager.registerListener( sensorListener, accelerometer, SensorManager.SENSOR_DELAY_NORMAL ) sensorManager.registerListener( sensorListener, magnetometer, SensorManager.SENSOR_DELAY_NORMAL ) // 컴포저블이 사라질 때 센서 해제 onDispose { sensorManager.unregisterListener(sensorListener) }
-
-
방위각을 통해 방향을 화살표로 표시
// 현재 기기 방위각을 센서로부터 얻음 (항상 0~360도 값 유지) val azimuth = rememberAzimuth() // 목표 방향 대비 기기 방향의 차이각 계산 val rotationAngle = (bearingToTarget - azimuth + 360f) % 360f Image( imageVector = Icons.Filled.KeyboardArrowUp, // 화살표 아이콘 (기본이 위쪽을 가리키는 그림) contentDescription = "목표 방향 화살표", modifier = modifier .size(100.dp) .rotate(rotationAngle) )
@Composable
fun rememberAzimuth(): Float {
val context = LocalContext.current
// SensorManager 및 센서 객체 가져오기
val sensorManager =
remember { context.getSystemService(Context.SENSOR_SERVICE) as SensorManager }
val accelerometer =
remember { sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) } // 가속도계 센서
val magnetometer =
remember { sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) } // 자기장 센서
var azimuthDeg by remember { mutableFloatStateOf(0f) } // 방위각 상태 값 (0~360)
DisposableEffect(Unit) {
// 센서 측정값을 담을 배열
val accelerometerReading = FloatArray(3)
val magnetometerReading = FloatArray(3)
val rotationMatrix = FloatArray(9)
val orientationAngles = FloatArray(3)
// 센서 이벤트 리스너 정의
val sensorListener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent) {
when (event.sensor.type) {
Sensor.TYPE_ACCELEROMETER -> {
// 가속도계 측정값 복사
accelerometerReading[0] = event.values[0]
accelerometerReading[1] = event.values[1]
accelerometerReading[2] = event.values[2]
}
Sensor.TYPE_MAGNETIC_FIELD -> {
// 자기장 센서 측정값 복사
magnetometerReading[0] = event.values[0]
magnetometerReading[1] = event.values[1]
magnetometerReading[2] = event.values[2]
}
}
// 두 센서 값으로부터 회전 행렬과 방위각 계산
val success = SensorManager.getRotationMatrix(
rotationMatrix,
null,
accelerometerReading,
magnetometerReading
)
if (success) {
SensorManager.getOrientation(rotationMatrix, orientationAngles)
// orientationAngles[0] 이 방위각(rad)이며, [1] Pitch, [2] Roll입니다.
val azimuthRad = orientationAngles[0]
var azimuthDegrees = Math.toDegrees(azimuthRad.toDouble()).toFloat()
if (azimuthDegrees < 0) {
azimuthDegrees += 360f // 음수 값을 0~360도로 변환
}
azimuthDeg = azimuthDegrees // 상태 업데이트
}
}
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) { /* 생략 */
}
}
// 센서 리스너 등록 (실행 속도: NORMAL)
sensorManager.registerListener(
sensorListener,
accelerometer,
SensorManager.SENSOR_DELAY_NORMAL
)
sensorManager.registerListener(
sensorListener,
magnetometer,
SensorManager.SENSOR_DELAY_NORMAL
)
// 컴포저블이 사라질 때 센서 해제
onDispose {
sensorManager.unregisterListener(sensorListener)
}
}
return azimuthDeg // 현재 방위각 값을 반환 (수시로 업데이트됨)
}
@Composable
fun ArrowDirectionIndicator(
modifier: Modifier = Modifier,
bearingToTarget: Float
) {
// 현재 기기 방위각을 센서로부터 얻음 (항상 0~360도 값 유지)
val azimuth = rememberAzimuth()
// 목표 방향 대비 기기 방향의 차이각 계산
val rotationAngle = (bearingToTarget - azimuth + 360f) % 360f
Image(
imageVector = Icons.Filled.KeyboardArrowUp, // 화살표 아이콘 (기본이 위쪽을 가리키는 그림)
contentDescription = "목표 방향 화살표",
modifier = modifier
.size(100.dp)
.rotate(rotationAngle)
)
}