Adaptive UI Tutorial for Android with Kotlin

Make your Android app feel at home on any device. Learn how to build an adaptive UI that looks and works well across all devices and screen sizes. By Joe Howard.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

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 in res/layout called forecast_grid.xml and add the following list of images inside a FlexboxLayout:

<?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.
Note: If your Activity/dialog is provided by appcompat from the support library, then the Android system will use an AppCompatImageView under the hood when you specify a regular ImageView in your layout. So, in that case, you could just use a regular ImageView with your vector assets.

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

Android Studio Forecast Grid Color

This is already starting to feel adaptive!

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, add a new method:

private fun mapWeatherToDrawable(forecast: String): Drawable? {
  var drawableId = 0
  when (forecast) {
    "sun" -> drawableId = R.drawable.ic_sun
    "rain" -> drawableId = R.drawable.ic_rain
    "fog" -> drawableId = R.drawable.ic_fog
    "thunder" -> drawableId = R.drawable.ic_thunder
    "cloud" -> drawableId = R.drawable.ic_cloud
    "snow" -> drawableId = R.drawable.ic_snow
  }
  return ContextCompat.getDrawable(this, 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 fun loadForecast(forecast: List<String>) {
  val forecastView = findViewById<View>(R.id.forecast) as FlexboxLayout
  for (i in 0 until forecastView.childCount) {
    val dayView = forecastView.getChildAt(i) as AppCompatImageView
    dayView.setImageDrawable(mapWeatherToDrawable(forecast[i]))
  }
}

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

loadForecast(location.forecast)

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 Adaptive 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, in the layout editor click on the orientation icon, and choose Switch to Landscape:

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 adaptive 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 ConstraintLayout, though not for much longer :]. Add the following to reuse the weather forecast layout and RecyclerView 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.