Support different screen sizes

Android devices come in all shapes and sizes, so your app's layout needs to be flexible. That is, instead of defining your layout with rigid dimensions that assume a certain screen size and aspect ratio, your layout should gracefully respond to different screen sizes and orientations.

By supporting as many screens as possible, your app can be made available to the greatest number of users with different devices, using a single APK or AAB. Additionally, making your app flexible for different screen sizes ensures that your app can handle window configuration changes on the device, such as when the user enables multi-window mode. The same principle applies when your app is running on a foldable device where the screen size and aspect ratio may change while your app is running.

But making your app adaptable to different screen sizes doesn't necessarily make it compatible with all Android form factors. You should take additional steps to support Android Wear, TV, Auto, and Chrome OS devices.

For information on building UIs for different screens, see the material design guidelines for responsive layout.

Window size classes

Window size classes are a set of opinionated viewport breakpoints for you to design, develop, and test resizable application layouts against. They have been chosen specifically to balance layout simplicity with the flexibility to optimize your app for unique cases.

Window size classes partition the raw window size available to your app into more manageable and meaningful buckets. There are three buckets: compact, medium, and expanded. The available width and height are partitioned individually, so at any point in time, your app has two size classes associated with it: a width window size class, and a height window size class.

While window size classes are specified for both width and height, the available width is often more important than available height due to the ubiquity of vertical scrolling. Therefore, the width window size class will likely be more relevant to your app’s UI.

Representations of width-based window size classes.
Figure 1. Representations of width-based window size classes.
Representations of height-based window size classes.
Figure 2. Representations of height-based window size classes.

As visualized above, these breakpoints allow you to continue thinking about layouts in terms of devices and configurations. Each size class breakpoint represents a majority case for typical device scenarios, which can be a helpful frame of reference as you think about the design of your breakpoint-based layouts.

Size class Breakpoint Device representation
Compact width < 600dp 99.96% of phones in portrait
Medium width 600dp+ 93.73% of tablets in portrait

Large unfolded inner displays in portrait

Expanded width 840dp+ 97.22% of tablets in landscape

Large unfolded inner displays in landscape

Most apps can stop here
Compact height < 480dp 99.78% of phones in landscape
Medium height 480dp+ 96.56% of tablets in landscape

97.59% of phones in portrait

Expanded height 900dp+ 94.25% of tablets in portrait

Although it is useful to visualize size classes with physical devices, window size classes are explicitly not determined by the physical size of the screen. In other words, window size classes do not represent “isTablet” logic. Rather, window size classes are determined by the window size available to your application.

This has two important consequences:

  • Physical devices do not guarantee a specific window size class. There are a large number of reasons why the screen space available to your app may differ from the physical screen size of the device. On mobile devices, split-screen mode can split the screen between multiple applications. On Chrome OS, Android apps can be presented in free-form windows that are arbitrarily resizable. Foldables can have multiple physical screens, and folding the device changes which screen is visible.

  • The window size class can change throughout the lifetime of your app. While your app is open, device rotations, multitasking, and folding can change the amount of screen space available. Therefore, the window size class is dynamic, and your app’s UI should adapt accordingly.

Use window size breakpoints to make high-level application layout decisions, such as deciding to use a specific canonical layout to make use of additional screen space. They also map to the Material Design layout breakpoints for layout grid column changes.

To calculate the window size classes, applications should query the current window metrics available to their app, as provided by the Jetpack WindowManager library.

Views

enum class WindowSizeClass { COMPACT, MEDIUM, EXPANDED }

class MainActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // ...

        // Replace with a known container that you can safely add a View
        // to where it won't affect the layout and the view won't be
        // replaced.
        val container: ViewGroup = binding.container

        // Add a utility view to the container to hook into
        // View.onConfigurationChanged.
        // This is required for all activities, even those that don't
        // handle configuration changes.
        // We also can't use Activity.onConfigurationChanged, since there
        // are situations where that won't be called when the configuration
        // changes.
        // View.onConfigurationChanged is called in those scenarios.
        container.addView(object : View(this) {
            override fun onConfigurationChanged(newConfig: Configuration?) {
                super.onConfigurationChanged(newConfig)
                computeWindowSizeClasses()
            }
        })

        computeWindowSizeClasses()
    }

    private fun computeWindowSizeClasses() {
        val metrics = WindowMetricsCalculator.getOrCreate()
            .computeCurrentWindowMetrics(this)

        val widthDp = metrics.bounds.width() /
            resources.displayMetrics.density
        val widthWindowSizeClass = when {
            widthDp < 600f -> WindowSizeClass.COMPACT
            widthDp < 840f -> WindowSizeClass.MEDIUM
            else -> WindowSizeClass.EXPANDED
        }

        val heightDp = metrics.bounds.height() /
            resources.displayMetrics.density
        val heightWindowSizeClass = when {
            heightDp < 480f -> WindowSizeClass.COMPACT
            heightDp < 900f -> WindowSizeClass.MEDIUM
            else -> WindowSizeClass.EXPANDED
        }

        // Use widthWindowSizeClass and heightWindowSizeClass
    }
}

Views

public enum WindowSizeClass { COMPACT, MEDIUM, EXPANDED }

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // ...

        // Replace with a known container that you can safely add a View
        // to where it won't affect the layout and the view won't be
        // replaced.
        ViewGroup container = binding.container;

        // Add a utility view to the container to hook into
        // View.onConfigurationChanged.
        // This is required for all activities, even those that don't
        // handle configuration changes.
        // We also can't use Activity.onConfigurationChanged, since there
        // are situations where that won't be called when the configuration
        // changes.
        // View.onConfigurationChanged is called in those scenarios.
        container.addView(new View(this) {
            @Override
            protected void onConfigurationChanged(Configuration newConfig) {
                super.onConfigurationChanged(newConfig);
                computeWindowSizeClasses();
            }
        });

        computeWindowSizeClasses();
    }

    private void computeWindowSizeClasses() {
        WindowMetrics metrics = WindowMetricsCalculator.getOrCreate()
                .computeCurrentWindowMetrics(this);

        float widthDp = metrics.getBounds().width() /
                getResources().getDisplayMetrics().density;
        WindowSizeClass widthWindowSizeClass;

        if (widthDp < 600f) {
            widthWindowSizeClass = WindowSizeClass.COMPACT;
        } else if (widthDp < 840f) {
            widthWindowSizeClass = WindowSizeClass.MEDIUM;
        } else {
            widthWindowSizeClass = WindowSizeClass.EXPANDED;
        }

        float heightDp = metrics.getBounds().height() /
                getResources().getDisplayMetrics().density;
        WindowSizeClass heightWindowSizeClass;

        if (heightDp < 480f) {
            heightWindowSizeClass = WindowSizeClass.COMPACT;
        } else if (heightDp < 900f) {
            heightWindowSizeClass = WindowSizeClass.MEDIUM;
        } else {
            heightWindowSizeClass = WindowSizeClass.EXPANDED;
        }

        // Use widthWindowSizeClass and heightWindowSizeClass
    }
}

Compose

enum class WindowSizeClass { COMPACT, MEDIUM, EXPANDED }

@Composable
fun Activity.rememberWindowSizeClass() {
    val configuration = LocalConfiguration.current
    val windowMetrics = remember(configuration) {
        WindowMetricsCalculator.getOrCreate()
            .computeCurrentWindowMetrics(this)
    }
    val windowDpSize = with(LocalDensity.current) {
        windowMetrics.bounds.toComposeRect().size.toDpSize()
    }
    val widthWindowSizeClass = when {
        windowDpSize.width < 600.dp -> WindowSizeClass.COMPACT
        windowDpSize.width < 840.dp -> WindowSizeClass.MEDIUM
        else -> WindowSizeClass.EXPANDED
    }

    val heightWindowSizeClass = when {
        windowDpSize.height < 480.dp -> WindowSizeClass.COMPACT
        windowDpSize.height < 900.dp -> WindowSizeClass.MEDIUM
        else -> WindowSizeClass.EXPANDED
    }

    // Use widthWindowSizeClass and heightWindowSizeClass
}

Once you are observing the window size classes in your app, you are ready to start changing your layout based on the current size class. To learn about how to use window size classes to make your view-based layouts responsive, see Migrate your UI to responsive layouts. To learn about how to use window size classes to make your Compose-based layouts responsive, see Build adaptive layouts.

Checklist for supporting different window size classes

As you make changes, test your layout behavior across all ranges of window sizes, especially at the compact, medium, and expanded layout widths.

If you have an existing layout for smaller screens, first optimize your layout for the expanded width size class, as this provides the most space for additional content or layout changes. Then, see what layout makes sense for the medium width class, and consider adding a specialized layout at that size. To provide an even better user experience, add functionality that makes sense for your app, such as supporting postures for foldables or optimizing for keyboard, mouse, and stylus input support.

To learn more about what makes an app great across all devices and screen sizes, check out Large screens app quality.

Create a flexible layout

No matter what hardware profile you want to support first, you need to create a layout that is responsive to even small variations in screen size.

Use ConstraintLayout

The best way to create a responsive layout for different screen sizes is to use ConstraintLayout as the base layout in your UI. ConstraintLayout allows you to specify the position and size for each view according to spatial relationships with other views in the layout. This way, all the views can move and stretch together as the screen size changes.

The easiest way to build a layout with ConstraintLayout is to use the Layout Editor in Android Studio. It allows you to drag new views to the layout, attach their constraints to the parent view and other sibling views, and edit the view's properties, all without editing any XML by hand (see figure 1).

For more information, see Build a Responsive UI With ConstraintLayout.

Figure 1. The Layout Editor in Android Studio showing a ConstraintLayout file

But ConstraintLayout won't solve every layout scenario (especially for dynamically-loaded lists, for which you should use RecyclerView), but no matter what layout you use, you should always avoid hard-coding layout sizes (see the next section).

Avoid hard-coded layout sizes

To ensure that your layout is flexible and adapts to different screen sizes, you should use "wrap_content" or "match_parent" for the width and height of most view components, instead of hard-coded sizes.

"wrap_content" tells the view to set its size to whatever is necessary to fit the content within that view.

"match_parent" makes the view expand to as much as possible within the parent view.

For example:

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@string/lorem_ipsum" />

Although the actual layout for this view depends on other attributes in its parent view and any sibling views, this TextView intends to set its width to fill all available space (match_parent) and set its height to exactly as much space is required by the height of the text (wrap_content). This allows the view to adapt to different screen sizes and different lengths of text.

Figure 2 shows how the width of the text view using "match_parent" adjusts as the screen width changes with device orientation.

Figure 2. A flexible text view

If you're using a LinearLayout, you can also expand the child views with layout weight so that each view fills the remaining space proportional to their weight value. However, using weights in a nested LinearLayout requires the system to perform multiple layout passes to determine the size for each view, slowing your UI performance. Fortunately, ConstraintLayout can achieve nearly all layouts possible with LinearLayout without the performance impacts, so you should try converting your layout to ConstraintLayout. Then, you can define weighted layouts with constraint chains.

Use SlidingPaneLayout for list/detail UIs

A list/detail UI may need to behave differently for different screen sizes. When running on a large display, there is plenty of space to have the list and detail panes side-by-side. Clicking on an item in the list displays its details in the detail pane. However, on smaller screens, this can become too crowded. Instead of displaying both panes, it can be better to display them one at a time. Initially, the list pane is shown filling the window. When the user taps an item the list pane is replaced by the detail pane for that item, which also fills the window.

You can use SlidingPaneLayout to manage the logic for determining which of these two user experiences is appropriate for the current window size:

<?xml version="1.0" encoding="utf-8"?>
<androidx.slidingpanelayout.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="280dp"
        android:layout_height="match_parent"
        android:layout_gravity="start" />

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="300dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        app:defaultNavHost="true"
        app:navGraph="@navigation/item_navigation" />

</androidx.slidingpanelayout.widget.SlidingPaneLayout>

The widths and weight here are the key factors that determine the behavior. If the window is large enough to display both views (at least 580dp) then the side- by-side UI is used. However, if it is smaller than that, then the full-screen list replaced by full-screen detail UI is used instead.

When in side-by-side mode, the window may be larger than the minimum requirement of 580dp in this example. Weight values will be used to size the two panes proportionally. In the example, the list pane will always be 280dp and the detail pane will fill the remaining space. The one exception to this is when using SlidingPaneLayout V1.2.0-alpha01 and later on foldable devices. In these cases SlidingPaneLayout will automatically adjust the size of the panes so that they are on either side of any fold or hinge.

Create alternative layouts

Although your layout should always respond to different screen sizes by stretching the space within and around its views, that might not provide the best user experience for every screen size. For example, the UI you designed for a phone, probably doesn't offer a good experience on a tablet. Therefore, your app should also provide alternative layout resources to optimize the UI design for certain screen sizes.

Figure 3. The same app on different screen sizes uses a different layout for each

You can provide screen-specific layouts by creating additional res/layout/ directories—one for each screen configuration that requires a different layout—and then append a screen configuration qualifier to the layout directory name (such as layout-w600dp for screens that have 600dp of available width).

These configuration qualifiers represent the visible screen space available for your app UI. The system takes into account any system decorations (such as the navigation bar) and window configuration changes (such as when the user enables multi-window mode) when selecting the layout from your app.

To create an alternative layout in Android Studio (using version 3.0 or higher), proceed as follows:

  1. Open your default layout and then click Orientation for Preview in the toolbar.
  2. In the drop-down list, click to create a suggested variant such as Create Landscape Variant or click Create Other.
  3. If you selected Create Other, the Select Resource Directory appears. Here, select a screen qualifier on the left and add it to the list of Chosen qualifiers. When you're done adding qualifiers, click OK. (See the following sections for information about screen size qualifiers.)

This creates a duplicate layout file in the appropriate layout directory so you can begin customizing the layout for that screen variant.

Use the smallest width qualifier

The "smallest width" screen size qualifier allows you to provide alternative layouts for screens that have a minimum width measured in density-independent pixels(dp or dip).

By describing the screen size as a measure of density-independent pixels, Android allows you to create layouts that are designed for very specific screen dimensions while avoiding any concerns you might have about different pixel densities.

For example, you can create a layout named main_activity that's optimized for handsets and tablets by creating different versions of the file in directories as follows:

res/layout/main_activity.xml           # For handsets (smaller than 600dp available width)
res/layout-sw600dp/main_activity.xml   # For 7” tablets (600dp wide and bigger)

The smallest width qualifier specifies the smallest of the screen's two sides, regardless of the device's current orientation, so it's a simple way to specify the overall screen size available for your layout.

Here's how other smallest width values correspond to typical screen sizes:

  • 320dp: a typical phone screen (240x320 ldpi, 320x480 mdpi, 480x800 hdpi, etc).
  • 480dp: a large phone screen ~5" (480x800 mdpi).
  • 600dp: a 7” tablet (600x1024 mdpi).
  • 720dp: a 10” tablet (720x1280 mdpi, 800x1280 mdpi, etc).

Figure 4 provides a more detailed view of how different screen dp widths generally correspond to different screen sizes and orientations.

Figure 4. Recommended width breakpoints to support different screen sizes

Remember that all the figures for the smallest width qualifier are density-independent pixels, because what matters is the amount of screen space available after the system accounts for pixel density (not the raw pixel resolution).

Use the available width qualifier

Instead of changing the layout based on the smallest width of the screen, you might want to change your layout based on how much width or height is currently available. For example, if you have a two-pane layout, you might want to use that whenever the screen provides at least 600dp of width, which might change depending on whether the device is in landscape or portrait orientation. In this case, you should use the "available width" qualifier as follows:

res/layout/main_activity.xml         # For handsets (smaller than 600dp available width)
res/layout-w600dp/main_activity.xml  # For 7” tablets or any screen with 600dp
                                     #   available width (possibly landscape handsets)

If the available height is a concern for you, then you can do the same using the "available height" qualifier. For example, layout-h600dp for screens with at least 600dp of screen height.

Add orientation qualifiers

Although you may be able to support all size variations using only combinations of the "smallest width" and "available width" qualifiers, you might also want to change the user experience when the user switches between portrait and landscape orientations.

For that you can add the port or land qualifiers to your resource directory names. Just be sure these come after the other size qualifiers. For example:

res/layout/main_activity.xml                # For handsets
res/layout-land/main_activity.xml           # For handsets in landscape
res/layout-sw600dp/main_activity.xml        # For 7” tablets
res/layout-sw600dp-land/main_activity.xml   # For 7” tablets in landscape

For more information about all the screen configuration qualifiers, see table 2 in the guide to Providing Resources.

Modularize UI components with fragments

When designing your app for multiple screen sizes you want to make sure you aren't needlessly duplicating your UI behavior across your activities. So you should use fragments to extract your UI logic into separate components. Then, you can then combine fragments to create multi-pane layouts when running on a large screen or place in separate activities when running on a handset.

For example, a news app on a tablet might show a list of articles on the left side and a full article on the right side—selecting an article on the left updates the article view on the right. On a handset, however, these two components should appear on separate screens—selecting an article from a list changes the entire screen to show that article.

To learn more, see Building a Dynamic UI with Fragments.

Test on all screen sizes

It's important to test your app on a variety of screen sizes so you can ensure your UI scales correctly.

Android 10 (API level 29) and higher supports a wider range of aspect ratios. With foldables, form factors can vary from super high long and thin screens (such as 21:9 for a folded device) all the way down to 1:1.

To be compatible with as many devices as possible, you should test your apps for as many of these screen ratios as you can:

If you cannot support some of those ratios, you can use the maxAspectRatio (as before), as well as minAspectRatio to indicate the highest and lowest ratios your app can handle. In cases with screens that exceed these limits, your app might be put in compatibility mode.

When there are five icons in the bottom navigation view devices running Android 10 (API level 29) and higher are guaranteed a minimum touch target size of 2 inches. See the Compatibility Definition Document.

If you don't have access to physical devices for all the different screen sizes, you can use the Android Emulator to emulate any screen size.

If you would rather test on a physical device, but don't want to buy the devices, you can use Firebase Test Lab to access devices in a Google data center.

Declare specific screen size support

If you decide that you don't want your app to run at certain screen sizes, you can set limits for how much your screen should resize or even restrict which devices can install your app based on their screen configuration. For more information, see Declare Restricted Screen Support.