Android apps need to support an ever expanding ecosystem of device form factors. An app's UI should be responsive to fit a wide range of screen sizes as well as different orientations and device states.
Responsive UI centers on the principles of flexibility and continuity.
Flexibility refers to layouts making optimal use of the available space and adjusting when the available space changes. Adjustments can take many forms: simply growing the size of a single view, repositioning views so that they are in more accessible locations, showing or hiding additional views, or a combination of these.
Continuity refers to having a seamless user experience while transitioning from one window size to another. Whatever experience the user is currently engaged in should continue without interruption. Because a change in size could be accompanied by destruction and recreation of the entire view hierarchy, it is important that the user not lose their place or their data.
Things to avoid
Avoid using physical, hardware values for making layout decisions. It might be tempting to make decisions based on a fixed value, but in many situations these values aren't useful for determining the space your UI can work with.
On tablets, an app might be running in multi-window mode, which means the app shares the screen with another app. On Chrome OS, an app might be in a resizable window. There can even be more than one physical screen, such as with a foldable device or a device that has multiple displays. In all of these cases, the physical screen size isn't relevant for deciding how to display content.
For the same reason, avoid locking your app to a specific orientation or aspect ratio. While the device itself may be in a particular orientation, your app may be in a different orientation based solely on the size of its window. For instance, on a tablet in landscape while using multi-window mode, an app can be in portrait because it is taller than it is wide.
Also, avoid trying to determine whether the device is a phone or tablet. What specifically qualifies as a tablet is somewhat subjective: is it based on having a certain size, or aspect ratio, or combination of size and aspect ratio? As new form factors emerge, these assumptions can easily change, and this distinction loses importance.
Instead of doing any of the above, use breakpoints and window size classes.
Breakpoints and window size classes
The actual portion of the screen that is allocated to your app is the app's window. It may occupy the full screen or part of the screen, so use the window size when making high-level decisions about your app's layout.
When designing for multiple form factors, find threshold values where these high-level decisions branch in different directions. To this end, Material Design guidelines provide breakpoints for width and height, which allows you to map raw sizes into discrete, standardized groups referred to as window size classes. Due to the ubiquity of vertical scrolling, most apps primarily care about the width size classes, so most apps can be optimized for all screen sizes by handling just a few breakpoints. (For more information on window size classes, see Support different screen sizes.)
Persistent UI elements
The Material Design layout guidelines define regions for app bars, navigation, and content. Typically, the first two are persistent UI elements at (or very near) the root of the view hierarchy. Note that "persistent" doesn't necessarily mean the view is always visible, but rather that it stays in place while other content views might move or change. For instance, a navigation element could be in a sliding drawer that is currently off screen, but the drawer is always there.
Persistent elements can be responsive, and usually they take up either the full width or the full height of the window, so prefer using size classes to decide where to place them. This will delineate the space that is left for content. In the following snippet, the activity uses a bottom bar for compact screens and a top app bar for larger screens. The qualified layouts use width breakpoints as described earlier.
<!-- res/layout/main_activity.xml --> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <!-- content view(s) --> <com.google.android.material.bottomappbar.BottomAppBar android:layout_width="wrap_content" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" ... /> </androidx.constraintlayout.widget.ConstraintLayout> <!-- res/layout-w600dp/main_activity.xml --> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <com.google.android.material.appbar.AppBarLayout android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" ... /> <!-- content view(s) --> </androidx.constraintlayout.widget.ConstraintLayout>
Once you've positioned your persistent UI elements, use the remaining space
for content, such as by using a
NavHostFragment with your app's navigation
Navigation for responsive UIs
for additional considerations.
Ensure all data is available for different sizes
Most app frameworks today make use of a data model that is separated from the Android components that contribute to the UI (Activities, Fragments, and Views). With Jetpack, this role is usually fulfilled by ViewModels, which have the added benefit of surviving across configuration changes (see ViewModel Overview for more information).
When implementing a layout that adapts to different sizes, it might be tempting to use a different data model based on the current size. However, this goes against the principle of unidirectional data flow. Data should flow downward to Views, and events like user interactions should flow upwards. Creating a dependency in the other direction, where the data model depends on the configuration of the UI layer, dramatically complicates this. When the app changes size, you must then account for converting from one data model to another.
Instead, let your data model accommodate the largest size class, and then you can selectively show, hide, or reposition content in the UI to adapt to the current size class. Below are a few strategies you can use when deciding how your layout should behave when transitioning between size classes.
Canonical layouts: Feed
Expanded space can be an opportunity to simply make things bigger and reformat your content so that it's more accessible.
Make collections larger. Many apps show a collection of items in a
scrolling container, such as a
ScrollView. Enabling a
container to become larger automatically means more content can be shown.
However, be careful that the content within the container does not become
overly stretched or distorted. For example, with a
using a different layout manager like
the width is not compact.
Individual items can also utilize a different size or shape to display more content and more easily distinguish item boundaries.
Emphasize a hero element. If the layout has a particular focal point, like an image or video, expand it when the app window grows to maintain the user's attention. Other supporting elements can be rearranged around or below the hero view.
There are many ways to construct such a layout, but
particularly suited for this purpose because it provides many ways to
constrain a child view's size—including by percentage, or enforcing an aspect
ratio—and to position its children relative to itself or to other
children. Learn more about all these capabilities in
Build a Responsive UI with ConstraintLayout.
Show collapsible content by default. When there is room available, expose content that would otherwise only be accessible through additional user interaction like tapping, scrolling, or gestures. For example, content that appears in a tabbed interface when compact could instead be rearranged into columns or a list when more space is available.
Expand margins. If the space is so large that you cannot find an appealing fit even after utilizing all of your content, then expand the margins of the layout so that the content remains centered and the individual views have natural sizes and spacing between them.
Alternatively, a full screen component can transform into a floating dialog UI. This is particularly well suited when that component requires exclusive focus to fulfill an immediate user task, such as composing an email or creating a calendar event.
Canonical layouts: Supporting panel, List-detail view
Use a supporting panel. A supporting panel presents additional content or contextual actions related to the primary content, such as comments in a document or items in a playlist. Typically, these use the bottom third of the screen for expanded height or the trailing third for expanded width.
An important consideration is where to place this content when there is not enough space to show the panel. Here are a few alternatives to explore:
- Side drawer on the trailing edge using
- Bottom drawer using
- Menu or popup window accessible by tapping on a menu icon
Create a two-pane layout. Large screens might show a combination of features that normally appear separately on smaller screens. A common interaction pattern in many apps is to show a list of items, like contacts or search results, and switch to an item's detail when the item is selected. Rather than enlarge the list for larger screens, use the list-detail view to show both features side by side in a two-pane layout. Unlike a supporting panel, the detail pane of a list-detail view is a standalone element that can be shown independently on smaller screens.
dedicated widget for implementing a list-detail view. This
widget automatically calculates whether there is enough room to display both
panes together based on the
layout_width value specified for the two panes,
and any leftover space can be distributed using
layout_weight. When there is
not enough room, then each pane uses the full width of the layout, and the
detail pane either slides off screen or on top of the list pane.