Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >可折叠设备的桌面模式

可折叠设备的桌面模式

作者头像
Android 开发者
发布于 2022-03-09 07:35:21
发布于 2022-03-09 07:35:21
2.4K00
代码可运行
举报
文章被收录于专栏:Android 开发者Android 开发者
运行总次数:0
代码可运行

展开您的视频播放体验

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

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

一个很好的例子是 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 库添加到项目中,并且声明相关依赖:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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> 监听布局变化:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 (屏幕底部)。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 恰当位置函数的一种可能的实现如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    /**
     * 返回折叠处相对于布局的位置 
     */
    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
 }

总结

在本文中,您学习了如何通过实现支持桌面模式的灵活布局来改善可折叠设备上媒体应用的用户体验。

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

更多资源

欢迎您 点击这里 向我们提交反馈,或分享您喜欢的内容、发现的问题。您的反馈对我们非常重要,感谢您的支持!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/08/27 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【翻译】MotionLayout实现折叠工具栏(Part 2)
2018-08-27 by Liuqingwen | Tags: Android 翻译 | Hits
IT自学不成才
2019/01/08
1.7K0
Android Studio 4.0+ 中新的 UI 层次结构调试工具
调试 UI 的问题有时很棘手,Android Studio 4.0 内置了全新的布局检查器 (Layout Inspector),它的使用效果类似 Chrome 开发者工具,可以帮助开发者调试 Android 应用的 UI (用户界面)。布局检查器可用于设备和 Android 模拟器,它可以展示视图的层次结构。该工具有助于定位由根节点引起的问题。和上一个版本不同的是,新版本的布局检查器可以以三维的视角来展现视图层次结构,您可以直观地看到视图的布局方式。通过该工具您可以逐层来检查视图层次结构,同时它还会展示所有视图的属性,包括继承自视图父类的属性。
Android 开发者
2024/02/22
2.6K0
Android Studio 4.0+ 中新的 UI 层次结构调试工具
kotlin--StateFlow运用
StateFlow当值发生变化,就会将值发送出去,下流就可以接收到新值。在某些场景下,StateFlow比LiveData更适用 效果: 1.定义ViewModel StateFlow需要初始值 package com.aruba.flowapplyapplication.viewmodel import android.view.View import androidx.lifecycle.ViewModel import kotlinx.coroutines.flow.MutableStateFl
aruba
2021/12/06
6610
kotlin--StateFlow运用
ConstraintLayout 使用详解,减少嵌套 UI, 提升性能
对于初学者来说,可能觉得ConstraintLayout属性多,且属性长而弃用它,那你错失了这个大宝贝。
程序员徐公
2023/03/08
1.7K0
ConstraintLayout 使用详解,减少嵌套 UI, 提升性能
Android入门教程 | 使用 ConstraintLayout 构建自适应界面
ConstraintLayout 可使用扁平视图层次结构(无嵌套视图组)创建复杂的大型布局。它与 RelativeLayout 相似,其中所有的视图均根据同级视图与父布局之间的关系进行布局,但其灵活性要高于 RelativeLayout,并且更易于与 Android Studio 的布局编辑器配合使用。
Android_anzi
2021/12/07
2.6K0
【翻译】MotionLayout实现折叠工具栏(Part 1)
2018-08-13 by Liuqingwen | Tags: Android 翻译 | Hits
IT自学不成才
2019/01/08
2K0
项目需求讨论 — ConstraintLayout 详细使用教程
关于ConstraintLayout的文章网上一抓一大把,而且ConstraintLayout在16年就已经出来了,但是我一直没有试着去使用(别问我为什么不去使用,当然是因为懒啊)。毕竟前面的LinearLayout搭配RelativeLayout用习惯了,但是毕竟能减少布局的嵌套。还是要抱着多学习的方式去接触。所以写下文章作为总结。
青蛙要fly
2018/08/29
1.7K0
项目需求讨论 — ConstraintLayout 详细使用教程
Android 完全符合规则但很头疼的Json映射成一个树结构且可折叠的列表?
前些天有个朋友问我,要实现一个树状的列表要怎么做,根据一个完全符合规则但是却很头疼的一个Json解析来实现,见下格式,对于有些Android开发者来说,这个Json或许并不友好,没有办法直接转成实体类,其实这一串Json解析映射成可折叠列表也并不难!
第三女神程忆难
2021/01/29
7000
Android 完全符合规则但很头疼的Json映射成一个树结构且可折叠的列表?
详解 | 为可折叠设备构建响应式 UI
Android 设备的屏幕尺寸日新月异,随着平板和可折叠设备的普及度越来越高,在开发响应式用户界面时,了解您应用的窗口尺寸和状态显得尤为重要。Jetpack WindowManager 现已进入 beta 测试阶段,这个库提供了与 Android 框架中 WindowManager 比较相似的功能,包括了对支持响应式 UI、检测屏幕改变的回调适配器和测试窗口 API 的支持。但 Jetpack WindowManager 还新增了对可折叠设备和 ChromeOS 这类窗口环境的支持。
Android 开发者
2022/03/10
1.4K0
详解 | 为可折叠设备构建响应式 UI
突破传统动画:探索MotionLayout的独特优势
在移动应用程序开发中,动画和过渡效果是提升用户体验的重要元素。Android提供了丰富的动画功能,而MotionLayout作为Android Jetpack中的一个组件,为我们带来了更强大、更灵活的动画工具。本文将深入介绍MotionLayout的使用和原理,帮助您掌握这个令人兴奋的技术。
Rouse
2023/08/31
3500
突破传统动画:探索MotionLayout的独特优势
【约束布局】ConstraintLayout 引导线 Guideline 约束 ( 简介 | 可视化操作 | 属性 | 水平引导线 | 垂直引导线 | 开始结束尺寸 | 百分比位置 | 约束组件 )
① 方向属性 : android:orientation=“horizontal” ; 取值 horizontal / vertical ;
韩曙亮
2023/03/27
4K0
【约束布局】ConstraintLayout 引导线 Guideline 约束 ( 简介 | 可视化操作 | 属性 | 水平引导线 | 垂直引导线 | 开始结束尺寸 | 百分比位置 | 约束组件 )
ConstraintLayout(约束布局)的使用
这些属性会引用另一个控件的id或者parent(这会引用父容器,即ConstraintLayout)
用户1205080
2018/10/18
2.3K0
ConstraintLayout(约束布局)的使用
实践 | Google I/O 应用是如何适配大尺寸屏幕 UI 的?
5 月 18 日至 20 日,我们以完全线上的形式举办了 Google 每年一度的 I/O 开发者大会,其中包括 112 场会议、151 个 Codelab、79 场开发者聚会、29 场研讨会,以及众多令人兴奋的发布。尽管今年的大会没有发布新版的 Google I/O 应用,我们仍然更新了代码库来展示时下 Android 开发最新的一些特性和趋势。
Android 开发者
2022/03/10
2.1K0
实践 | Google I/O 应用是如何适配大尺寸屏幕 UI 的?
Android经典实战之约束布局ConstraintLayout的实用技巧和经验
ConstraintLayout 是 Android 中一种强大的布局管理器,能够帮助你创建复杂而灵活的布局。它通过约束系统将一个 View 的位置和大小与其他 View 或父布局联系起来,使得布局代码更加简洁且易于维护。
AntDream
2024/08/19
3140
Android经典实战之约束布局ConstraintLayout的实用技巧和经验
Constraint Layout 2.0 用法详解
Constraint Layout 是最受欢迎的 Jetpack 库之一,它的 2.0 正式版本也发布啦 (目前最新版本 2.1.0-alpha1)!也许您已熟悉了 Constraint Layout 1.1 版本中的功能,并开始用它来快速构建复杂的页面布局,而新版本除了包含 1.1 版本中的所有功能之外,还在 Android Studio 中集成了可以直接预览 XML 的工具,甚至可以直接在预览界面中对布局进行编辑。
Android 开发者
2020/12/15
2.3K0
Constraint Layout 2.0 用法详解
ConstraintLayout2.0一篇写不完之Carousel
Carousel是一个Motion Helper,它可以轻松构建自定义的Carousel视图,显示用户可以浏览的元素列表。与实现此类视图的其他解决方案相比,Carousel可以利用MotionLayout迅速为轮播创建复杂的动画效果。
用户1907613
2021/06/17
1.5K0
ConstraintLayout2.0一篇写不完之Carousel
ConstraintLayout概要
约束布局ConstraintLayout 是一个ViewGroup,可以在Api9以上的Android系统使用它,它的出现主要是为了解决布局嵌套过多的问题,以灵活的方式定位和调整小部件。
码客说
2020/07/03
9160
强大的ConstraintLayout:使用ConstraintLayout打造响应式UI
约束布局ConstraintLayout发布(2017年)至今已经好几个年头了。经过几个版本的功能迭代,现阶段的ConstraintLayout相当强大,80%以上的复杂界面都可以使用ConstraintLayout来实现;剩下的20%里,有80%是没充分利用好ConstraintLayout的特性来实现,最后的20%,才是Android Support 包团队需要加油补上的。
Kkkiro
2019/07/19
3K0
太有意思了,教你实现实现王者荣耀团战!
https://juejin.im/post/6856743286653386760
Java技术江湖
2020/09/07
1.2K0
太有意思了,教你实现实现王者荣耀团战!
再学一次ConstraintLayout 一些新特性
首先,ConstraintLayout是一个新的布局,它是直接继承自ViewGroup的,所以在兼容性方面是非常好的.官方称可以兼容到API 9.可以放心食用.
Android技术干货分享
2019/03/28
1.7K0
再学一次ConstraintLayout 一些新特性
推荐阅读
相关推荐
【翻译】MotionLayout实现折叠工具栏(Part 2)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验