1. Before you begin
In the previous codelab, you created an Affirmations app that displays a list of text in a RecyclerView
.
In this follow-up codelab, you add an inspiring image to each affirmation of your app. You will display the text and image for each affirmation within a card, using the MaterialCardView
widget from the Material Components for Android library. Then you will finish the app by polishing the UI to create a more cohesive and beautiful user experience. This is a screenshot of the completed app:
Prerequisites
- Can add image resources to an app.
- Comfortable modifying an XML layout.
- Able to create an app that displays a list of text in a
RecyclerView
. - Able to create an adapter for a
RecyclerView
.
What you'll learn
- How to add images to the list of displayed affirmations in a
RecyclerView
. - How to use
MaterialCardView
in aRecyclerView
item layout. - How to make visual changes in the UI to make the app look more polished.
What you'll build
- A polished Affirmations app that uses a
RecyclerView
to display a list of cards. Each card contains an image and affirmation text.
What you need
- A computer with Android Studio version 4.1 or higher installed.
- Access to an internet connection to download image files.
- The Affirmations app from the previous Create an Affirmations app codelab. (No starter code is provided. Creating the app is a prerequisite.)
2. Adding images to the list items
So far you have created an adapter ItemAdapter
to display affirmation strings in a RecyclerView
. Functionally this works great, but visually it is not very appealing. In this task, you will modify the list item layout and adapter code to display images with the affirmations.
Download the images
- To start, open up the Affirmations app project in Android Studio from the previous codelab. If you don't have this project, go through the steps in the previous codelab to create that project. Then return here.
- Next download the image files onto your computer. There should be ten images, one for each affirmation in your app. The files should be named from
image1.jpg
toimage10.jpg
. - Copy the images from your computer into your project's res > drawable folder (
app/src/main/res/drawable
) within Android Studio. Once these resources have been added to your app, you will be able to access these images from your code using their resource IDs, such asR.drawable.image1
. (You may have to rebuild your code for Android Studio to find the image.)
Now the images are ready to use in the app.
Add support for images in the Affirmation class
In this step, you'll add a property in the Affirmation
data class to hold a value for an image resource ID. That way a single Affirmation
object instance will contain a resource ID for the text of the affirmation and a resource ID for the image of the affirmation.
- Open the
Affirmation.kt
file within themodel
package. - Modify the constructor of the
Affirmation
class by adding anotherInt
parameter namedimageResourceId
.
Using resource annotations
Both stringResourceId
and imageResourceId
are integer values. Although this looks okay, the caller could accidentally pass in the arguments in the wrong order (imageResourceId
first instead of stringResourceId
).
To avoid this, you can use Resource annotations. Annotations are useful because they add additional info to classes, methods, or parameters. Annotations are always declared with an @ symbol. In this case, add the @StringRes
annotation to your string resource ID property and @DrawableRes
annotation to your drawable resource ID property. Then you will get a warning if you supply the wrong type of resource ID.
- Add the
@StringRes
annotation tostringResourceId
. - Add the
@DrawableRes
annotation toimageResourceId
. - Make sure the imports
androidx.annotation.DrawableRes
andandroidx.annotation.StringRes
are added at the top of your file after the package declaration.
Affirmation.kt
package com.example.affirmations.model
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
data class Affirmation(
@StringRes val stringResourceId: Int,
@DrawableRes val imageResourceId: Int
)
Initialize list of affirmations with images
Now that you've changed the constructor of the Affirmation
class, you need to update the Datasource
class. Pass in an image resource ID to each Affirmation
object that is initialized.
- Open
Datasource.kt
. You should see an error for each instantiation ofAffirmation
. - For each
Affirmation
, add the resource ID of an image as an argument, such asR.drawable.image1
.
Datasource.kt
package com.example.affirmations.data
import com.example.affirmations.R
import com.example.affirmations.model.Affirmation
class Datasource() {
fun loadAffirmations(): List<Affirmation> {
return listOf<Affirmation>(
Affirmation(R.string.affirmation1, R.drawable.image1),
Affirmation(R.string.affirmation2, R.drawable.image2),
Affirmation(R.string.affirmation3, R.drawable.image3),
Affirmation(R.string.affirmation4, R.drawable.image4),
Affirmation(R.string.affirmation5, R.drawable.image5),
Affirmation(R.string.affirmation6, R.drawable.image6),
Affirmation(R.string.affirmation7, R.drawable.image7),
Affirmation(R.string.affirmation8, R.drawable.image8),
Affirmation(R.string.affirmation9, R.drawable.image9),
Affirmation(R.string.affirmation10, R.drawable.image10)
)
}
}
Add an ImageView to the list item layout
To show an image for each affirmation in your list, you need to add an ImageView
to your item layout. Because you now have two views (a TextView
and ImageView
), you need to place them as children views within a ViewGroup
. To arrange the views in a vertical column, you can use a LinearLayout
. LinearLayout
aligns all child views in a single direction, vertically or horizontally.
- Open res > layout > list_item.xml. Add a
LinearLayout
around the existingTextView
and set theorientation
property tovertical
. - Move the
xmlns schema
declaration line from theTextView
element to theLinearLayout
element to get rid of the error.
list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
- Inside the
LinearLayout,
before theTextView,
add anImageView
with a resource ID ofitem_image
. - Set the
ImageView
‘s width tomatch_parent
and height to194dp
. Depending on screen size, this value should show a few cards on screen at any given time. - Set the
scaleType
tocenterCrop.
- Set the
importantForAccessibility
attribute tono
since the image is used for decorative purposes.
<ImageView
android:layout_width="match_parent"
android:layout_height="194dp"
android:id="@+id/item_image"
android:importantForAccessibility="no"
android:scaleType="centerCrop" />
Update the ItemAdapter to set the image
- Open
adapter/ItemAdapter.kt
(app > java > adapter > ItemAdapter) - Go to the
ItemViewHolder
class. - An
ItemViewHolder
instance should hold a reference to theTextView
and a reference to theImageView
in the list item layout. Make the following change.
Below the initialization of the textView
property, add a val
called imageView
. Use findViewById()
to find the reference to the ImageView
with ID item_image
and assign it to the imageView
property.
ItemAdapter.kt
class ItemViewHolder(private val view: View): RecyclerView.ViewHolder(view) {
val textView: TextView = view.findViewById(R.id.item_title)
val imageView: ImageView = view.findViewById(R.id.item_image)
}
- Find the
onBindViewHolder()
function inItemAdapter
. - Previously you set the affirmation's
stringResourceId
on totextView
in theItemViewHolder
. Now set the affirmation item'simageResourceId
onto theImageView
of the list item view.
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val item = dataset[position]
holder.textView.text = context.resources.getString(item.stringResourceId)
holder.imageView.setImageResource(item.imageResourceId)
}
- Run the app and scroll through the list of affirmations.
The app looks much prettier with images! Yet you can still improve the app UI. In the next section you will make small adjustments to the app to improve the UI.
3. Polishing the UI
So far you've built a functional app that consists of a list of affirmation strings and images. In this section, you'll see how small changes in the code and XML can make the app look more polished.
Add padding
To start with, add some whitespace between the items in the list.
- Open
item_list.xml
(app > res > layout > item_list.xml) and add16dp
padding to the existingLinearLayout
.
list_item.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
- Add
16dp
padding to theitem_title
TextView
. - In the
TextView
, set thetextAppearance
attribute to?attr/textAppearanceHeadline6
.textAppearance
is an attribute that allows you to define text-specific styling. For other possible predefined text appearance values, you can see the TextAppearances section in this blogpost on Common Theme Attributes.
<TextView
android:id="@+id/item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp"
android:textAppearance="?attr/textAppearanceHeadline6" />
- Run the app. Do you think the list looks better?
Use cards
It is still hard to tell if an image belongs to the affirmation text above or below that image. To fix this you can use a Card view. A Card view provides an easy way to contain a group of views while providing a consistent style for the container. For more Material Design guidance on using cards, check out this guide on cards.
- Add a
MaterialCardView
around the existingLinearLayout
. - Once again, move the schema declaration from
LinearLayout
intoMaterialCardView
. - Set the
layout_width
of theMaterialCardView
tomatch_parent
, and thelayout_height
towrap_content
. - Add a
layout_margin
of8dp
. - Remove the padding in the
LinearLayout
, so you don't have too much whitespace. - Now run the app again. Can you tell each affirmation apart better with
MaterialCardView
?
list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/item_image"
android:layout_width="match_parent"
android:layout_height="194dp"
android:importantForAccessibility="no"
android:scaleType="centerCrop" />
<TextView
android:id="@+id/item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp"
android:textAppearance="?attr/textAppearanceHeadline6" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
Change the app theme colors
The default app theme color may not be as calming as some other choices you could make. In this task, you will change the app theme color to blue. You can then change it again using your own ideas!
You can find pre-defined shades of blue from the Material Design color palette from this link.
In this codelab, you will be using the following colors from the Material Design palette:
- blue_200:
#FF90CAF9
- blue_500:
#FF2196F3
- blue_700:
#FF1976D2
Add color resources
Define colors used within your app in a centralized place: the colors.xml
file.
- Open
colors.xml
(res > values > colors.xml). - Add new color resources to the file for the blue colors defined below:
<color name="blue_200">#FF90CAF9</color>
<color name="blue_500">#FF2196F3</color>
<color name="blue_700">#FF1976D2</color>
Change the theme colors
Now that you have new color resources, you can use them in your theme.
- Open
themes.xml
(res > values > themes > themes.xml). - Find the
<!-- Primary brand color. -->
section. - Add or change
colorPrimary
to use@color/blue_500
. - Add or change
colorPrimaryVariant
to use@color/blue_700
.
<item name="colorPrimary">@color/blue_500</item>
<item name="colorPrimaryVariant">@color/blue_700</item>
- Run the app. You should see the app bar color is changed to blue.
Update the dark theme colors
It's good to choose more desaturated colors for the dark theme of the app.
- Open the dark theme
themes.xml
file (themes > themes.xml (night)). - Add or change the
colorPrimary
andcolorPrimaryVariant
theme attributes as follows:
<item name="colorPrimary">@color/blue_200</item>
<item name="colorPrimaryVariant">@color/blue_500</item>
- Run your app.
- In the Settings of your device, turn on the Dark Theme.
- Your app switches to the Dark Theme. Verify that it looks like the screenshot below:
- At this point, you can also remove unused colors in your
colors.xml
file (for example, the purple color resources used in the default app theme).
Change the app icon
As a final step, you'll update the app icon.
- Download the app icon files
ic_launcher_foreground.xml
andic_launcher_background.xml
. If your browser shows the file instead of downloading it, select File > Save Page As... to save it to your computer. - Within Android Studio, delete two files:
drawable/ic_launcher_background.xml
anddrawable-v24/ic_launcher_foreground.xml
files since those are for the previous app icon. You can uncheck the box Safe delete (with usage search). - Then right click on the res > drawable folder and select New > Image Asset.
- In the Configure Image Asset window make sure Foreground layer is selected.
- Below that, find the Path label.
- Click the folder icon inside the Path text box.
- Find and open the
ic_launcher_foreground.xml
file that you downloaded on your computer.
- Switch to the Background Layer tab.
- Click the Browse icon inside the Path text box.
- Find and open the
ic_launcher_background.xml
file on your computer. No other changes are necessary. - Click Next.
- In the Confirm Icon Path dialog, click Finish. It's OK to overwrite the existing icons.
- For best practices, you can move the new vector drawables
ic_launcher_foreground.xml
andic_launcher_background.xml
into a new resource directory calleddrawable-anydpi-v26
. Adaptive icons were introduced in API 26, so these resources will only be used on devices running API 26 and above (for any dpi). - Delete the
drawable-v24
directory if there's nothing left there. - Run your app and notice the beautiful new app icon in the app drawer!
- As a last step, don't forget to reformat the Kotlin and XML files in the project so your code is cleaner and follows style guidelines.
Congratulations! You created an inspiring Affirmations app.
Using this knowledge of how to display a list of data in an Android app, what can you build next?
4. Solution code
The solution code for the Affirmations app is in the GitHub repository below:
- Navigate to the provided GitHub repository page for the project.
- Verify that the branch name matches the branch name specified in the codelab. For example, in the following screenshot the branch name is main.
- On the GitHub page for the project, click the Code button, which brings up a popup.
- In the popup, click the Download ZIP button to save the project to your computer. Wait for the download to complete.
- Locate the file on your computer (likely in the Downloads folder).
- Double-click the ZIP file to unpack it. This creates a new folder that contains the project files.
Open the project in Android Studio
- Start Android Studio.
- In the Welcome to Android Studio window, click Open.
Note: If Android Studio is already open, instead, select the File > Open menu option.
- In the file browser, navigate to where the unzipped project folder is located (likely in your Downloads folder).
- Double-click on that project folder.
- Wait for Android Studio to open the project.
- Click the Run button
to build and run the app. Make sure it builds as expected.
5. Summary
- To display additional content in a
RecyclerView
, modify the underlying data model class and data source. Then update the list item layout and adapter to set that data onto the views. - Use resource annotations to help ensure that the right type of resource ID is passed into a class constructor.
- Use the Material Components for Android library to have your app more easily follow the recommended Material Design guidelines.
- Use
MaterialCardView
to display content in a Material card. - Small visual tweaks to your app in terms of color and spacing can make the app look more polished and consistent.
6. Learn more
7. Challenge task
In this series of codelabs, you've learned using LinearLayoutManager
with RecyclerView
. RecyclerView
can use different LayoutManagers to layout data differently.
- Change the
layoutManager
property of theRecyclerView
toGridLayoutManager
. - Change the column count to 3.
- Change the adapter layout to visualize data in a grid.