关于android:可折叠设备的桌面模式

10次阅读

共计 6369 个字符,预计需要花费 16 分钟才能阅读完成。

开展您的视频播放体验

可折叠设施向用户们提供了应用他们的手机做更多事件的可能性,包含 桌面模式* 等翻新,也就是当手机平放时,铰链处于程度地位,同时折叠屏幕处于局部关上的状态。

当您不想将手机握在手里应用时,桌面模式十分不便。它很适宜于看媒体、进行视频通话、拍照甚至是玩游戏。

一个很好的例子是 Google Duo 团队 对其利用进行的优化,从而使该利用在平板电脑和可折叠设施上均能运行良好。

△ Duo 利用在优化前后的比照

在这篇文章中,您会理解到一个简略而又高效的形式来使您的利用在可折叠设施上运行时适配布局。

这是一个简略的媒体播放器案例,它会主动调节尺寸以防止让折叠处呈现在画面两头,并且调整播放管制组件的地位,从屏幕齐全开展时嵌入画面中,变为当屏幕局部折叠时显示为独自的面板。如同视频展现的样子:

△ 在 Samsung Galaxy Z Fold2 5G 手机上展现桌面模式的案例

* 桌面模式在 Samsung Galaxy Z 系列可折叠手机上也被称为 Flex 模式。

后期筹备

示例利用应用了 Exoplayer,这是 Android 平台上十分风行的开源媒体播放库。同时还用到了以下 Jetpack 组件:

  • MotionLayout,它是 ConstraintLayout 的一个子类。MotionLayout 联合了父类的灵活性,同时又具备在视图从一种姿势过渡到另一种时展现晦涩动画的能力。
  • ReactiveGuide,这是一个不可见的组件,它会在某个 SharedValue 发生变化时主动扭转本人的地位。ReactiveGuide 须要与 Guideline 辅助类独特作用。
  • WindowManager,这是一个帮忙利用开发者们对新设施类型参数提供反对的库,并且为不同的窗口特色提供了通用的 API 接口。

要应用这些库,您必须将 Google Maven 库增加到我的项目中,并且申明相干依赖:

dependencies {
    ...
    // 成文时应用如下的版本号,Exoplayer 最新版本号详见 https://github.com/google/ExoPlayer/releases
    implementation 'com.google.android.exoplayer:exoplayer-core:2.14.1'
    implementation 'com.google.android.exoplayer:exoplayer-ui:2.14.1'

    implementation 'androidx.constraintlayout:constraintlayout:2.1.0-rc01'
    implementation 'androidx.window:window:1.0.0-beta01'

    ...
}

布局

首先思考视频播放器 Activity 的布局,其根元素是蕴含了三个子视图的 MotionLayout。

<androidx.constraintlayout.motion.widget.MotionLayout
    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:id="@+id/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/black"
    app:layoutDescription="@xml/activity_main_scene"
    tools:context=".MainActivity">

    <com.google.android.exoplayer2.ui.PlayerView
        android:id="@+id/player_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/fold"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:use_controller="false" />

    <androidx.constraintlayout.widget.ReactiveGuide
        android:id="@+id/fold"
        app:reactiveGuide_valueId="@id/fold"
        app:reactiveGuide_animateChange="true"
        app:reactiveGuide_applyToAllConstraintSets="true"
        android:orientation="horizontal"
        app:layout_constraintGuide_end="0dp"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content" />

    <com.google.android.exoplayer2.ui.PlayerControlView
        android:id="@+id/control_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@color/black"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/fold" />

</androidx.constraintlayout.motion.widget.MotionLayout>

其中两个视图来自 Exoplayer 套件,您能够通过它们来为 PlayerView (显示媒体的界面) 和 PlayerControlView (播放控件的容器) 指定不同的布局。

第三个视图是一个 ReactiveGuide。它被搁置在另外两个视图两头,并且以 Guideline 的模式作为另外两个视图的划分。

次要的 PlayerView 被限度为永远在 ReactiveGuide 的上方。这样一来,当您将 ReactiveGuide 从底部挪动至折叠地位时,布局的转换就会产生。

您可能想要将播放控件始终限定在 ReactiveGuide 的底部。这样一来该控件会在屏幕齐全开展时被暗藏,而当屏幕局部折叠时又呈现在底部。

请留神第 28 行的 layout_constraintGuide_end 属性。它就是当您挪动参考线时须要扭转的值。因为 ReactiveGuide 是程度的,此属性指的是参考线到父布局底部的间隔。

让您的利用感知屏幕折叠

当初进入最重要的局部: 如何获知您的手机何时进入了桌面模式,并获取到折叠处的地位呢?

当初始化实现后,WindowManager 库容许您通过收集来自函数 WindowInfoRepository.windowLayoutInfo()) 的数据流 Flow<WindowLayoutInfo> 监听布局变动:

override fun onStart() {super.onStart()
        initializePlayer()
        layoutUpdatesJob = uiScope.launch {
            WindowInfoRepository.windowLayoutInfo
                .collect { newLayoutInfo ->
                    onLayoutInfoChanged(newLayoutInfo)
                }
        }
    }

    override fun onStop() {super.onStop()
        layoutUpdatesJob?.cancel()
        releasePlayer()}

如果您想要理解如何初始化和开释一个 Exoplayer 实例,请查阅——Exoplayer codelab。

每当您获取到新的布局信息时,您能够查问显示屏特色,并查看设施以后显示中是否存在折叠或铰链:

private fun onLayoutInfoChanged(newLayoutInfo: WindowLayoutInfo) {if (newLayoutInfo.displayFeatures.isEmpty()) {
            // 如果以后屏幕没有显示特色可用,咱们可能正位于副屏观看、// 不可折叠屏幕或是位于可折叠的主屏但处于分屏模式。centerPlayer()} else {newLayoutInfo.displayFeatures.filterIsInstance(FoldingFeature::class.java)
                .firstOrNull {feature -> isInTabletopMode(feature) }
                ?.let { foldingFeature ->
                    val fold = foldPosition(binding.root, foldingFeature)
                    foldPlayer(fold)
                } ?: run {centerPlayer()
            }
        }
    }

留神如果您不想应用 Kotlin 数据流,从 1.0.0-alpha07 版本开始,您能够应用 window-java 这个工具,它提供一系列对 Java 敌对的 API 来注册或是勾销注册回调函数,或是应用 window-rxjava2 以及 window-rxjava3 工具来应用适配 RxJava 的 API。

当设施方向为程度向且 FoldingFeature.isSeparating()) 返回了 true 时,此设施可能正处于桌面模式。

如果是这样的话,您能够计算出折叠处的绝对地位,而后将 ReactiveGuide 挪动到该地位;如果状况相同,您能够将其挪动到 0 (屏幕底部)。

private fun centerPlayer() {ConstraintLayout.getSharedValues().fireNewValue(R.id.fold, 0)
        binding.playerView.useController = true // 应用内嵌画面的控件
    }

    private fun foldPlayer(fold: Int) {ConstraintLayout.getSharedValues().fireNewValue(R.id.fold, fold)
        binding.playerView.useController = false // 应用位于屏幕底部一侧的控件
    }

当您这样调用函数 fireNewValue 时,库函数会扭转 layout_constraintGuide_end 的值。当设施齐全开展时,整个屏幕都会被用于显示主 PlayerView。

最初的问题: 当设施折叠时,您应该将 ReactiveGuide 挪动到哪里?

FoldingFeature 对象有一个办法 bounds()),它能够取得屏幕坐标系内折叠处的边界矩形信息。

如果您要实现横屏性能,那么大多数时候,边界会以一个在屏幕中垂直居中的矩形来示意,它和屏幕一样宽,并且高度与铰链雷同 (对于可折叠设施而言值为 0,对于双屏幕设施而言会是两个屏幕之间的间隔)。

如果您的利用处于全屏模式,您能够将 PlayerView 固定在 FoldingFeatures.bounds().top 的顶部,并将 ControlView 固定在 FoldingFeatures.bounds().bottom 的底部。

在其余的所有状况下 (非全屏) 您须要思考导航栏或屏幕上其余 UI 组件占据的空间。

为了挪动参考线,您必须指定它间隔父布局底部的间隔。计算 ReactiveGuide 失当地位函数的一种可能的实现如下:

    /**
     * 返回折叠处绝对于布局的地位 
     */
    fun foldPosition(view: View, foldingFeature: FoldingFeature): Int {val splitRect = getFeatureBoundsInWindow(foldingFeature, view)
        splitRect?.let {return view.height.minus(splitRect.top)
        }

        return 0
    }

    /**
     * 获取 displayFeature 变换到视图坐标系的边界和它以后在窗口中的地位。* 这里的计算中默认会蕴含内边距。*/
    private fun getFeatureBoundsInWindow(
        displayFeature: DisplayFeature,
        view: View,
        includePadding: Boolean = true
    ): Rect? {
        // 视图在窗口中的地位要与显示特色在同一坐标空间中。val viewLocationInWindow = IntArray(2)
        view.getLocationInWindow(viewLocationInWindow)

        // 将窗口中的 displayFeature 边界矩形与视图的边界矩形相交以裁剪边界。val viewRect = Rect(viewLocationInWindow[0], viewLocationInWindow[1],
            viewLocationInWindow[0] + view.width, viewLocationInWindow[1] + view.height
        )

        // 如果需要的话,蕴含内边距
        if (includePadding) {
            viewRect.left += view.paddingLeft
            viewRect.top += view.paddingTop
            viewRect.right -= view.paddingRight
            viewRect.bottom -= view.paddingBottom
        }

        val featureRectInView = Rect(displayFeature.bounds)
        val intersects = featureRectInView.intersect(viewRect)

        // 查看 displayFeature 与指标视图是否齐全重叠
        if ((featureRectInView.width() == 0 && featureRectInView.height() == 0) ||
            !intersects
        ) {return null}

        // 将显示特色坐标偏移至视图坐标空间起始点
        featureRectInView.offset(-viewLocationInWindow[0], -viewLocationInWindow[1])

        return featureRectInView
 }

总结

在本文中,您学习了如何通过实现反对桌面模式的灵便布局来改善可折叠设施上媒体利用的用户体验。

敬请持续关注后续对于不同状态参数开发指南的文章!

更多资源

  • Exoplayer Codelab: 用 Exoplayer 播放视频流
  • 桌面模式实例利用
  • 为可折叠设施而设计
  • 为可折叠设施构建利用
  • Jetpack WindowManager
  • 应用 MotionLayout 治理静止和微件动画

欢迎您通过下方二维码向咱们提交反馈,或分享您喜爱的内容、发现的问题。您的反馈对咱们十分重要,感谢您的反对!

正文完
 0