Initial Movies UI
Flesh out the screens
Now we flesh out the screens, making a very simple (but inefficient) list, and a simple display.
First, we create a couple of helper components for consistency.
Display
and Label
are just Text
s with some parameters set for styling and padding. Using this
approach of creating wrapper functions gives us "extended" components.
Note
Note how Display
has
two padding modifiers.
These are applied in the order they're defined. In this case,
we apply an 8dp padding all around, and then an extra 16dp padding only at
the start of the text. This causes the text to appear indented.
If we did something like
Modifier
.padding(8.dp)
.border(2.dp, Color.Blue, RoundedCornerShape(4.dp))
.padding(8.dp)
we'd see 8dp padding, a blue border inside it, and 8dp more padding inside the border. This is a much simpler system than the margin/padding combinations that existed with the old "views" UI approach.
We surround each of the screens with a
Scaffold
, which is a
"slot api"
composable function. This is an implementation of the Template Method Pattern,
an algorithm with replaceable steps. The Scaffold
defines an algorithm for managing
several "slots" on the screen, areas like "stuff at the top", "stuff at the bottom",
"stuff in between", etc. Scaffold
defines the layout algorithm for measuring and placing
elements in the specified position; you define what goes in those positions.
In this example, we define topBar
, passing a lambda for
what elements should appear in that section of the Scaffold
. When the scaffold
is performing its layout work, it calls that lambda to obtain the components, measures
them, and places them. (Note that some slotted apis may conditionally execute
lambdas such as these; for example, if you define a navigation drawer, it might not
appear unless the user has swiped in from the side.)
Our topBar
declares that a TopAppBar
should appear. This is a common tool bar
element, which can contain a title, navigation icon, and action icons. We'll do
much more with it in later modules. Here we're just setting a title.
Our MovieDisplay
sets up a
Column
as the main content of the Scaffold
. Note the paddingValues
that are passed to
the content lambda. These define the padding you must use to avoid overlapping with
other slots in the Scaffold
.
Our Column
is scrollable and stacks Label
s and Display
s to show the details
of a movie.
MovieListUi
is a bit more complex. It uses a Scaffold
in the same way as MovieDisplay
,
but the contents of its column are
dynamic. We iterate through the list of movies
using forEach
, and create a nice little Card
for each movie, showing an icon and title.
We make the icon and title line up nicely by adding an alignment specification.
By defining onClick
at the Card
level, the
user can click anywhere inside the card to select the movie. That click passes the
movie for that card to onMovieClicked
, informing the caller that a movie has been
selected.
Be sure to define any literal strings that the user will see in
app/src/main/res/values/strings.xml
.
Code Changes
ADDED: /app/src/main/java/com/androidbyexample/movieui1/components/Display.kt
package com.androidbyexample.movieui1.componentsimport androidx.compose.foundation.borderimport androidx.compose.foundation.layout.fillMaxWidthimport androidx.compose.foundation.layout.paddingimport androidx.compose.foundation.shape.RoundedCornerShapeimport androidx.compose.material3.MaterialThemeimport androidx.compose.material3.Textimport androidx.compose.runtime.Composableimport androidx.compose.ui.Modifierimport androidx.compose.ui.graphics.Colorimport androidx.compose.ui.unit.dp@Composablefun Display( text: String,) { Text( text = text, style = MaterialTheme.typography.titleLarge, modifier = Modifier.padding(8.dp) .padding(start = 16.dp).fillMaxWidth(), )}
ADDED: /app/src/main/java/com/androidbyexample/movieui1/components/Label.kt
package com.androidbyexample.movieui1.componentsimport androidx.annotation.StringResimport androidx.compose.foundation.layout.fillMaxWidthimport androidx.compose.foundation.layout.paddingimport androidx.compose.material3.MaterialThemeimport androidx.compose.material3.Textimport androidx.compose.runtime.Composableimport androidx.compose.ui.Modifierimport androidx.compose.ui.res.stringResourceimport androidx.compose.ui.unit.dp@Composablefun Label( @StringRes textId: Int,) { Text( text = stringResource(id = textId), style = MaterialTheme.typography.titleMedium, modifier = Modifier .padding(8.dp) .fillMaxWidth(), )}
CHANGED: /app/src/main/java/com/androidbyexample/movieui1/screens/MovieDisplayUi.kt
package com.androidbyexample.movieui1.screens import androidx.compose.foundation.layout.Columnimport androidx.compose.foundation.layout.paddingimport androidx.compose.foundation.rememberScrollStateimport androidx.compose.foundation.verticalScrollimport androidx.compose.material3.ExperimentalMaterial3Apiimport androidx.compose.material3.Scaffoldimport androidx.compose.material3.Text import androidx.compose.material3.TopAppBarimport androidx.compose.runtime.Composable import androidx.compose.ui.Modifierimport com.androidbyexample.movieui1.Movie import com.androidbyexample.movieui1.Rimport com.androidbyexample.movieui1.components.Displayimport com.androidbyexample.movieui1.components.Label @OptIn(ExperimentalMaterial3Api::class)@Composable fun MovieDisplayUi(// movie: Moviemovie: Movie,) {}// Text(text = "Movie Display: ${movie.title}")Scaffold(topBar = { TopAppBar( title = { Text(text = movie.title) } ) }) { paddingValues ->Column( modifier = Modifier .padding(paddingValues) .verticalScroll(rememberScrollState()) ) { Label(textId = R.string.title) Display(text = movie.title) Label(textId = R.string.description) Display(text = movie.description) }}
CHANGED: /app/src/main/java/com/androidbyexample/movieui1/screens/MovieListUi.kt
package com.androidbyexample.movieui1.screens//import androidx.compose.foundation.clickableimport androidx.compose.foundation.layout.Columnimport androidx.compose.foundation.layout.Rowimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.paddingimport androidx.compose.foundation.rememberScrollStateimport androidx.compose.foundation.verticalScrollimport androidx.compose.material.icons.Iconsimport androidx.compose.material.icons.filled.Starimport androidx.compose.material3.Cardimport androidx.compose.material3.CardDefaultsimport androidx.compose.material3.CardElevationimport androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Iconimport androidx.compose.material3.Scaffoldimport androidx.compose.material3.Text import androidx.compose.material3.TopAppBarimport androidx.compose.runtime.Composable import androidx.compose.ui.Alignmentimport androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResourceimport androidx.compose.ui.unit.dpimport com.androidbyexample.movieui1.Movie import com.androidbyexample.movieui1.Rimport com.androidbyexample.movieui1.components.Display @OptIn(ExperimentalMaterial3Api::class)@Composable fun MovieListUi( movies: List<Movie>, onMovieClicked: (Movie) -> Unit, ) {// Text(// text = "Movie List",// modifier = Modifier.clickable {// onMovieClicked(movies[0])Scaffold( topBar = { TopAppBar( title = { Text(text = stringResource(R.string.movies)) } ) }, modifier = Modifier.fillMaxSize() ) { paddingValues -> Column( modifier = Modifier .padding(paddingValues) .fillMaxSize() .verticalScroll(rememberScrollState()) ) {movies.forEach { movie -> Card( elevation = CardDefaults.cardElevation( defaultElevation = 8.dp, ),onClick = { onMovieClicked(movie) },modifier = Modifier.padding(8.dp) ) { Row(verticalAlignment = Alignment.CenterVertically,modifier = Modifier.padding(8.dp) ) { Icon( imageVector = Icons.Default.Star, contentDescription = stringResource(id = R.string.movie) ) Display(text = movie.title) } } }// )} }}
CHANGED: /app/src/main/res/values/strings.xml
<resources> <string name="app_name">MovieUi1</string> <string name="movies">Movies</string> <string name="movie">Movie</string> <string name="title">Title</string> <string name="description">Description</string></resources>