Detailed explanation | building a responsive UI for foldable devices

Optimize your application for foldable devices and large screen devices

The screen size of Android devices changes with each passing day. With the increasing popularity of tablet and foldable devices, it is particularly important to understand the window size and status of your application when developing a responsive user interface. Jetpack WindowManager Now in the beta phase, this library is available with the Android framework WindowManager Similar functions include support for responsive UI, callback adapter for detecting screen changes, and test window API. However, Jetpack WindowManager also adds support for window environments such as collapsible devices and Chrome OS.

The new WindowManager API includes the following:

  • WindowLayoutInfo : contains the display properties of the window, such as whether the window is collapsible or contains hinges
  • FoldingFeature : enables you to monitor the folding state of the foldable device and judge the posture of the device
  • WindowMetrics : provides the display indicators of the current window or all windows

Jetpack WindowManager is not bound to Android, which enables the API to iterate quickly to support the rapidly growing market, and allows developers to obtain support by updating the library without waiting for the Android version to be updated.

Now, the jetpack WindowManager library has entered the beta testing stage. We encourage all developers to use jetpack WindowManager, its device independent API, testing API and WindowMetrics, so that your application can easily respond to changes in window size. It has entered the beta testing stage, which means that you can safely focus on creating exciting experiences on these devices. Jetpack WindowManager supports API 14 as a minimum.

About Jetpack WindowManager

Jetpack WindowManager is a modern library with Kotlin priority. It supports new devices in different forms and provides "AppCompat like" functions to build applications with responsive UI.

folding state

Supporting foldable devices is the most intuitive feature of the Jetpack WindowManager library. When the folding state of the device changes, the application will receive corresponding events, and then update the UI interface to support new user interaction.

△ Google Duo running on Samsung Galaxy Z Fold2

You can Google Duo learning case To learn how to support foldable devices.

There are two folding states: FLAT and half_ Open. For FLAT, you can think that the surface is completely FLAT and open, although in some cases it may be split by hinges. For half_ Open, there are at least two logical areas in the window. We illustrate the possible situations of each state with pictures below.

△ folding state: FLAT and HALF-OPENED

When the application is active, the information of folding state change can be obtained by collecting events through Kotlin data stream.

We use lifecycle scope to control the beginning and end of event collection, as in this article< The story behind designing the repeatOnLifeCycle API >And the sample code:

lifecycleScope.launch(Dispatchers.Main) {
    // The code block passed to repeatonllifecycle will be executed when the lifecycle enters STARTED
    // And cancel when the lifecycle is STOPPED
    // Repeatonlife will automatically restart the code block when the lifecycle enters STARTED again
    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
        // Safely collect data from the windows info repository when the lifecycle is STARTED
        // Stop collecting data when the lifecycle enters STOPPED
        windowInfoRepository.windowLayoutInfo
            .collect { newLayoutInfo ->
                updateStateLog(newLayoutInfo)
                updateCurrentState(newLayoutInfo)
            }
    }
}

When the user can see the application, the application can use the information it receives WindowLayoutInfo Object to update the layout.

FoldingFeature Including such as hinges direction , and whether the folding function creates two logical screen areas( isSeparating Attribute). We can use these values to check whether the device is in desktop mode (the screen is half open and the hinge is horizontal):

△ the device is in TableTop mode

private fun isTableTopMode(foldFeature: FoldingFeature) =
    foldFeature.isSeparating && 
            foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL

Or book mode (screen half open and hinge in vertical direction):

△ the device is in Book mode

private fun isBookMode(foldFeature: FoldingFeature) =
    foldFeature.isSeparating &&
            foldFeature.orientation == FoldingFeature.Orientation.VERTICAL

See: Desktop mode in collapsible devices , this paper introduces how to implement this function in media player application.

Note: it is important to collect events in the main thread / UI thread, which can avoid synchronization problems between UI and event processing.

Support responsive UI

The screen size of Android devices changes very frequently, so it is very important to start designing a fully adaptive and responsive UI. Another function included in the Jetpack WindowManager library is to retrieve the indicator information of the current window and the largest window. This and WindowMetrics API in API 30 Similar, but it is backward compatible with API 14.

Jetpack WindowManager provides two ways to retrieve WindowMetrics Information, either through a flow in a data flow event or through WindowMetricsCalculator Class.

When writing view code, using asynchronous API s can be difficult (such as onMeasure ), you can use windowmetrics calculator.

val windowMetrics = 
    WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(activity)

Another usage scenario is for testing (see the testing section below).

In the high-level usage of the processing application UI, the WindowInfoRepository#currentWindowMetrics Notification can be received when the window size changes, regardless of whether a configuration change is triggered.

This example is about how to switch your layout according to the available areas:

// Because repeatonlife is a pending function, a new coroutine is created
lifecycleScope.launch(Dispatchers.Main) {
   // The code block passed to repeatonllifecycle will be executed when the lifecycle enters STARTED
    // And cancel when the lifecycle is STOPPED
    // It will restart automatically when the lifecycle enters STARTED again
   lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
       // Safely collect data from the windows info repository when the lifecycle is STARTED
       // Stop collecting data when the lifecycle enters STOPPED
       windowInfoRepository.currentWindowMetrics
           .collect { windowMetrics ->
               val currentBounds = windowMetrics.bounds
               Log.i(TAG, "New bounds: {$currentBounds}")
               // We can update the layout here as needed
           }
   }
}

Callback adapter

To use this library in the Java programming language or use a callback interface, add it to your application androidx.window:window-java Dependence. This component provides WindowInfoRepositoryCallbackAdapter , you can register (unregister) a callback to receive the update of device attitude and window index information through it.

public class SplitLayoutActivity extends AppCompatActivity {

   private WindowInfoRepositoryCallbackAdapter windowInfoRepository;
   private ActivitySplitLayoutBinding binding;
   private final LayoutStateChangeCallback layoutStateChangeCallback =
           new LayoutStateChangeCallback();

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);

       binding = ActivitySplitLayoutBinding.inflate(getLayoutInflater());
       setContentView(binding.getRoot());

       windowInfoRepository =
               new WindowInfoRepositoryCallbackAdapter(WindowInfoRepository.getOrCreate(this));
   }

   @Override
   protected void onStart() {
       super.onStart();
       windowInfoRepository.addWindowLayoutInfoListener(Runnable::run, layoutStateChangeCallback);
   }

   @Override
   protected void onStop() {
       super.onStop();
       windowInfoRepository.removeWindowLayoutInfoListener(layoutStateChangeCallback);
   }

   class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> {
       @Override
       public void accept(WindowLayoutInfo windowLayoutInfo) {
           binding.splitLayout.updateWindowLayout(windowLayoutInfo);
       }
   }
}

test

Developers say that a more robust testing API is critical to maintaining LTS (long-term support). Let's talk about how to test the posture of foldable devices on ordinary devices.

Now we know that the Jetpack WindowManager library can send a notification to your application when the device posture changes, so that you can modify the layout of the application.

The library is in androidx.window:window-testing Provided in WindowLayoutInfoPublisherRule Enables you to publish a WindowInfoLayout to support testing the FoldingFeature:

import androidx.window.testing.layout.FoldingFeature
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule

We can virtual one in the test FoldingFeature:

val feature = FoldingFeature(
   activity = activity,
   center = center,
   size = 0,
   orientation = VERTICAL,
   state = HALF_OPENED
)
val expected =
   WindowLayoutInfo.Builder().setDisplayFeatures(listOf(feature)).build()

publisherRule.overrideWindowLayoutInfo(expected)

Then use WindowLayoutInfoPublisherRule to publish it:

val publisherRule = WindowLayoutInfoPublisherRule()

publisherRule.overrideWindowLayoutInfo(expected)

Finally, use the available Espresso matcher To check whether the layout of the Activity we are testing meets expectations.

The following test released a test in half_ FoldingFeature in the opened state with the hinge perpendicular to the center of the screen:

@Test
fun testDeviceOpen_Vertical(): Unit = testScope.runBlockingTest {
   activityRule.scenario.onActivity { activity ->
       val feature = FoldingFeature(
           activity = activity,
           orientation = VERTICAL,
           state = HALF_OPENED
       )
       val expected =
           WindowLayoutInfo.Builder().setDisplayFeatures(listOf(feature)).build()

       val value = testScope.async {
           activity.windowInfoRepository().windowLayoutInfo.first()
       }
       publisherRule.overrideWindowLayoutInfo(expected)
       runBlockingTest {
           Assert.assertEquals(
               expected,
               value.await()
           )
       }
   }

    // Check start when there is a vertical fold feature_ Layout at end_ Left side of layout
    // This requires running the test on a screen large enough to accommodate the two views on the screen
   onView(withId(R.id.start_layout))
       .check(isCompletelyLeftOf(withId(R.id.end_layout)))
}

View sample code

On Github Latest example Shows how to use the Jetpack WindowManager library to collect information from the WindowLayoutInfo stream, or to obtain display posture information by registering a callback with the windowinforepository callback adapter.

The example also contains tests that can be run on any device or simulator.

Using WindowManager in your application

Collapsible devices and dual screen devices are no longer just experimental or forward-looking - large screen space and additional device posture have proved to be of user value, and now there are more devices for your users to choose from. Foldable devices and dual screen devices represent the natural evolution of smart phones. For Android developers, this provides an opportunity to enter the growing high-end market, thanks to device manufacturers' renewed attention to large screen devices.

We launched last year Jetpack WindowManager alpha 01 version . Since then, the library has developed steadily, and its early feedback has greatly improved it. Now, it has embraced the Kotlin priority concept of Android, and gradually transitioned from callback driven model to collaboration and data flow. As WindowManager enters the testing phase, the API has stabilized, and we strongly recommend using it.

Updates are not limited to this. We plan to add more functions to the library and develop it into a system UI library unbound with AppCompat, so that developers can easily realize modern and responsive UI on all Android devices.

Welcome click here Submit feedback to us, or share your favorite content and found problems. Your feedback is very important to us. Thank you for your support!

Tags: Android jetpack

Posted on Thu, 25 Nov 2021 19:48:09 -0500 by yuan