Responsive UI Tutorial for Android

James Nocentini
Update 4/5/17: Updated for Android Studio 2.2.3 by James Nocentini. Original tutorial also by James.

Responsive UI Tutorial for Android

Android runs on a wide variety of devices that offer different screen sizes and densities. Because of this, it is important for Android apps to have a responsive UI that can adapt to these different screens. Since the early days of the Android platform, system APIs have provided very powerful abstractions to design responsive UIs, also known as adaptive layouts.

This is an update to our adaptive UI in Android tutorial which will show you how to build apps that work across different devices by dealing with the fragmentation in the Android device market. You’ll learn about:

  • Configuration qualifiers
  • Alternative layouts and drawables
  • And layout previews in Android Studio — an immensely useful tool

What would a tutorial be without something to tinker with? It’d be pretty boring. So, you’ll build the user interface for a simple weather app completely from scratch! When you’re done, the screen will display an image, text labels and a map in three different configurations. Apps look so cool and well built when they have a responsive UI.

Responsive UI Tutorial for Android

Getting Started

Download the starter project named Adaptive Weather here, and open it in Android Studio. Then build and run.

The app displays a simple RecyclerView listing several cities.

cities

To learn all about RecyclerViews, we recommend reading our Android RecyclerView tutorial.

Open the build.gradle file of the app module to declare the following dependency:

dependencies {
    ...
    compile 'com.google.android:flexbox:0.2.5'
}

Google FlexBox provides an implementation of the FlexBox specification on the Android platform. As you will see later on, it is a very useful tool for designing responsive layouts. And combining it with Android’s resource qualifier system makes it even more powerful!

Note: The Android platform is constantly updated and the version numbers may have increased since we published this tutorial. You can find details of the different versions, including the most recent on the support library pages on the Android developer site.

During this tutorial, you’ll often switch between the Android and Project modes in the Project navigator. Generally speaking:

  • Android mode is the default when working within Android Studio because it provides a clean and simple file structure.
  • Project mode is also necessary for building alternative layouts.
  • Android Studio Project Navigator

    Weather Icons

    Android devices have different screen densities, and for that reason it’s a good practice to import static images in multiple sizes. This is one way Android’s system APIs provide a way to create responsive UIs. As described in the Supporting Multiple Screens guide, the categories of screen densities are:

    • ldpi (low) ~120dpi
    • mdpi (medium) ~160dpi
    • hdpi (high) ~240dpi
    • xhdpi (extra-high) ~320dpi
    • xxhdpi (extra-extra-high) ~480dpi
    • xxxhdpi (extra-extra-extra-high) ~640dpi

    Whilst some UI editors make it easy to export images in different sizes, we will be exploring a different approach in this tutorial. Android Studio recently added support for Vector Drawables. This means that all your assets can be imported once and will be scaled at runtime depending on the device configuration (screen size and orientation).

    Download the Weather Icons and extract. In Android Studio right-click on res/drawable and click on the New\Vector Asset menu item:

    Android Studio Create Vector Asset

    Select Local file (SVG, PSD) under Asset Type. From the filesystem location chooser under Path locate the weather-icons folder and choose the first icon, cloud.svg. Make sure to check the Override under Size setting otherwise your icons will look a bit distorted later on (¯\_(ツ)_/¯). Click Next and Finish:

    Android Studio Configure Vector Asset

    Now you should see your icon in Android Studio as res/drawable/ic_cloud.xml. Repeat the same operations for the other icons: fog, rain, snow, sun, thunder.

    Finally, enable the use of Vector Drawables in the app module’s build.gradle as follows:

    android {
        ...
    
        defaultConfig {
            ...
            vectorDrawables.useSupportLibrary = true
        }
    }
    

    With scalable assets now in place in the project, you’re ready to start customizing the layouts.

    Building layouts

    With the dependencies declared, you get to shift your focus to building some layouts!

    This simple application only contains one screen, which is represented by MainActivity. From the Project navigator, open res/layout/activity_main.xml. Click on the Preview button on the right side to see it in action.

    An activity comprises a Java class — in this case MainActivity.java — and a layout file. In fact, one activity can have several layouts, as you’ll see shortly. For now, it’s important to remember that the existing layout file, activity_main.xml, is the default layout.

    Forecast Grid View

    First, define the default layout for your main activity. To start this, open res/values/colors.xml and replace its content with the following:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
      <color name="color_primary">#9B26AF</color>
      <color name="color_primary_dark">#89229b</color>
      <color name="text_color_primary">#ffffff</color>
      <color name="forecast_grid_background">#89bef2</color>
    </resources>
    

    Here you’re overriding the default Material theme colors and providing a background color for the forecast grid. Next, right-click on the values folder and select the New\Value resource file menu:

    Android Studio New Resource File

    Enter fractions.xml for the file name and paste the following:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
      <item name="weather_icon" type="fraction">33%</item>
    </resources>
    

    Here you’re specifying that the width taken by each icon should be 1/3 of the total width.

    Next, create a new layout called forecast_grid.xml and add the following list of images:

    <?xml version="1.0" encoding="utf-8"?>
    <com.google.android.flexbox.FlexboxLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/forecast"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/forecast_grid_background"
        app:alignItems="center"
        app:flexWrap="wrap"
        app:justifyContent="space_around">
    
      <android.support.v7.widget.AppCompatImageView
          android:id="@+id/day1"
          android:layout_width="wrap_content"
          android:layout_height="60dp"
          app:layout_flexBasisPercent="@fraction/weather_icon"
          app:srcCompat="@drawable/ic_thunder"/>
      <android.support.v7.widget.AppCompatImageView
          android:id="@+id/day2"
          android:layout_width="wrap_content"
          android:layout_height="60dp"
          app:layout_flexBasisPercent="@fraction/weather_icon"
          app:srcCompat="@drawable/ic_fog"/>
      <android.support.v7.widget.AppCompatImageView
          android:id="@+id/day3"
          android:layout_width="wrap_content"
          android:layout_height="60dp"
          app:layout_flexBasisPercent="@fraction/weather_icon"
          app:srcCompat="@drawable/ic_rain"/>
      <android.support.v7.widget.AppCompatImageView
          android:id="@+id/day4"
          android:layout_width="wrap_content"
          android:layout_height="60dp"
          app:layout_flexBasisPercent="@fraction/weather_icon"
          app:srcCompat="@drawable/ic_snow"/>
      <android.support.v7.widget.AppCompatImageView
          android:id="@+id/day5"
          android:layout_width="wrap_content"
          android:layout_height="60dp"
          app:layout_flexBasisPercent="@fraction/weather_icon"
          app:srcCompat="@drawable/ic_cloud"/>
      <android.support.v7.widget.AppCompatImageView
          android:id="@+id/day6"
          android:layout_width="wrap_content"
          android:layout_height="60dp"
          app:layout_flexBasisPercent="@fraction/weather_icon"
          app:srcCompat="@drawable/ic_sun"/>
    
    </com.google.android.flexbox.FlexboxLayout>
    

    There are a couple things to note with the above block:

    1. You’re using the com.google.android.flexbox.FlexboxLayout resource to layout the icons on the screen.
    2. You’re using the android.support.v7.widget.AppCompatImageView resource to draw the weather icons on the screen. You would normally use the ImageView resource with plain images (.png, .jpg) but for Vector Drawables you must use this component instead.

    In the Preview pane, you see should the weather icons aligned perfectly:

    Android Studio Forecast Grid Color

    This is already starting to feel responsive. Instead of positioning the icons with margins or using a relative layout you have used the FlexBox properties to spread them symmetrically. If you remove a middle icon for example, the remaining ones will automatically shift to the left to fill in the empty space. This is the true power of using FlexBox in layouts. The forecast grid is now ready to be used in your default layout for the main activity.

    Main Activity

    Open res/layout/activity_main.xml and replace its contents with the following:

    <?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="match_parent"
                  android:orientation="vertical">
    
      <include layout="@layout/forecast_grid"
               android:layout_width="match_parent"
               android:layout_height="0dp"
               android:layout_weight="1"/>
    
      <android.support.v7.widget.RecyclerView
          android:id="@+id/list"
          android:layout_width="match_parent"
          android:layout_height="0dp"
          android:layout_weight="1"/>
    
    </LinearLayout>
    

    Here’s what is happening in this layout:

    • Orientation for the LinearLayout is set to vertical
    • Dimensions: using the layout_weight XML attribute you’re setting each child view to take half of the screen height
    • Layout reuse: using the include XML tag you’re placing the forecast grid on the top half by referencing the forecast_grid.xml layout. This is one of the core functionalities to creating different layouts without duplicating the code.

    Notice that the preview in the editor gets instantly updated. At this point you still haven’t deployed the application to a device or emulator which is astonishing.

    Android Studio Preview Layout

    Build and run. You should now see the weather icons above the list of locations.

    Android Simulator Recycler View Selection 1

    Updating the Weather Forecast

    Take a look at the static JSON data in assets/data.json. The forecast for a given location is represented as an array of strings. You could create another RecyclerView with a GridLayout to dynamically create the forecast, but that’s asking for trouble :]. Instead you will write a method that maps each possible forecast value to a corresponding drawable icon.

    In MainActivity.java, add a new method:

    private Drawable mapWeatherToDrawable(String forecast) {
      int drawableId = 0;
      switch (forecast) {
        case "sun":
          drawableId = R.drawable.ic_sun;
          break;
        case "rain":
          drawableId = R.drawable.ic_rain;
          break;
        case "fog":
          drawableId = R.drawable.ic_fog;
          break;
        case "thunder":
          drawableId = R.drawable.ic_thunder;
          break;
        case "cloud":
          drawableId = R.drawable.ic_cloud;
          break;
        case "snow":
          drawableId = R.drawable.ic_snow;
          break;
      }
      return getResources().getDrawable(drawableId);
    }
    

    Now you are ready to write the code that responds to the click event of a RecyclerView row. Add the following method to MainActivity:

    private void loadForecast(List<String> forecast) {
      FlexboxLayout forecastView = (FlexboxLayout) findViewById(R.id.forecast);
      for (int i = 0; i < forecastView.getChildCount(); i++) {
        AppCompatImageView dayView = (AppCompatImageView) forecastView.getChildAt(i);
        dayView.setImageDrawable(mapWeatherToDrawable(forecast.get(i)));
      }
    }
    

    Then find // TODO in MainActivity and replace it with the following:

    loadForecast(location.getForecast());
    

    Build and run. Click on a location name and notice the weather forecast gets updated:

    Android Simulator Recycler View Selection 2

    Good job, what a beautiful looking weather application! The weather in San Francisco isn't looking so beautiful though :].

    Creating Responsive UI: Landscape Layout

    So far, you built this application with the portrait mode in mind but let's take a look at what happens when the phone is rotated to landscape. Open activity_main.xml and in the layout editor click on the orientation icon:

    Android Studio Preview Orientation

    At this stage, you could run the app on multiple Android devices or simulators. But this method of testing alternative layouts is time consuming and repetitive at best, and error prone at worst. There must be another way.

    Thankfully, Android Studio has extensive previewing capabilities. Open the default activity_main.xml file, and hover your mouse over the bottom right corner of the screen to resize the layout. Notice that upon clicking the handle, Android Studio automatically displayed guides for different device sizes.

    Android Studio Preview Dragging

    Ugh -- landscape mode is none too kind to your design. Let's try to have both views side by side instead. To tell the system which resource to pick for a given dimension, you place the layout resource in a folder named in a particular way. The system will pick the correct activity layout for the current device's screen dimensions. This way, you will have responsive UIs for your app.

    Layout qualifiers

    Back in Android Studio, right-click on res/layout and click on the New\Layout resource file menu:

    Android Studio Create New Layout 1

    Name the file activity_main and add the landscape resource qualifier:

    Android Studio Create New Layout 2

    The layout editor now shows a blank screen for the landscape mode because it picked the newly-created layout file layout-land/activity_main.xml. This only contains an empty LinearLayout, though not for much longer :]. Add the following to reuse the weather forecast layout and Recycler View in a horizontal orientation this time.

    <?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="match_parent"
                  android:orientation="horizontal">
    
      <include layout="@layout/forecast_grid" 
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1" />
    
      <android.support.v7.widget.RecyclerView
          android:id="@+id/list"
          android:layout_width="0dp"
          android:layout_height="match_parent"
          android:layout_weight="1"/>
    
    </LinearLayout>
    

    And the layout editor now shows all your elements in landscape orientation.

    Android Studio Preview Landscape

    Well done! You have created the first layout qualifier in this application. There are layout qualifiers for plenty of other configurations (screen width, height, aspect ratio etc.). In the next section we will modify the landscape layout even further with just a one-line change.

    Resource qualifiers

    Another enhancement you could make to the layout is to organize the weather icons as 2 columns and 3 rows as opposed to the current 3 columns and 2 rows layout. We could duplicate the forecast_grid.xml layout, but then it would be duplicated code and harder to maintain. The width occupied by each weather icon in relation to the FlexBox view width is determined by the layout_flexBasisPercent attribute:

    <android.support.v7.widget.AppCompatImageView
        android:id="@+id/day1"
        android:layout_width="wrap_content"
        android:layout_height="60dp"
        app:layout_flexBasisPercent="@fraction/weather_icon"
        app:srcCompat="@drawable/ic_thunder"/>
    

    The value is a fraction type and is currently equal to 33% in the resource qualifier file res/values/fractions.xml. Following the same approach to creating a landscape layout, you can create resource files for the landscape configuration. Right-click on res/values and select the New\Values resource file menu item. Name the file fractions and add the landscape orientation qualifier:

    Android Studio New Fractions File

    Inside the resources XML tag, add the following:

    <item name="weather_icon" type="fraction">49%</item>
    

    Return the main activity layout in landscape mode and notice the weather icons are laid out on 2 columns and 3 rows:

    Android Preview Landscape Layout updated

    Well done! You can pause here and appreciate the fact that once again, you didn't have to deploy the application to achieve this result. Of course now you should build & run though and make sure it works :]

    The configuration qualifiers can be applied to any attribute type in your XML layout (font size, colors, margins etc.).

    Extra Large Layout

    Return to the portrait orientation in the layout editor and drag the screen size all the way to the X-Large size range.

    Android Preview Drag X-large

    For devices with that much screen real estate, you could show all the weather icons on 1 row. Go ahead and right-click on res/values and select the New\Values resource file menu item. Name the file fractions and add the X-Large size qualifier:

    Android Studio Create New Fractions File

    Add the following inside the resources XML tag:

    <item name="weather_icon" type="fraction">16%</item>
    

    Return to the layout editor and notice that all the weather icons are aligned on 1 row.

    Android Preview X-large Layout

    Configuration Calculations

    Don't worry, the content in this section isn't as scary as the title makes it sound. When the user interacts with the application, the layout state changes over time (rows are selected, input fields populated with text etc.). When the layout changes (for example when the orientation changes), the existing layout is thrown away a new layout is inflated. But the system has no way of knowing how to restore the state because the two layouts could be completely different as far as it knows.

    To see a live example of this in action, build and run the application. Select a location then change the orientation and notice the location isn't selected anymore!

    Android Simulator Example Run 1

    If you are not already surprised that the forecast in London is sunny all week then you may also notice that the selected row was deselected after switching to landscape.

    To fix this, you will hook into the activity lifecycle methods to save the selected location to a bundle and retrieve after the screen rotation.

    Add the following field at the top of MainActivity.java:

    private static final String SELECTED_LOCATION_INDEX = "selectedLocationIndex";
    

    Then add the following method to MainActivity:

    @Override
    protected void onSaveInstanceState(Bundle outState) {
      super.onSaveInstanceState(outState);
      outState.putInt(SELECTED_LOCATION_INDEX, mLocationAdapter.getSelectedLocationIndex());
    }
    

    Add the following to the end of onCreate():

    if (savedInstanceState != null) {
      int index = savedInstanceState.getInt(SELECTED_LOCATION_INDEX);
      mLocationAdapter.setSelectedLocationIndex(index);
      loadForecast(mLocations.get(index).getForecast());
    }
    

    Build and run again and this time the location remains selected across configuration changes. Hooray!

    Where to Go From Here

    Well done! You’ve built your first Android app with adaptive layouts and you learned how activities can make use of multiple layouts. You learned how drawables work with different displays, and how to make your app come to life on practically any Android device.

    Of course, there's a lot more to Android than layouts, and no shortage of ways to build on the adaptive UI principles you discovered in this responsive UI for Android tutorial. To learn more, check out Google's guidelines on the best UI practices. If you want, you can challenge yourself by trying the following:

    • Use another available qualifier to have yet another type of layout. For example, what if you'd like to have a different background color based on the locale qualifier?
    • Or, try using size qualifier on other resources, such as strings. You could add a TextView which shows a short message, or a longer message with the same name if the screen is in landscape?

    Get the full source code for this project as a downloadable zip or as a repo on GitHub.

    Feel free to share your feedback, findings or ask any questions in the comments below or in the forums. Talk to you soon!

Team

Each tutorial at www.raywenderlich.com is created by a team of dedicated developers so that it meets our high quality standards. The team members who worked on this tutorial are:

James Nocentini

James is an Android and iOS developer based in Brighton. He is a big fan of the offline-first approach for building apps and currently works at Couchbase. Always learning and aiming to write useful tutorials and workshops for the community.

Feel free to reach out on Twitter or Github.

Other Items of Interest

Save time.
Learn more with our video courses.

raywenderlich.com Weekly

Sign up to receive the latest tutorials from raywenderlich.com each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 20 total!

Swift Team

... 15 total!

iOS Team

... 42 total!

Android Team

... 16 total!

macOS Team

... 11 total!

Unity Team

... 11 total!

Articles Team

... 12 total!

Resident Authors Team

... 15 total!