What You'll Learn
センサーAPI(Accelerometer、Gyroscope、Step Counter、Proximity Sensor、Compose連携)を解説します。
Convert Sensor to Flow
fun sensorFlow(
context: Context,
sensorType: Int,
samplingPeriod: Int = SensorManager.SENSOR_DELAY_UI
): Flow<FloatArray> = callbackFlow {
val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
val sensor = sensorManager.getDefaultSensor(sensorType) ?: run {
close(IllegalStateException("Sensor not available"))
return@callbackFlow
}
val listener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent) {
trySend(event.values.copyOf())
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
}
sensorManager.registerListener(listener, sensor, samplingPeriod)
awaitClose { sensorManager.unregisterListener(listener) }
}
Accelerometer
@Composable
fun AccelerometerScreen() {
val context = LocalContext.current
var acceleration by remember { mutableStateOf(floatArrayOf(0f, 0f, 0f)) }
LaunchedEffect(Unit) {
sensorFlow(context, Sensor.TYPE_ACCELEROMETER)
.collect { values -> acceleration = values }
}
Column(Modifier.fillMaxSize().padding(16.dp)) {
Text("Accelerometer", style = MaterialTheme.typography.headlineMedium)
Spacer(Modifier.height(16.dp))
Text("X: ${"%.2f".format(acceleration[0])} m/s²")
Text("Y: ${"%.2f".format(acceleration[1])} m/s²")
Text("Z: ${"%.2f".format(acceleration[2])} m/s²")
Spacer(Modifier.height(24.dp))
Canvas(Modifier.size(200.dp)) {
val centerX = size.width / 2
val centerY = size.height / 2
val ballX = centerX + acceleration[0] * -10
val ballY = centerY + acceleration[1] * 10
drawCircle(Color.LightGray, radius = size.minDimension / 2)
drawCircle(Color.Red, radius = 20f, center = Offset(ballX, ballY))
}
}
}
Step Counter
@Composable
fun StepCounterScreen() {
val context = LocalContext.current
var steps by remember { mutableIntStateOf(0) }
LaunchedEffect(Unit) {
sensorFlow(context, Sensor.TYPE_STEP_COUNTER)
.collect { values -> steps = values[0].toInt() }
}
Column(
Modifier.fillMaxSize().padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("今日のSteps", style = MaterialTheme.typography.headlineMedium)
Text("$steps", style = MaterialTheme.typography.displayLarge)
Text("歩", style = MaterialTheme.typography.headlineSmall)
Spacer(Modifier.height(24.dp))
LinearProgressIndicator(
progress = { minOf(steps / 10000f, 1f) },
modifier = Modifier.fillMaxWidth()
)
Text("目標: 10,000歩", style = MaterialTheme.typography.bodySmall)
}
}
Shake Detection
@Composable
fun ShakeDetector(onShake: () -> Unit) {
val context = LocalContext.current
var lastShakeTime by remember { mutableLongStateOf(0L) }
LaunchedEffect(Unit) {
sensorFlow(context, Sensor.TYPE_ACCELEROMETER)
.collect { values ->
val magnitude = sqrt(values[0] * values[0] + values[1] * values[1] + values[2] * values[2])
if (magnitude > 15f) {
val now = System.currentTimeMillis()
if (now - lastShakeTime > 1000) {
lastShakeTime = now
onShake()
}
}
}
}
}
Summary
| センサー | TYPE |
|---|---|
| Acceleration | TYPE_ACCELEROMETER |
| Gyroscope | TYPE_GYROSCOPE |
| Steps | TYPE_STEP_COUNTER |
| Proximity | TYPE_PROXIMITY |
| Light | TYPE_LIGHT |
-
callbackFlowでセンサーをFlow化 -
SensorManagerでセンサー登録/解除 - Shake DetectionはAccelerationの大きさで判定
-
awaitCloseで確実にListener Cleanup
8種類のAndroidAppTemplates(センサーIntegration Support)を公開しています。
Template List → Gumroad
Related Articles:
Ready-Made Android App Templates
8 production-ready Android app templates with Jetpack Compose, MVVM, Hilt, and Material 3.
Browse templates → Gumroad
Top comments (0)