M

mobile-android-design

Master Material Design 3 and Jetpack Compose patterns for building native Android apps. Use when designing Android interfaces, implementing Compose UI, or following Google's Material Design guidelines.

技能概览

名称

mobile-android-design

版本

v1.0.0

作者

wshobson

更新日期

2026-01-29T17:01:30.110Z

SKILL.md


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


  • Designing Android app interfaces following Material Design 3
  • Building Jetpack Compose UI and layouts
  • Implementing Android navigation patterns (Navigation Compose)
  • Creating adaptive layouts for phones, tablets, and foldables
  • Using Material 3 theming with dynamic colors
  • Building accessible Android interfaces
  • Implementing Android-specific gestures and interactions
  • Designing for different screen configurations

  • 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:


  • Cards, Buttons, FABs, Chips
  • Navigation (rail, drawer, bottom nav)
  • Text fields, Dialogs, Sheets
  • Lists, Menus, Progress indicators

  • 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


  • Recomposition Issues: Avoid passing unstable lambdas; use remember
  • State Loss: Use rememberSaveable for configuration changes
  • Performance: Use LazyColumn instead of Column for long lists
  • Theme Leaks: Ensure MaterialTheme wraps all composables
  • Navigation Crashes: Handle back press and deep links properly
  • Memory Leaks: Cancel coroutines in DisposableEffect

  • Resources