Android fundamentals 05.3: Adaptive layouts

1. Welcome

Introduction

The MaterialMe app that you created in a previous chapter doesn't properly handle device-orientation changes from portrait (vertical) mode to landscape (horizontal) mode. On a tablet, the font sizes are too small, and the space is not used efficiently.

The Android framework has a way to solve both issues. Resource qualifiers allow the Android runtime to use alternate XML resource files depending on the device configuration—the orientation, the locale, and other qualifiers. For a full list of available qualifiers, see Providing alternative resources.

In this practical you optimize the use of space in the MaterialMe app so that the app works well in landscape mode and on tablets. In another practical on using the layout editor, you learned how to create layout variants for horizontal orientation and tablets. In this practical you use an adaptive layout, which is a layout that works well for different screen sizes and orientations, different devices, different locales and languages, and different versions of Android.

What you should already know

You should be able to:

  • Create and run apps in Android Studio.
  • Create and edit UI elements using the layout editor.
  • Edit XML layout code, and access elements from your Java code.
  • Create a click handler for a Button click.
  • Use drawables, styles, and themes.
  • Extract text to a string resource and a dimension to a dimension resource.

What you'll learn

  • How to create alternate resources for devices in landscape mode.
  • How to create alternate resources for tablets.
  • How to create alternate resources for different locales.

What you'll do

  • Update the MaterialMe app for better use of space in landscape mode.
  • Add an alternative layout for tablets.
  • Localize the content of your app.

2. App overview

The updated MaterialMe app will include an improved layout for landscape mode on phones. It will also include improved layouts for portrait and landscape modes on tablets, and it will offer localized content for users outside the United States.

The screenshot below shows a phone running the updated MaterialMe app in landscape orientation:

Phone running the MaterialMe app with landscape resource qualifiers

The screenshot below shows a tablet running the updated MaterialMe app, with qualifiers to show two columns when running in portrait orientation:

Tablet running the MaterialMe app with qualifiers to show two columns in portrait mode

3. Task 1: Support landscape orientation

You may recall that when the user changes the orientation of the device, the Android framework destroys and recreates the current activity. The new orientation often has different layout requirements than the original one. For example, the MaterialMe app looks good in portrait mode, but does not make optimal use of the screen in landscape mode. With the larger width in landscape mode, the image in each list item overwhelms the text providing a poor user experience.

Landscape Mode before resource qualifiers

In this task, you create an alternative resource file that will change the appearance of the app when it is used in landscape orientation.

1.1 Change to a GridLayoutManager

Layouts that contain list items often look unbalanced in landscape mode when the list items include full-width images. One good solution is to use a grid instead of a linear list when displaying CardView elements in landscape mode.

Recall that the items in a RecyclerView list are placed using a LayoutManager; until now, you have been using the LinearLayoutManager which lays out each item in a vertical or horizontal scrolling list. GridLayoutManager is another layout manager that displays items in a grid, rather than a list.

When you create a GridLayoutManager, you supply two parameters: the app context, and an integer representing the number of columns. You can change the number of columns programmatically, which gives you flexibility in designing adaptive layouts. In this case, the number of columns integer should be 1 in portrait orientation (single column) and 2 when in landscape mode. Notice that when the number of columns is 1, a GridLayoutManager behaves similar to a LinearLayoutManager.

This practical builds on the MaterialMe app from the previous practical.

  1. Continue developing your version of the MaterialMe app, or download MaterialMe. If you decide to make a copy of the MaterialMe project to preserve the version from the previous practical, rename the copied version MaterialMe-Resource.
  2. Create a new resources file called integers.xml. To do this, open the res folder in the Project > Android pane, right-click (or Control-click) on the values folder, and select New > Values resource file.
  3. Name the file integers.xml and click OK.
  4. Create an integer constant between the <resources> tags called grid_column_count and set it equal to 1:
<integer name="grid_column_count">1</integer>
  1. Create another values resource file, again called integers.xml; however, the name will be modified as you add resource qualifiers from the Available qualifiers pane. The resource qualifiers are used to label resource configurations for various situations.
  2. Select Orientation in the Available qualifiers pane, and press the >> symbol in the middle of the dialog to assign this qualifier.
  3. Change the Screen orientation menu to Landscape, and notice how the directory name values-land appears. This is the essence of resource qualifiers: the directory name tells Android when to use that specific layout file. In this case, that is when the phone is rotated to landscape mode.
  4. Click OK to generate the new layout file.
  5. Copy the integer constant you created into this new resource file, but change the value to 2.

You should now have two individual integers.xml files grouped into an integers.xml folder in the Project > Android pane. The second file is labeled with the qualifier you selected, which is land in this case. The qualifier appears in parentheses: integers.xml (land).

1.2 Modify MainActivity

  1. Open MainActivity, and add code to onCreate() to get the integer from the integers.xml resource file:
int gridColumnCount = 
        getResources().getInteger(R.integer.grid_column_count);

The Android runtime will take care of deciding which integers.xml file to use, depending on the state of the device.

  1. Change the LinearLayoutManager for the RecyclerView to a GridLayoutManager, passing in the context and the newly created integer:
mRecyclerView.setLayoutManager(new 
                     GridLayoutManager(this, gridColumnCount));
  1. Run the app and rotate the device. The number of columns changes automatically with the orientation of the device.

The Material Me app with landscape resource qualifiers

When using the app in landscape mode, you will notice that the swipe to dismiss functionality is no longer intuitive, since the items are now in a grid rather than a single column. In the next steps, you turn off the swipe action if there is more than one column.

  1. Use the gridColumnCount variable to disable the swipe action (set swipeDirs to zero) when there is more than one column:
int swipeDirs;
if(gridColumnCount > 1){
   swipeDirs = 0;
} else {
   swipeDirs = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
}
  1. Use swipeDirs in place of the swipe direction arguments (ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) for ItemTouchHelper.SimpleCallback():
ItemTouchHelper helper = new ItemTouchHelper(new 
          ItemTouchHelper.SimpleCallback(ItemTouchHelper.LEFT | 
                ItemTouchHelper.RIGHT |
                ItemTouchHelper.DOWN | ItemTouchHelper.UP,
                swipeDirs) {
  1. Run the app and rotate the device. In landscape (horizontal) orientation, the user can no longer swipe to delete a card.

4. Task 2 : Support tablets

Although you have modified the app to look better in landscape mode, running it on a tablet with physically larger dimensions results in all the text appearing too small. Also when the device is in landscape orientation, the screen is not used efficiently; three columns would be more appropriate for a tablet-sized screen in landscape mode.

In this task, you add additional resource qualifiers to change the appearance of the app when used on tablets.

Tablet running the MaterialMe app with only a landscape qualifier (2 columns)

2.1 Adapt the layout to tablets

In this step, you create different resource qualifiers to maximize screen use for tablet-sized devices, increasing the column count to 2 for portrait (vertical) orientation and 3 for landscape (horizontal) orientation.

The resource qualifier you need depends on your specific requirements. When creating a new resource file, there are several qualifiers in the Available qualifiers pane that you can use to select the correct conditions:

  • Smallest Screen Width: This qualifier is used most frequently to select for tablets. It is defined by the smallest width of the device (regardless of orientation), which removes the ambiguity when talking about "height" and "width" since some devices are traditionally held in landscape mode, and others in portrait. Anything with a smallest width of at least 600dp is considered a tablet.
  • Screen Width: The screen width is the effective width of the device, regardless of the orientation. The width changes when the device is rotated, since the effective height and width of the device are switched.
  • Screen Height: Same as Screen Width, except it uses the effective height instead of the effective width.

To start this task:

  1. Create an integers.xml resource file which uses the Smallest Screen Width qualifier with the value set to 600. Android uses this file whenever the app runs on a tablet.
  2. Copy the code from the integers.xml (land) file (it has a grid count of 2) and paste it in the new integers.xml (sw600dp) file.
  3. Create another integers.xml file that includes both the Smallest Screen Width qualifier set to 600, and the Orientation qualifier set to Landscape. Android uses the resulting integers.xml (sw600dp-land) file when the app runs on a tablet in landscape mode.
  4. Copy the code from the integers.xml (land) file and paste it in the new integers.xml (sw600dp-land) file.
  5. Change the grid_column_count variable to 3 in the integers.xml (sw600dp-land) file.
  6. Run the app on a tablet or tablet emulator, and rotate it to landscape mode. The app should show three columns of cards, as shown in the first figure below. Rotate it to portrait mode, and the app should show two columns of cards, as shown in the second figure below. With these resource qualifier files, the app uses the screen real estate much more effectively.

Tablet running the MaterialMe app with qualifiers to show 3 columns in landscape mode

Tablet running the MaterialMe app with qualifiers to show 2 columns in portrait mode

2.2 Update the tablet list item styles

At this point, your app changes the number of columns in a GridLayoutManager to fit the orientation of the device and maximize the use of the screen. However, the TextView elements that appeared correctly-sized on a phone's screen now appear too small for the larger screen of a tablet.

To fix this, you extract the TextAppearance styles from the layout resource files into the styles.xml resource file. You will also use resource qualifiers to create additional styles.xml files for tablets.

Follow these steps to add the TextAppearance styles:

  1. Open styles.xml and add the following styles:
<style name="SportsDetailText" 
                   parent="TextAppearance.AppCompat.Subhead"/>
<style name="SportsTitle" 
                   parent="TextAppearance.AppCompat.Headline"/>
  1. Create a new values resource file called styles.xml that uses the Smallest Screen Width qualifier with a value of 600 for tablets.
  2. Copy all styles from the original styles.xml file into the new styles.xml (sw600dp) file.
  3. In styles.xml (sw600dp), change the parent of the SportsTitle style to "TextAppearance.AppCompat.Display1":
<style name="SportsTitle" 
                   parent="TextAppearance.AppCompat.Display1"/>
  1. The Android predefined Display1 style uses the textColorSecondary value from the current theme (ThemeOverlay.AppCompat.Dark), which in this case is a light gray color. The light gray color does not show up well on the banner images in your app. To correct this add an "android:textColor" attribute to the SportsTitle style and set it to "?android:textColorPrimary":
<style name="SportsTitle" 
                  parent="TextAppearance.AppCompat.Display1">
    <item name=
          "android:textColor">?android:textColorPrimary</item>
</style>

The question mark tells Android runtime to find the value in the theme applied to the View. In this example the theme is ThemeOverlay.AppCompat.Dark in which the textColorPrimary attribute is white.

  1. Change the parent of SportsDetailText style to "TextAppearance.AppCompat.Headline".
  2. To update the style of the TextView elements, open list_item.xml, and change the style attribute of the title TextView to @style/SportsTitle:
style="@style/SportsTitle"
  1. Change the style attribute of the newsTitle and subTitle TextView elements to @style/SportsDetailText.
  2. Run your app on a tablet or tablet emulator. Each list item now has a larger text size on the tablet.

2.3 Update the tablet sports detail styles

You have now fixed the display for the MainActivity, which lists all the Sports CardView elements. The DetailActivity still has the same font sizes on tablets and phones.

  1. Add the following style in the styles.xml file for the detail title:
<style name="SportsDetailTitle" 
                   parent="TextAppearance.AppCompat.Headline"/>
  1. Add the following style in the styles.xml (sw600dp) file for the detail title:
<style name="SportsDetailTitle" 
                   parent="TextAppearance.AppCompat.Display3"/>
  1. Open activity_detail.xml, and change the style attribute of both the newsTitleDetail and subTitleDetail TextView elements to the new SportsDetailText style you created in a previous step:
style="@style/SportsDetailText"
  1. In activity_detail.xml, change the style attribute of the titleDetail TextView element to the new SportsDetailTitle style you created:
style="@style/SportsDetailTitle"
  1. Run your app. All of the text is now larger on the tablet, which greatly improves the user experience of your application.

Tablet running the MaterialMe app with styles to make the text larger

5. Task 3: Localize your app

A "locale" represents a specific geographic, political or cultural region of the world. Resource qualifiers can be used to provide alternate resources based on the users' locale. Just as for orientation and screen width, Android provides the ability to include separate resource files for different locales. In this step, you modify your strings.xml file to be a little more international.

3.1 Add a localized strings.xml file

You may have noticed that the sports information contained in this app is designed for users from the U.S. The app uses the term "soccer" to represent a sport known as "football" everywhere else in the world.

To make your app more internationalized, you can provide a locale-specific strings.xml file. This alternative-resource file will show the word "soccer" to users in the U.S. The generic strings.xml file will show the word "football" to users in all other locales.

  1. Create a new values resource file.
  2. Call the file strings.xml and select Locale from the list of available qualifiers. The Language and Specific Region Only panes appear.

Clicking the Locale qualifier presents choices for language and region.

  1. In the Language pane, select en: English.
  2. In the Specific Region Only pane, select US: United States and click OK. Android Studio creates a specific values directory in your project directories for the U.S. locale, called values-en-rUS. In the Project > Android pane, the strings.xml file in this directory appears as strings.xml (en-rUS) within the newly created strings.xml folder (with a U.S. flag icon).

The generic strings.xml file and the locale-specific strings.xml (en-rUS) file in the Project > Android pane

  1. Copy all string resources of the generic strings.xml file (now located in the strings.xml folder) to strings.xml (en-rUS).
  2. In the generic strings.xml file, change the Soccer item in the sports_titles array to Football, and change the Soccer news text in the sports_info array to Football news.

3.2 Run the app in different locales

In order to see the locale-specific differences, you can start your device or emulator, and change its language and locale to U.S. English (if not already set). In U.S. English, you should see "Soccer". You can then switch to any language and locale other than U.S. English, and run the app again. You should then see "Football".

  1. To switch the preferred language in your device or emulator, open the Settings app.

If your Android device is in another language, look for the gear icon: [IMAGEINFO]: ic_gear_icon_settings.png

  1. Find the Languages & input settings in the Settings app, and choose Languages. Languages is the first choice on the Languages & input screen.

Remember the globe icon for the Languages & input choice, so that you can find it again if you switch to a language you do not understand: [IMAGEINFO]: ic_globe_icon_languages_and_input.png

  1. For devices and emulators running a version of Android previous to Android 7, choose Language on the Languages & input screen, select a language and locale such as Français (France), and skip the following steps.

(In versions of Android previous to Android 7, users can choose only one language. In Android 7 and newer versions, users can choose multiple languages and arrange them by preference. The primary language is numbered 1, as shown in the following figure, followed by lower-preference languages.)

  1. For devices and emulators running Android 7 or newer, choose Languages on the Languages & input screen, select a language such as Français (France), and use the move icon on the right side of the Language preferences screen to drag Français (France) to the top of the list.

Dragging French to the top of the preferred languages list for Android 7 and newer versions

  1. Run the app with your device or emulator. In U.S. English, you should see "Soccer".
  2. Switch to any language and locale other than U.S. English, and run the app again. You should then see "Football".

U.S. English version (left side) shows

This example does not show a translated word for "Football" depending on the language. For a lesson in localizing an app with translations, see the Advanced Android Development — Practicals.

6. Solution code

Android Studio project: MaterialMe-Resource

7. Coding challenge

Challenge 1: It turns out that several countries other than the U.S. use "soccer" instead of "football". Research these countries and add localized strings resources for them.

Challenge 2: Use the localization techniques you learned in Task 3 in combination with Google translate to translate all of the strings in your app into a different language.

8. Summary

  • GridLayoutManager is a layout manager that handles two-dimensional scrolling lists.
  • You can dynamically change the number of columns in a GridLayoutManager.
  • The Android runtime uses alternative configuration files, depending on the runtime environment of the device running your app. For example, the runtime might use alternative configuration files for different device layouts, screen dimensions, locale, countries, or keyboard types.
  • In your code, you create these alternative resources for the Android runtime to use. The resources are located in files that have resource qualifiers as part of their names.
  • The format for a directory holding alternative resource files is <resource_name>-<qualifier>.
  • You can qualify any file in your res directory in this way.

9. Related concepts

The related concept is in 5.3: Resources for adaptive layouts.

10. Learn more

Android Studio documentation: Meet Android Studio

Android developer documentation:

Material Design:

11. Homework

This section lists possible homework assignments for students who are working through this codelab as part of a course led by an instructor. It's up to the instructor to do the following:

  • Assign homework if required.
  • Communicate to students how to submit homework assignments.
  • Grade the homework assignments.

Instructors can use these suggestions as little or as much as they want, and should feel free to assign any other homework they feel is appropriate.

If you're working through this codelab on your own, feel free to use these homework assignments to test your knowledge.

Build and run an app

Modify the RecyclerView app to use a GridLayoutManager with the following column counts:

  • For a phone: 1 column in portrait orientation, 2 columns in landscape orientation
  • For a tablet: 2 columns in portrait orientation, 3 columns in landscape orientation

The screenshot below shows a resource-qualified RecyclerView on a phone in portrait orientation:

Resource-qualified RecyclerView vertical phone

The screenshot below shows a resource-qualified RecyclerView on a phone in landscape orientation:

Resource-qualified RecyclerView horizontal phone

The screenshot below shows a resource-qualified RecyclerView on a tablet in landscape orientation:

Resource-qualified RecyclerView horizontal tablet

Answer these questions

Question 1

Which resource qualifier is used most frequently to select for tablets? Choose one:

  • Orientation
  • Screen width
  • Screen height
  • Smallest screen width

Question 2

Which folder would hold the strings.xml file for translation into French for Canada? Choose one:

  • res/values-fr-rFR/
  • res/values-ca-rFR/
  • res/values-fr-rCA/
  • res/values-en-rFR/

Question 3

Which folder is for XML files that contain strings, integers, and colors? Choose one:

  • res/layout
  • res/mipmap
  • res/raw
  • res/values

Submit your app for grading

Guidance for graders

Check that the app has the following features:

  • For phones and tablets in both landscape and portrait modes, the code includes resource-qualified values files that contain the integer for the column count.
  • The app uses getResources().getInteger() to retrieve a value from a resource file, then uses the value as the column count for grid layout.

12. Next codelab

To find the next practical codelab in the Android Developer Fundamentals (V2) course, see Codelabs for Android Developer Fundamentals (V2).

For an overview of the course, including links to the concept chapters, apps, and slides, see Android Developer Fundamentals (Version 2).