Learn how to implement Material You dynamic theming in Compose, adapting colors based on device wallpaper.
Dynamic Color Schemes (API 31+)
@Composable
fun DynamicColorTheme(
content: @Composable () -> Unit
) {
val colorScheme = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val context = LocalContext.current
if (isSystemInDarkTheme()) {
dynamicDarkColorScheme(context)
} else {
dynamicLightColorScheme(context)
}
} else {
// Fallback for API < 31
if (isSystemInDarkTheme()) {
darkColorScheme()
} else {
lightColorScheme()
}
}
MaterialTheme(
colorScheme = colorScheme,
content = content
)
}
Color Fallback for Older APIs
fun getColorScheme(context: Context, isDark: Boolean): ColorScheme {
return when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
if (isDark) {
dynamicDarkColorScheme(context)
} else {
dynamicLightColorScheme(context)
}
}
else -> {
// Custom fallback palette
if (isDark) {
darkColorScheme(
primary = Color(0xFF6200EE),
secondary = Color(0xFF03DAC6),
tertiary = Color(0xFF03DAC5)
)
} else {
lightColorScheme(
primary = Color(0xFF6200EE),
secondary = Color(0xFF03DAC6),
tertiary = Color(0xFF1F6Feb)
)
}
}
}
}
Using ColorScheme Properties
@Composable
fun ColoredComponents() {
Column(
modifier = Modifier.fillMaxSize().padding(16.dp)
) {
// Primary container button
Button(
onClick = {},
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.onPrimaryContainer
)
) {
Text("Primary Container")
}
Spacer(modifier = Modifier.height(8.dp))
// Secondary container card
Card(
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.secondaryContainer
),
modifier = Modifier.fillMaxWidth().padding(8.dp)
) {
Text(
"Secondary Container",
modifier = Modifier.padding(16.dp),
color = MaterialTheme.colorScheme.onSecondaryContainer
)
}
Spacer(modifier = Modifier.height(8.dp))
// Tertiary container
Surface(
color = MaterialTheme.colorScheme.tertiaryContainer,
modifier = Modifier.fillMaxWidth().padding(8.dp)
) {
Text(
"Tertiary Container",
modifier = Modifier.padding(16.dp),
color = MaterialTheme.colorScheme.onTertiaryContainer
)
}
Spacer(modifier = Modifier.height(8.dp))
// Surface variant
Surface(
color = MaterialTheme.colorScheme.surfaceVariant,
modifier = Modifier.fillMaxWidth().padding(8.dp)
) {
Text(
"Surface Variant",
modifier = Modifier.padding(16.dp),
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
Theme Settings UI with Toggles
@Composable
fun ThemeSettingsScreen() {
var isDarkMode by rememberSaveable { mutableStateOf(isSystemInDarkTheme()) }
var useDynamicColor by rememberSaveable { mutableStateOf(true) }
val context = LocalContext.current
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text(
"Theme Settings",
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier.padding(bottom = 16.dp)
)
// Dark mode toggle
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text("Dark Mode")
Switch(
checked = isDarkMode,
onCheckedChange = { isDarkMode = it }
)
}
Divider(modifier = Modifier.padding(vertical = 8.dp))
// Dynamic color toggle (API 31+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text("Use Dynamic Color")
Switch(
checked = useDynamicColor,
onCheckedChange = { useDynamicColor = it }
)
}
Divider(modifier = Modifier.padding(vertical = 8.dp))
}
// Color preview
Text(
"Color Preview",
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(top = 16.dp, bottom = 8.dp)
)
LazyRow(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(
listOf(
"Primary" to MaterialTheme.colorScheme.primary,
"Secondary" to MaterialTheme.colorScheme.secondary,
"Tertiary" to MaterialTheme.colorScheme.tertiary,
"Primary Container" to MaterialTheme.colorScheme.primaryContainer,
"Secondary Container" to MaterialTheme.colorScheme.secondaryContainer
)
) { (label, color) ->
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Surface(
color = color,
shape = RoundedCornerShape(8.dp),
modifier = Modifier.size(60.dp)
) {}
Text(label, style = MaterialTheme.typography.labelSmall)
}
}
}
}
}
Leverage Material You to create personalized, adaptive themes that delight users!
Get 8 Android app templates: Gumroad
