Wear OS Android UI Application in Kotlin
Wear OS Android UI Application in Kotlin
Here's a comprehensive Wear OS application built with Kotlin that demonstrates key UI components and patterns for smartwatches like Zeblaze.
1. Project Setup
First, ensure your build.gradle (Module) includes these dependencies:
dependencies { // Wear OS specific implementation "androidx.wear:wear:1.3.0" implementation "androidx.wear.compose:compose-material:1.2.0" implementation "androidx.wear.compose:compose-foundation:1.2.0" implementation "androidx.wear.compose:compose-navigation:1.2.0" // General Compose implementation "androidx.activity:activity-compose:1.8.0" implementation "androidx.compose.ui:ui:1.5.4" implementation "androidx.compose.material:material-icons-core:1.5.4" implementation "androidx.compose.material:material-icons-extended:1.5.4" // Health Services implementation "androidx.health:health-services-client:1.0.0-beta02" // ViewModel implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2" }
2. Main Activity and Navigation
MainActivity.kt:
import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.runtime.Composable import androidx.navigation.compose.rememberNavController import androidx.wear.compose.navigation.SwipeDismissableNavHost import androidx.wear.compose.navigation.composable import androidx.wear.compose.navigation.rememberSwipeDismissableNavController class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { WearApp() } } } @Composable fun WearApp() { val navController = rememberSwipeDismissableNavController() SwipeDismissableNavHost( navController = navController, startDestination = "home" ) { composable("home") { HomeScreen(navController) } composable("steps") { StepsScreen(navController) } composable("heart") { HeartRateScreen(navController) } composable("workout") { WorkoutScreen(navController) } composable("settings") { SettingsScreen(navController) } } }
3. Home Screen
HomeScreen.kt:
import androidx.compose.foundation.layout.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.navigation.NavController import androidx.wear.compose.material.* import java.text.SimpleDateFormat import java.util.* @Composable fun HomeScreen(navController: NavController) { var time by remember { mutableStateOf(getCurrentTime()) } LaunchedEffect(Unit) { while (true) { kotlinx.coroutines.delay(1000) time = getCurrentTime() } } Scaffold( timeText = { TimeText() }, vignette = { Vignette(vignettePosition = VignettePosition.TopAndBottom) } ) { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { // Time display Text( text = time, style = MaterialTheme.typography.display1 ) Spacer(modifier = Modifier.height(8.dp)) // Quick stats Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly ) { MetricButton( value = "7,650", unit = "steps", onClick = { navController.navigate("steps") } ) MetricButton( value = "78", unit = "BPM", onClick = { navController.navigate("heart") } ) } Spacer(modifier = Modifier.height(16.dp)) // Main actions Button( onClick = { navController.navigate("workout") }, modifier = Modifier.size(ButtonDefaults.LargeButtonSize) ) { Icon( imageVector = Icons.Default.DirectionsRun, contentDescription = "Workout" ) } } } } @Composable fun MetricButton(value: String, unit: String, onClick: () -> Unit) { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.clickable(onClick = onClick) ) { Text( text = value, style = MaterialTheme.typography.title2 ) Text( text = unit, style = MaterialTheme.typography.caption2 ) } } private fun getCurrentTime(): String { val sdf = SimpleDateFormat("HH:mm", Locale.getDefault()) return sdf.format(Date()) }
4. Steps Screen with Health Data
StepsScreen.kt:
import androidx.compose.foundation.layout.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.navigation.NavController import androidx.wear.compose.material.* import kotlinx.coroutines.delay @Composable fun StepsScreen(navController: NavController) { var steps by remember { mutableStateOf(0) } var goal by remember { mutableStateOf(10000) } var isRefreshing by remember { mutableStateOf(false) } // Simulate data refresh LaunchedEffect(isRefreshing) { if (isRefreshing) { delay(1500) steps = (7500..8500).random() isRefreshing = false } } Scaffold( timeText = { TimeText() }, positionIndicator = { PositionIndicator() } ) { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { CircularProgressIndicator( progress = (steps.toFloat() / goal.toFloat()).coerceAtMost(1f), modifier = Modifier.size(100.dp), startAngle = 270f, strokeWidth = 8.dp, indicatorColor = Color.Green ) Spacer(modifier = Modifier.height(16.dp)) Text( text = "$steps / $goal", style = MaterialTheme.typography.title2 ) Text( text = "steps today", style = MaterialTheme.typography.caption2 ) Spacer(modifier = Modifier.height(16.dp)) Button( onClick = { isRefreshing = true }, enabled = !isRefreshing, colors = ButtonDefaults.buttonColors( backgroundColor = Color.Green.copy(alpha = 0.2f), contentColor = Color.Green ) ) { Text(text = if (isRefreshing) "Updating..." else "Refresh") } Spacer(modifier = Modifier.height(8.dp)) TextButton(onClick = { navController.popBackStack() }) { Text(text = "Back") } } } }
5. Heart Rate Screen with Animation
HeartRateScreen.kt:
import androidx.compose.animation.core.* import androidx.compose.foundation.layout.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.navigation.NavController import androidx.wear.compose.material.* import kotlinx.coroutines.delay @Composable fun HeartRateScreen(navController: NavController) { var heartRate by remember { mutableStateOf<Int?>(null) } var isMeasuring by remember { mutableStateOf(false) } val pulseAnimation by animateFloatAsState( targetValue = if (isMeasuring) 1.2f else 1f, animationSpec = repeatable( iterations = AnimationConstants.Infinite, animation = tween(500), repeatMode = RepeatMode.Reverse ) ) LaunchedEffect(isMeasuring) { if (isMeasuring) { delay(3000) // Simulate measurement time heartRate = (65..85).random() isMeasuring = false } } Scaffold( timeText = { TimeText() }, positionIndicator = { PositionIndicator() } ) { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Icon( imageVector = Icons.Default.Favorite, contentDescription = "Heart", tint = Color.Red, modifier = Modifier .size(48.dp) .scale(pulseAnimation) ) Spacer(modifier = Modifier.height(16.dp)) Text( text = when { isMeasuring -> "Measuring..." heartRate == null -> "--" else -> heartRate.toString() }, style = MaterialTheme.typography.display2 ) Text( text = "BPM", style = MaterialTheme.typography.caption1 ) Spacer(modifier = Modifier.height(16.dp)) Button( onClick = { isMeasuring = true }, enabled = !isMeasuring, colors = ButtonDefaults.buttonColors( backgroundColor = Color.Red.copy(alpha = 0.2f), contentColor = Color.Red ) ) { Text(text = if (isMeasuring) "..." else "Measure") } Spacer(modifier = Modifier.height(8.dp)) TextButton(onClick = { navController.popBackStack() }) { Text(text = "Back") } } } }
6. Workout Tracking Screen
WorkoutScreen.kt:
import androidx.compose.foundation.layout.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.navigation.NavController import androidx.wear.compose.material.* import kotlinx.coroutines.delay import java.text.SimpleDateFormat import java.util.* @Composable fun WorkoutScreen(navController: NavController) { var isRunning by remember { mutableStateOf(false) } var startTime by remember { mutableStateOf(0L) } var currentTime by remember { mutableStateOf("00:00:00") } LaunchedEffect(isRunning) { if (isRunning) { startTime = System.currentTimeMillis() while (isRunning) { val elapsed = System.currentTimeMillis() - startTime currentTime = SimpleDateFormat("HH:mm:ss", Locale.getDefault()) .apply { timeZone = TimeZone.getTimeZone("UTC") } .format(Date(elapsed)) delay(1000) } } } Scaffold( timeText = { TimeText() }, positionIndicator = { PositionIndicator() } ) { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text( text = currentTime, style = MaterialTheme.typography.display2, textAlign = TextAlign.Center ) Spacer(modifier = Modifier.height(16.dp)) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly ) { Button( onClick = { isRunning = !isRunning if (!isRunning) startTime = 0 }, colors = ButtonDefaults.buttonColors( backgroundColor = if (isRunning) Color.Red else Color.Green ), modifier = Modifier.size(ButtonDefaults.LargeButtonSize) ) { Icon( imageVector = if (isRunning) Icons.Default.Stop else Icons.Default.PlayArrow, contentDescription = if (isRunning) "Stop" else "Start" ) } } Spacer(modifier = Modifier.height(8.dp)) if (!isRunning && startTime > 0) { Text( text = "Workout saved", style = MaterialTheme.typography.caption1, color = Color.Green ) } Spacer(modifier = Modifier.height(8.dp)) TextButton(onClick = { navController.popBackStack() }) { Text(text = "Back") } } } }
7. Settings Screen with Toggle
SettingsScreen.kt:
import androidx.compose.foundation.layout.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.navigation.NavController import androidx.wear.compose.material.* @Composable fun SettingsScreen(navController: NavController) { var notificationsEnabled by remember { mutableStateOf(true) } var hapticFeedback by remember { mutableStateOf(false) } ScalingLazyColumn( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally ) { item { ListHeader { Text("Settings") } } item { ToggleChip( checked = notificationsEnabled, onCheckedChange = { notificationsEnabled = it }, label = { Text("Notifications") }, appIcon = { Icon( Icons.Default.Notifications, contentDescription = "Notifications" ) } ) } item { ToggleChip( checked = hapticFeedback, onCheckedChange = { hapticFeedback = it }, label = { Text("Haptic Feedback") }, appIcon = { Icon( Icons.Default.Vibration, contentDescription = "Haptic Feedback" ) } ) } item { Button( onClick = { navController.popBackStack() }, modifier = Modifier.padding(top = 16.dp) ) { Text("Back") } } } }
Key Features Implemented:
Navigation: Swipe-to-dismiss navigation between screens
Health Metrics: Steps counter and heart rate monitor
Workout Tracking: Stopwatch functionality for workouts
Settings: Configurable options with toggle chips
Animations: Pulse animation for heart rate measurement
Progress Indicators: Circular progress for step goals
Wear OS Components: Proper use of Wear-specific Material components
This implementation provides a solid foundation for any Wear OS smartwatch application. You can extend it with actual sensor data integration using Health Services API and customize the UI to match specific design language.
Comments
Post a Comment