展开您的视频播放体验

可折叠设备向用户们提供了使用他们的手机做更多事情的可能性,包括桌面模式*等创新,也就是当手机平放时,铰链处于水平位置,同时折叠屏幕处于部分打开的状态。

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

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

△ Duo 应用在优化前后的对比

  • Google Duo 团队
    https://developer.android.google.cn/stories/apps/google-duo

在这篇文章中,您会了解到一个简单而又高效的方式来使您的应用在可折叠设备上运行时适配布局。

这是一个简单的媒体播放器案例,它会自动调节尺寸以避免让折叠处出现在画面中间,并且调整播放控制组件的位置,从屏幕完全展开时嵌入画面中,变为当屏幕部分折叠时显示为单独的面板。如同视频展示的样子:

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

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

前期准备

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

  • MotionLayout,它是 ConstraintLayout 的一个子类。MotionLayout 结合了父类的灵活性,同时又具备在视图从一种姿态过渡到另一种时展示流畅动画的能力。

  • ReactiveGuide,这是一个不可见的组件,它会在某个 SharedValue 发生变化时自动改变自己的位置。ReactiveGuide 需要与 Guideline 辅助类共同作用。

  • WindowManager,这是一个帮助应用开发者们对新设备类型参数提供支持的库,并且为不同的窗口特征提供了通用的 API 接口。

  • Exoplayer
    https://github.com/google/ExoPlayer

  • MotionLayout
    https://developer.android.google.cn/reference/androidx/constraintlayout/motion/widget/MotionLayout

  • ConstraintLayout
    https://developer.android.google.cn/training/constraint-layout

  • ReactiveGuide
    https://developer.android.google.cn/reference/androidx/constraintlayout/widget/ReactiveGuide

  • Guideline
    https://developer.android.google.cn/reference/androidx/constraintlayout/widget/Guideline

  • WindowManager
    https://developer.android.google.cn/jetpack/androidx/releases/window

要使用这些库,您必须将 Google Maven 库添加到项目中,并且声明相关依赖:

dependencies {...// 成文时使用如下的版本号,Exoplayer 最新版本号详见 https://github.com/google/ExoPlayer/releasesimplementation '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'...
}
  • Google Maven
    https://developer.android.google.cn/studio/build/dependencies?skip_cache=true#google-maven

布局

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

<androidx.constraintlayout.motion.widget.MotionLayoutxmlns: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.PlayerViewandroid: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.ReactiveGuideandroid: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.PlayerControlViewandroid: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>
  • MotionLayout
    https://developer.android.google.cn/reference/androidx/constraintlayout/motion/widget/MotionLayout

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

第三个视图是一个 ReactiveGuide。它被放置在另外两个视图中间,并且以 Guideline 的形式作为另外两个视图的划分。

主要的 PlayerView 被限制为永远在 ReactiveGuide 的上方。这样一来,当您将 ReactiveGuide 从底部移动至折叠位置时,布局的转换就会发生。

您可能想要将播放控件一直限定在 ReactiveGuide 的底部。这样一来该控件会在屏幕完全展开时被隐藏,而当屏幕部分折叠时又出现在底部。

请注意第 28 行的 layout_constraintGuide_end 属性。它就是当您移动参考线时需要改变的值。由于 ReactiveGuide 是水平的,此属性指的是参考线到父布局底部的距离。

  • PlayerView
    https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/ui/PlayerView.html

  • PlayerControlView
    https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/ui/PlayerControlView.html

  • ReactiveGuide
    https://developer.android.google.cn/reference/androidx/constraintlayout/widget/ReactiveGuide

  • Guideline
    https://developer.android.google.cn/reference/androidx/constraintlayout/widget/Guideline

  • layout_constraintGuide_end
    https://developer.android.google.cn/reference/androidx/constraintlayout/widget/ConstraintLayout.LayoutParams#guideEnd

让您的应用感知屏幕折叠

现在进入最重要的部分: 如何获知您的手机何时进入了桌面模式,并获取到折叠处的位置呢?

当初始化完成后,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()}
  • WindowInfoRepository.windowLayoutInfo()
    https://developer.android.google.cn/reference/kotlin/androidx/window/layout/WindowInfoRepository#windowLayoutInfo()

  • WindowLayoutInfo
    https://developer.android.google.cn/reference/kotlin/androidx/window/layout/WindowLayoutInfo

如果您想要了解如何初始化和释放一个 Exoplayer 实例,请查阅——Exoplayer codelab:

https://developer.android.google.cn/codelabs/exoplayer-intro

每当您获取到新的布局信息时,您可以查询显示屏特征,并检查设备当前显示中是否存在折叠或铰链:

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 时,此设备可能正处于桌面模式。

  • 1.0.0-alpha07
    https://developer.android.google.cn/jetpack/androidx/releases/window#1.0.0-alpha07

  • FoldingFeature.isSeparating()
    https://developer.android.google.cn/reference/kotlin/androidx/window/layout/FoldingFeature#isSeparating()

如果是这样的话,您可以计算出折叠处的相对位置,然后将 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(),它可以获得屏幕坐标系内折叠处的边界矩形信息。

  • layout_constraintGuide_end
    https://developer.android.google.cn/reference/androidx/constraintlayout/widget/ConstraintLayout.LayoutParams#guideEnd

  • FoldingFeature
    https://developer.android.google.cn/reference/kotlin/androidx/window/layout/FoldingFeature

  • bounds()
    https://developer.android.google.cn/reference/kotlin/androidx/window/layout/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.paddingLeftviewRect.top += view.paddingTopviewRect.right -= view.paddingRightviewRect.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 播放视频流
    https://developer.android.google.cn/codelabs/exoplayer-intro

  • 桌面模式实例应用
    https://www.youtube.com/watch?v=jIBNhxyciLQ

  • 为可折叠设备而设计
    https://developer.android.google.cn/training/constraint-layout/foldables

  • 为可折叠设备构建应用
    https://developer.android.google.cn/guide/topics/ui/foldables

  • Jetpack WindowManager
    https://github.com/androidx/androidx/tree/androidx-main/window

  • 使用 MotionLayout 管理运动和微件动画
    https://developer.android.google.cn/training/constraint-layout/motionlayout

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

 点击屏末 阅读原文 | 即刻了解 Google Duo 团队实例

推荐阅读

如页面未加载,请刷新重试

可折叠设备的桌面模式相关推荐

  1. 引入适用于双屏和可折叠设备的Web API

    微信搜索[前端全栈开发者]关注这个脱发.摆摊.卖货.持续学习的程序员,第一时间阅读最新文章,会优先两天发表新文章.关注即可大礼包,准能为你节省不少钱! 浏览器是所有设备上使用最广泛的应用之一,如今,它 ...

  2. android q pc模式,安卓Q新增原生桌面模式:手机连接显示器变主机 心疼老罗!

    去年,锤子科技的创始人罗永浩在坚果R1的发布会上推出了坚果TNT工作站,坚果手机可充当"主机"与坚果TNT组成一个"桌面级电脑".但是坚果TNT工作站并没有如期 ...

  3. android如何适配平板,适用于平板电脑、大屏设备和可折叠设备的自适应布局

    将应用支持扩展到更大屏幕的设备(例如平板电脑.可折叠设备和 Chrome 操作系统设备)是扩大您的覆盖面和互动度的绝佳方式.平板电脑的增长率比去年同期 (YOY) 大幅增长了 30%,Chrome 操 ...

  4. android q桌面,Android Q带来全新桌面模式

    原标题:Android Q带来全新桌面模式 IT之家3月14日消息 谷歌在美国当地时间3月13日(北京时间14日凌晨)正式推送了Android Q的首个Beta版本,"亲儿子"Pi ...

  5. 鸿蒙桌面系统什么时候上线,鸿蒙OS全新PC桌面模式即将上线?回顾一下手机桌面系统的发展历程...

    说到在手机操作系统中内置大屏幕桌面模式,那要追溯到2011年,那一年摩托罗拉发布了一款型号为ME860(Atrix 4G)的安卓手机,采用4.0英寸的电容触摸屏,后置指纹识别,搭载英伟达Tegra 2 ...

  6. chrome显示比例 Android,Android版Chrome在大尺寸平板电脑中将默认采用桌面模式

    我们在Chromium Gerrit中发现了这个即将到来的功能的信息,其中对其功能进行了一些说明.根据描述,Android版谷歌浏览器在检测到设备拥有足够大的显示屏时,会自动请求网站的桌面版.虽然我们 ...

  7. 鸿蒙os桌面怎么布局好看,鸿蒙OS全新PC桌面模式即将上线?回顾一下手机桌面系统的发展历程...

    2017年的时候,三星推出了Galaxy S8系列手机,共分为Galaxy S8.Galaxy S8+两款产品,屏幕尺寸分别是5.8英寸和6.2英寸,搭载了10nm制程的骁龙835处理器,预装Andr ...

  8. android q 桌面模式,Android Q带来全新桌面模式

    IT之家3月14日消息 谷歌在美国当地时间3月13日(北京时间14日凌晨)正式推送了Android Q的首个Beta版本,"亲儿子"Pixel系列全系手机可以尝鲜体验这最新的系统. ...

  9. 华为鸿蒙桌面,鸿蒙OS全新PC桌面模式即将上线?回顾一下手机桌面系统的发展历程...

    说到在手机操作系统中内置大屏幕桌面模式,那要追溯到2011年,那一年摩托罗拉发布了一款型号为ME860(Atrix 4G)的安卓手机,采用4.0英寸的电容触摸屏,后置指纹识别,搭载英伟达Tegra 2 ...

最新文章

  1. leetcode-26-删除排序数组中的重复项
  2. 基于python的modbus协议编程_IM5D.6B利用(2.4G)无线模块实现远程控制(基于智能编程任务赛,2019版)...
  3. iphone上如何绘制饼图(使用CGContextAddArc)(原创)
  4. pkuseg-python的postag.zip在不能联网的服务器上的解决办法
  5. 错误的模糊应用(类继承问题)
  6. 电商移动促销页面设计素材PSD分层模板,轻松出稿稿
  7. java分布式dubbo_Dubbo剖析-搭建一个简单的分布式系统(1)
  8. MacBook远程桌面Windows使用Microsoft Remote Desktop for Mac_亲测使用
  9. Hadoop数字统计
  10. matlab中基于传递函数或者状态方程的幅频特性分析
  11. PCRF、PCEF、PCC(转帖)
  12. git三板斧--Linux
  13. Zilliqa官方挖矿指南中文版
  14. 关于存储优化型实例和大型数据仓库EC2实例选型
  15. c语言 链表 无头结点,C++ 单链表(无头结点)
  16. HTML精仿ios相册,高仿ios相册地图功能
  17. TencentOS tiny 移植到STM32F103全教程(基于标准库)
  18. 测试显卡用什么软件最好,用什么软件能检测显卡的好坏?
  19. 拓扑绝缘体 量子计算机,拓扑绝缘体的新应用:优化量子计算机关键组件!
  20. Cortex-M启动代码分析(以STM32F4为例)

热门文章

  1. Linux_Linux指令_lsof 指令
  2. 计算机教师结构化方式面试,市计算机:17名学生通过全国教师资格证结构化面试...
  3. webqq 机器人 java_跨平台QQ客户端iQQ 根据WebQQ3.0协议Java开发
  4. Spark开发环境搭建
  5. git(9)Git 内部原理,java入门视频百度网盘
  6. factor java_使用randomForest,Caret和factor变量预测栅格时出错
  7. OpenCV使用findContours()查找轮廓
  8. 华为鸿蒙系统建立生态链的环境,华为自研操作系统,怎么构建生态?感觉太难了?...
  9. R3300L Android相关的记录
  10. 设计模式星火01_单例模式