[Android翻译]解除对WindowManager的束缚

1,150 阅读8分钟

image.png

原文地址:medium.com/androiddeve…

原文作者:medium.com/@pmaggi

发布时间:2021年8月20日 - 6分钟阅读

为可折叠设备和大屏幕设备优化应用程序

Android的屏幕尺寸正在迅速变化,随着平板电脑和可折叠设备的不断普及,了解你的应用程序的窗口尺寸和状态对于开发一个响应式的UI至关重要。Jetpack WindowManager现在处于测试阶段,它是一个库和API,提供类似于Android框架WindowManager的功能,包括对响应式UI的支持、检测屏幕变化的回调适配器以及窗口测试API。但Jetpack WindowManager还提供了对新型设备的支持,如可折叠设备和Chrome OS等窗口环境。

新的WindowManager APIs包括以下内容。

  • WindowLayoutInfo:包含了一个窗口的显示特征,例如窗口是否包含了折叠或铰链
  • FoldingFeature:使你能够监测可折叠设备的折叠状态,以确定设备的姿势
  • WindowMetrics:提供当前窗口的指标或整体显示的指标

Jetpack WindowManager与安卓系统没有捆绑,允许更快地迭代API,以快速支持快速发展的设备市场,并使应用程序开发人员能够采用库的更新,而不必等待最新的安卓版本。

现在该库已经进入测试阶段,我们鼓励所有的开发者采用Jetpack WindowManager,它具有设备无关的API,测试API,以及带来WindowMetrics,使你能够轻松应对窗口尺寸的变化。逐步过渡到测试版意味着你可以对你所采用的API有信心,使你可以完全专注于在这些设备上建立令人兴奋的体验。Jetpack WindowManager支持低至API 14的功能检测。

该库

Jetpack WindowManager是一个现代的、以Kotlin为首的库,它支持新的设备形态因素,并提供 "类似AppCompat "的功能,以构建具有响应式用户界面的应用程序。

折叠状态

这个库所提供的最明显的功能是对可折叠设备的支持。当设备的折叠状态发生变化时,应用程序可以接收事件,允许更新用户界面以支持新的用户互动。

1.gif

三星Galaxy Z Fold2上的Google Duo

请看这个Google Duo案例研究,它介绍了如何为可折叠设备添加支持。

有两种可能的折叠状态:平面半开放。对于FLAT,你可以认为表面是完全平坦地打开的,尽管在某些情况下它可能被铰链分割。对于HALF_OPENED,窗口至少有两个逻辑区域。下面,我们有图片说明每种状态的可能性。

image.png

折叠状态。平坦和半开放

当应用程序处于活动状态时,应用程序可以通过收集Kotlin流的事件来接收关于折叠状态变化的信息。 为了开始和停止事件收集,我们可以使用一个生命周期范围,正如 repeatOnLifeCycle API设计故事博文和下面的代码示例中所解释的。

lifecycleScope.launch(Dispatchers.Main) {
    // The block passed to repeatOnLifecycle is executed when the lifecycle
    // is at least STARTED and is cancelled when the lifecycle is STOPPED.
    // It automatically restarts the block when the lifecycle is STARTED again.
    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
        // Safely collects from windowInfoRepository when the lifecycle is STARTED
        // and stops collection when the lifecycle is STOPPED.
        windowInfoRepository.windowLayoutInfo
            .collect { newLayoutInfo ->
                updateStateLog(newLayoutInfo)
                updateCurrentState(newLayoutInfo)
            }
    }
}

然后,应用程序可以使用收到的WindowLayoutInfo对象中的可用信息,在应用程序对用户可见时更新其布局。

FoldingFeature包括铰链方向和折叠功能是否创建两个逻辑屏幕区域(isSeparating属性)等信息。我们可以使用这些值来检查设备是否处于桌面模式(半开,铰链水平)。

image.png

设备处于TableTop模式

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

或处于书本模式(半开,铰链垂直)。

image.png

设备在书本模式下

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

你可以在《可折叠设备上的桌面模式》一文中看到一个例子,说明如何为一个媒体播放器应用程序做到这一点。

注意:在主/UI线程上收集这些事件很重要,以避免UI和处理这些事件之间的同步问题。

对响应式UI的支持

由于安卓系统中的屏幕尺寸变化非常频繁,因此开始设计完全自适应和响应式的UI非常重要。WindowManager库中包含的另一个功能是能够检索当前和最大的窗口度量信息。这与API 30中包含的框架WindowMetrics API提供的信息类似,但它向后兼容到API 14。

Jetpack WindowManager提供了两种检索WindowMetrics信息的方式,作为流事件流或通过WindowMetricsCalculator类同步进行。

当在视图中写代码时,异步的API可能太难处理(比如onMeasure),就使用WindowMetricsCalculator。

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

另一个用例是在测试中(见下面的测试)。

对于应用程序UI的更高层次的处理,使用WindowInfoRepository#currentWindowMetrics来获得库的通知,当有一个窗口大小的变化时,独立于这个变化是否触发了配置的变化。

下面是一个如何根据你的可用区域的大小来切换你的布局的例子。

// Create a new coroutine since repeatOnLifecycle is a suspend function
lifecycleScope.launch(Dispatchers.Main) {
   // The block passed to repeatOnLifecycle is executed when the lifecycle
   // is at least STARTED and is cancelled when the lifecycle is STOPPED.
   // It automatically restarts the block when the lifecycle is STARTED again.
   lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
       // Safely collect from currentWindowMetrics when the lifecycle is STARTED
       // and stops collection when the lifecycle is STOPPED
       windowInfoRepository.currentWindowMetrics
           .collect { windowMetrics ->
               val currentBounds = windowMetrics.bounds
               Log.i(TAG, "New bounds: {$currentBounds}")
               // We can update the layout if needed from here
           }
   }
}

回调适配器 要在Java编程语言中使用这个库,或者使用回调接口,请在你的应用程序中包含androidx.window:window-java依赖项。该工件提供了WindowInfoRepositoryCallbackAdapter,你可以用它来注册(和取消注册)一个回调,以接收设备姿态和窗口度量信息的更新。

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);
       }
   }
}

测试

我们从开发者那里听说,更强大的测试API对于维持长期支持至关重要。让我们来谈谈如何在正常设备上测试可折叠的姿势。

到目前为止,我们已经看到Jetpack WindowManager库在设备姿态发生变化时通知你的应用程序,这样你就可以修改应用程序的布局。

该库在androidx.window:window-testing中提供了WindowLayoutInfoPublisherRule,它使你可以在测试FoldingFeature的支持下发布WindowInfoLayout。

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

我们可以用它来创建一个假的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)

然后使用WindowLayoutInfoPublisherRule来发布它。

val publisherRule = WindowLayoutInfoPublisherRule()

publisherRule.overrideWindowLayoutInfo(expected)

最后一步是使用可用的Espresso匹配器检查我们正在测试的活动的布局是否符合预期。

下面是一个测试发布FoldingFeature的例子,它在屏幕中心有一个HALF_OPENED的垂直铰链。

@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()
           )
       }
   }

   // Checks that start_layout is on the left of end_layout with a vertical folding feature.
   // This requires to run the test on a big enough screen to fit both views on screen
   onView(withId(R.id.start_layout))
       .check(isCompletelyLeftOf(withId(R.id.end_layout)))
}

请看它的运行情况。代码样本

GitHub上的一个最新样本显示了如何使用Jetpack WindowManager库来检索显示姿势信息,从WindowLayoutInfo流中收集信息或通过WindowInfoRepositoryCallbackAdapter注册一个回调。

该样本还包括一些测试,可以在任何设备或模拟器上运行。

在你的应用程序中采用WindowManager

可折叠和双屏设备不再是实验性的或未来主义的--大的显示区域和额外的姿势具有被证实的用户价值,而且现在有更多的设备可以供你的用户使用。可折叠设备和双屏设备代表了智能手机的自然进化。对于安卓开发者来说,他们提供了进入一个正在增长的高端市场的机会,这也得益于设备制造商的重新关注。

我们去年推出了Jetpack WindowManager alpha01。从那时起,该库有了稳定的发展,针对早期的反馈有了一些很大的改进。该库现在已经接受了Android的Kotlin优先理念,从回调驱动的模型过渡到coroutines和flow。随着WindowManager现在处于测试阶段,该API已经稳定,我们强烈建议采用。 而更新并不限于此。我们计划为该库添加更多的功能,并将其发展成一个用于系统UI的非捆绑式AppCompat,使开发者能够在所有的Android设备上轻松实现现代的、响应式的UI。

保持反馈的畅通!

如果你想看到更多关于为可折叠设备和其他大屏幕设备进行优化的资源,请访问这个页面


www.deepl.com 翻译