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.

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.

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 to 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