Android Mobile Design
Master Material Design 3 (Material You) and Jetpack Compose to build modern, adaptive Android applications that integrate seamlessly with the Android ecosystem.
When to Use This Skill
Core Concepts
1. Material Design 3 Principles
Personalization: Dynamic color adapts UI to user's wallpaper
Accessibility: Tonal palettes ensure sufficient color contrast
Large Screens: Responsive layouts for tablets and foldables
Material Components:
2. Jetpack Compose Layout System
Column and Row:
// Vertical arrangement with alignment
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.Start
) {
Text(
text = "Title",
style = MaterialTheme.typography.headlineSmall
)
Text(
text = "Subtitle",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
// Horizontal arrangement with weight
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Default.Star, contentDescription = null)
Text("Featured")
Spacer(modifier = Modifier.weight(1f))
TextButton(onClick = {}) {
Text("View All")
}
}
Lazy Lists and Grids:
// Lazy column with sticky headers
LazyColumn {
items.groupBy { it.category }.forEach { (category, categoryItems) ->
stickyHeader {
Text(
text = category,
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surface)
.padding(16.dp),
style = MaterialTheme.typography.titleMedium
)
}
items(categoryItems) { item ->
ItemRow(item = item)
}
}
}
// Adaptive grid
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 150.dp),
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(items) { item ->
ItemCard(item = item)
}
}
3. Navigation Patterns
Bottom Navigation:
@Composable
fun MainScreen() {
val navController = rememberNavController()
Scaffold(
bottomBar = {
NavigationBar {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
NavigationDestination.entries.forEach { destination ->
NavigationBarItem(
icon = { Icon(destination.icon, contentDescription = null) },
label = { Text(destination.label) },
selected = currentDestination?.hierarchy?.any {
it.route == destination.route
} == true,
onClick = {
navController.navigate(destination.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
)
}
}
}
) { innerPadding ->
NavHost(
navController = navController,
startDestination = NavigationDestination.Home.route,
modifier = Modifier.padding(innerPadding)
) {
composable(NavigationDestination.Home.route) { HomeScreen() }
composable(NavigationDestination.Search.route) { SearchScreen() }
composable(NavigationDestination.Profile.route) { ProfileScreen() }
}
}
}
Navigation Drawer:
@Composable
fun DrawerNavigation() {
val drawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet {
Spacer(Modifier.height(12.dp))
Text(
"App Name",
modifier = Modifier.padding(16.dp),
style = MaterialTheme.typography.titleLarge
)
HorizontalDivider()
NavigationDrawerItem(
icon = { Icon(Icons.Default.Home, null) },
label = { Text("Home") },
selected = true,
onClick = { scope.launch { drawerState.close() } }
)
NavigationDrawerItem(
icon = { Icon(Icons.Default.Settings, null) },
label = { Text("Settings") },
selected = false,
onClick = { }
)
}
}
) {
Scaffold(
topBar = {
TopAppBar(
title = { Text("Home") },
navigationIcon = {
IconButton(onClick = { scope.launch { drawerState.open() } }) {
Icon(Icons.Default.Menu, contentDescription = "Menu")
}
}
)
}
) { innerPadding ->
Content(modifier = Modifier.padding(innerPadding))
}
}
}
4. Material 3 Theming
Color Scheme:
// Dynamic color (Android 12+)
val dynamicColorScheme = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context)
else dynamicLightColorScheme(context)
} else {
if (darkTheme) DarkColorScheme else LightColorScheme
}
// Custom color scheme
private val LightColorScheme = lightColorScheme(
primary = Color(0xFF6750A4),
onPrimary = Color.White,
primaryContainer = Color(0xFFEADDFF),
onPrimaryContainer = Color(0xFF21005D),
secondary = Color(0xFF625B71),
onSecondary = Color.White,
tertiary = Color(0xFF7D5260),
onTertiary = Color.White,
surface = Color(0xFFFFFBFE),
onSurface = Color(0xFF1C1B1F),
)
Typography:
val AppTypography = Typography(
displayLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 57.sp,
lineHeight = 64.sp
),
headlineMedium = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 28.sp,
lineHeight = 36.sp
),
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp
),
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp
),
labelMedium = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 12.sp,
lineHeight = 16.sp
)
)
5. Component Examples
Cards:
@Composable
fun FeatureCard(
title: String,
description: String,
imageUrl: String,
onClick: () -> Unit
) {
Card(
onClick = onClick,
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant
)
) {
Column {
AsyncImage(
model = imageUrl,
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.height(180.dp),
contentScale = ContentScale.Crop
)
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = description,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
}
Buttons:
// Filled button (primary action)
Button(onClick = { }) {
Text("Continue")
}
// Filled tonal button (secondary action)
FilledTonalButton(onClick = { }) {
Icon(Icons.Default.Add, null)
Spacer(Modifier.width(8.dp))
Text("Add Item")
}
// Outlined button
OutlinedButton(onClick = { }) {
Text("Cancel")
}
// Text button
TextButton(onClick = { }) {
Text("Learn More")
}
// FAB
FloatingActionButton(
onClick = { },
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.onPrimaryContainer
) {
Icon(Icons.Default.Add, contentDescription = "Add")
}
Quick Start Component
@Composable
fun ItemListCard(
item: Item,
onItemClick: () -> Unit,
modifier: Modifier = Modifier
) {
Card(
onClick = onItemClick,
modifier = modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp)
) {
Row(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.background(MaterialTheme.colorScheme.primaryContainer),
contentAlignment = Alignment.Center
) {
Icon(
imageVector = Icons.Default.Star,
contentDescription = null,
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
}
Spacer(modifier = Modifier.width(16.dp))
Column(modifier = Modifier.weight(1f)) {
Text(
text = item.title,
style = MaterialTheme.typography.titleMedium
)
Text(
text = item.subtitle,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
Icon(
imageVector = Icons.Default.ChevronRight,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
Best Practices
1. Use Material Theme: Access colors via MaterialTheme.colorScheme for automatic dark mode support
2. Support Dynamic Color: Enable dynamic color on Android 12+ for personalization
3. Adaptive Layouts: Use WindowSizeClass for responsive designs
4. Content Descriptions: Add contentDescription to all interactive elements
5. Touch Targets: Minimum 48dp touch targets for accessibility
6. State Hoisting: Hoist state to make components reusable and testable
7. Remember Properly: Use remember and rememberSaveable appropriately
8. Preview Annotations: Add @Preview with different configurations
Common Issues
rememberrememberSaveable for configuration changesLazyColumn instead of Column for long listsMaterialTheme wraps all composablesDisposableEffectResources
相关技能
- A
agent-browser
Automates browser interactions for web testing, form filling, screenshots, and data extraction. Use when the user needs to navigate websites, interact with web pages, fill forms, take screenshots, test web applications, or extract information from web pages.
- A
analytics-tracking
When the user wants to set up, improve, or audit analytics tracking and measurement. Also use when the user mentions "set up tracking," "GA4," "Google Analytics," "conversion tracking," "event tracking," "UTM parameters," "tag manager," "GTM," "analytics implementation," or "tracking plan." For A/B test measurement, see ab-test-setup.
- A
atxp
Access ATXP paid API tools for web search, AI image generation, music creation, video generation, and X/Twitter search. Use when users need real-time web search, AI-generated media (images, music, video), or X/Twitter search. Requires authentication via `npx atxp login`.
- B
better-auth-best-practices
Skill for integrating Better Auth - the comprehensive TypeScript authentication framework.
- B
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation.