What You Will Build
A movie catalog with horizontal card layouts showing poster placeholders, titles, release years, star ratings, and a call-to-action button. This is the standard pattern used by IMDB, Letterboxd, and streaming app browse screens.
Why This Pattern Matters
Horizontal list items with image + text + actions are the most common card pattern in Android apps. Building this teaches you Row-based layouts inside LazyColumn items, star rating rendering with repeat(), and gradient placeholder images.
Step 1: Define the Movie Model
data class Movie(
val title: String,
val year: String,
val rating: Float
)
val movies = listOf(
Movie("The Matrix", "1999", 4.8f),
Movie("Inception", "2010", 4.7f),
Movie("Interstellar", "2014", 4.9f),
Movie("Dune", "2021", 4.5f),
Movie("Oppenheimer", "2023", 4.6f)
)
Step 2: Build the Movie Card
@Composable
fun MovieCard(movie: Movie) {
Card(Modifier.fillMaxWidth()) {
Row(Modifier.padding(12.dp)) {
// Poster placeholder with gradient
Box(
Modifier.size(100.dp, 140.dp)
.clip(RoundedCornerShape(8.dp))
.background(
Brush.linearGradient(
listOf(Color(0xFF6C63FF), Color(0xFFFF6584))
)
),
contentAlignment = Alignment.Center
) {
Icon(Icons.Default.Movie, null,
tint = Color.White, modifier = Modifier.size(36.dp))
}
Spacer(Modifier.width(16.dp))
Column {
Text(movie.title, fontWeight = FontWeight.Bold, fontSize = 18.sp)
Text(movie.year,
color = MaterialTheme.colorScheme.onSurfaceVariant)
Spacer(Modifier.height(8.dp))
// Star rating
Row {
repeat(5) { i ->
Icon(
if (i < movie.rating.toInt()) Icons.Default.Star
else Icons.Default.StarBorder,
null,
modifier = Modifier.size(16.dp),
tint = Color(0xFFFFD700)
)
}
}
Spacer(Modifier.height(8.dp))
Button(onClick = {}, modifier = Modifier.height(36.dp)) {
Text("Watch Now", fontSize = 13.sp)
}
}
}
}
}
Step 3: Assemble in a LazyColumn
Scaffold(topBar = { TopAppBar(title = { Text("Movie App") }) }) { padding ->
LazyColumn(
Modifier.padding(padding).padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(movies) { movie -> MovieCard(movie) }
}
}
Tips and Pitfalls
- repeat(5) for star ratings is cleaner than creating 5 separate Icon composables. Compare the index against
rating.toInt()to choose filled vs outlined stars. - Fixed poster dimensions (100x140) maintain a consistent 5:7 aspect ratio matching real movie posters.
- Gradient placeholders look professional while you wire up actual image loading with Coil or Glide.
- Extract MovieCard as a composable to keep the LazyColumn items block clean and enable preview.
Min SDK: 21 | Compose BOM: 2024.01.00+