Explain in detail | build a responsive UI for foldable devices

Posted by RoBoTTo on Thu, 10 Mar 2022 07:50:11 +0100

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 entering the beta testing stage, this library is provided 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 allows the API to iterate quickly to support the rapidly growing market, and allows developers to get support by updating the library without waiting for the Android version to update.

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 introduced by it, so that your application can easily respond to changes in window size. Having entered the beta testing stage 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 function of 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 use pictures below to illustrate the possible situations of each state.

△ 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 the design of repeatonlife 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 life cycle is STOPPED
    // The start code will automatically restart the lifecycle once 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 , the example in this paper introduces how to realize 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, through the flow of data flow events or through WindowMetricsCalculator Class.

When writing view code, using asynchronous API s can be difficult (for example onMeasure ), you can use windowmetrics calculator at this time.

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

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

The usage library provides high-level processing in the UI 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 life cycle is STOPPED
    // It will restart automatically when the life cycle 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 said 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 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 the 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 vertical folding 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 the display posture information by registering a callback with the windowinforepository callback adapter.

The example also contains tests that can be run in 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 gestures have proven 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 focus on large screen devices.

We launched it last year Jetpack WindowManager alpha 01 version . The library has grown steadily since then, and early feedback has greatly improved it. Now, it has embraced the Kotlin priority concept of Android, and gradually transitioned from callback driven model to collaborative process 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!