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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Adaptive UI Tutorial for Android with Kotlin
20 mins
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:
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:
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.
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:
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.
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!
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 at the top of MainActivity, below the properties:
companion object {
private const val SELECTED_LOCATION_INDEX = "selectedLocationIndex"
}
Then add the following method to MainActivity below the onCreate()
method:
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(SELECTED_LOCATION_INDEX, locationAdapter.selectedLocationIndex)
}
Add the following to the end of onCreate()
:
if (savedInstanceState != null) {
val index = savedInstanceState.getInt(SELECTED_LOCATION_INDEX)
if (index >= 0 && index < locations.size) {
locationAdapter.selectedLocationIndex = index
loadForecast(locations[index].forecast)
}
}
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 adaptive 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.
Feel free to share your feedback, findings or ask any questions in the comments below or in the forums. Talk to you soon!