Migrate your UI to responsive layouts

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 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 ChromeOS, 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.

Multiple devices showing app windows of different sizes.
Figure 1. Window sizes can differ from the physical device or display size.

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 change, and the distinction loses importance.

Instead of trying any of the preceding strategies, 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, the Material Design responsive layout grid provides breakpoints for width and height, which lets 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 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 delineates 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>

Content

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 graph. See 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.

Expand content

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 RecyclerView or 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 RecyclerView, consider using a different layout manager like GridLayoutManager, StaggeredGridLayoutManager, or FlexboxLayout when the width is not compact.

A device folded and unfolded showing how different layout managers lay out the app differently based on width size class.
Figure 2. Different layout managers for different window size classes.

Individual items can also utilize a different size or shape to display more content and more readily 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 ConstraintLayout is 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.

Standard phone showing a dialog full screen, and an unfolded foldable phone showing the same dialog as a floating window.
Figure 3. Full screen dialog transformed into a standard dialog at medium and expanded width.

Add content

Canonical layouts: Supporting pane, List-detail view

Use a supporting pane. A supporting pane 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 pane. Here are a few alternatives to explore:

  • Side drawer on the trailing edge using DrawerLayout
  • Bottom drawer using BottomSheetBehavior
  • Menu or popup window accessible by tapping on a menu icon
Figure 4. Alternative ways of presenting additional content in a supporting pane.

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 pane, the detail pane of a list-detail view is a standalone element that can be shown independently on smaller screens.

Use the SlidingPaneLayout 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.

SlidingPaneLayout showing both panes of a list-detail layout on a device with a wide display.
Figure 5. SlidingPaneLayout showing two panes in expanded width and one pane in compact width.

Create a two-pane layout contains more details about using SlidingPaneLayout. Also note that this pattern may impact how you structure your navigation graph (see Navigation for responsive UIs).

Additional resources