从 Android 8.0(API 级别 26)开始,Android 允许以画中画 (PiP) 模式启动 activity。画中画是一种特殊类型的多窗口模式,最常用于视频播放。使用该模式,用户可以通过固定到屏幕一角的小窗口观看视频,同时在应用之间进行导航或浏览主屏幕上的内容。
画中画利用 Android 7.0 中提供的多窗口模式 API 来提供固定的视频叠加窗口。如要将画中画添加到您的应用中,您需要注册支持画中画的 activity,根据需要将 activity 切换为画中画模式,并确保当 activity 处于画中画模式时,界面元素处于隐藏状态且视频能够继续播放。
画中画窗口会显示在屏幕的最顶层,位于 系统。
运行画中画的兼容 Android TV OS 设备也支持画中画 Android 14(API 级别 34)或更高版本。虽然二者有许多相似之处,但 使用 Cloud Shell 时 电视上的画中画。
用户如何与画中画窗口互动
用户可以将画中画窗口拖动到其他位置。从 Android 12 开始,用户还可以执行以下操作:
点按一次该窗口可显示全屏切换开关、关闭按钮、设置按钮以及应用提供的自定义操作(例如播放控件)。
点按两次即可在当前画中画大小和最大画中画大小之间切换 或最小画中画大小,例如点按两次最大化窗口 反之亦然。
将窗口拖动到左侧或右侧边缘,即可隐藏该窗口。为了解除 点按已隐藏窗口的可见部分或将其拖出。
使用“双指张合即可缩放”手势可调整画中画窗口的大小。
您的应用会控制当前 activity 在何时进入画中画模式。下面是一些示例:
activity 可以在用户点按主屏幕按钮或滑动时进入画中画模式 回家。这样,Google 地图就会继续显示 用户同时运行另一个 activity。
您的应用可以在用户从某个位置返回时将视频切换到画中画模式 以便浏览其他内容。
您的应用可以在用户观看到某集内容结束时,将视频切换到画中画模式。主屏幕会显示有关这部电视剧下一集的宣传信息或剧情摘要信息。
您的应用可以提供一种方式,让用户可以在观看视频时将其他内容加入播放队列。视频会在主画面模式下继续以画中画模式播放, 屏幕上有一个内容选择 activity。
声明画中画支持
默认情况下,系统不会自动为应用提供画中画支持。如果您想
在您的应用中支持画中画功能,请在清单中注册视频 activity,方法是
将 android:supportsPictureInPicture
设为 true
。此外,请指定
activity 会处理布局配置变更,这样您的 activity 就不会
在画中画模式转换期间发生布局更改时重新启动。
<activity android:name="VideoActivity"
android:supportsPictureInPicture="true"
android:configChanges=
"screenSize|smallestScreenSize|screenLayout|orientation"
...
将 activity 切换到画中画模式
从 Android 12 开始,您可以通过设置
将 setAutoEnterEnabled
标志设置为 true
。使用此设置时,系统会
根据需要自动切换到画中画模式,而不必明确调用
onUserLeaveHint
中的 enterPictureInPictureMode()
。这段代码包含
这还带来了更流畅的过渡效果有关详情,请参阅 Make
从手势导航过渡到画中画模式更流畅。
如果您以 Android 11 或更低版本为目标平台,则 activity 必须调用
enterPictureInPictureMode()
即可切换到画中画模式。例如,以下代码会将 activity 切换为
当用户点击应用界面中的专用按钮时,进入画中画模式:
Kotlin
override fun onActionClicked(action: Action) { if (action.id.toInt() == R.id.lb_control_picture_in_picture) { activity?.enterPictureInPictureMode() return } }
Java
@Override public void onActionClicked(Action action) { if (action.getId() == R.id.lb_control_picture_in_picture) { getActivity().enterPictureInPictureMode(); return; } ... }
您可能需要添加将 activity 切换到画中画模式的逻辑
即进入后台运行例如,如果用户在 Google 地图导航时按下主屏幕或最近使用的应用按钮,则该应用会切换到画中画模式。您可以通过替换 onUserLeaveHint()
来实现这一目的:
Kotlin
override fun onUserLeaveHint() { if (iWantToBeInPipModeNow()) { enterPictureInPictureMode() } }
Java
@Override public void onUserLeaveHint () { if (iWantToBeInPipModeNow()) { enterPictureInPictureMode(); } }
建议:为用户提供完善的画中画过渡体验
Android 12 对动画过渡的外观进行了重大改进 全屏和画中画窗口之间切换。我们强烈建议您 适用的更改;完成这些操作后,这些更改会自动扩展到 大屏设备,例如可折叠设备和平板电脑,而无需任何进一步操作。
如果您的应用未包含适用的更新,画中画过渡功能仍保持不变 功能正常,但动画不够精美。例如,将 从全屏切换到画中画模式可能会导致画中画窗口在 然后再重新显示。
这些变更涉及以下内容。
- 使从手势导航过渡到画中画模式更顺畅
- 设置用于进入和退出画中画模式的适当
sourceRectHint
- 为非视频内容停用无缝大小调整
请参阅 Android Kotlin PictureInPicture 示例 作为参考,帮助您实现出色的过渡体验。
更加顺畅地从手势导航模式过渡到画中画模式
从 Android 12 开始,setAutoEnterEnabled
标志提供大量
在画中画模式下使用手势过渡到视频内容时更流畅的动画
导航 - 例如,从全屏模式向上滑动到主屏幕时。
请完成以下步骤进行此项更改,并参阅此示例 参考:
使用
setAutoEnterEnabled
构造PictureInPictureParams.Builder
:Kotlin
setPictureInPictureParams(PictureInPictureParams.Builder() .setAspectRatio(aspectRatio) .setSourceRectHint(sourceRectHint) .setAutoEnterEnabled(true) .build())
Java
setPictureInPictureParams(new PictureInPictureParams.Builder() .setAspectRatio(aspectRatio) .setSourceRectHint(sourceRectHint) .setAutoEnterEnabled(true) .build());
尽早使用最新的
PictureInPictureParams
调用setPictureInPictureParams
。应用不会等到onUserLeaveHint
回调(就像在 Android 11 中所做的那样)。例如,您可能想调用
setPictureInPictureParams
, 第一次播放以及之后的任何播放(如果宽高比更改)。仅在必要时调用
setAutoEnterEnabled(false)
。例如: 如果当前播放已暂停 状态。
设置用于进入和退出画中画模式的适当 sourceRectHint
从 Android 8.0 中引入画中画功能开始,setSourceRectHint
用于表示在从 0 到 15 之间,
画中画 - 例如视频播放器中的视频观看边界。
在 Android 12 中,系统使用 sourceRectHint
实现更流畅
以及退出画中画模式时显示的动画效果。
如需正确设置用于进入和退出画中画模式的 sourceRectHint
,请执行以下操作:
构造
PictureInPictureParams
使用sourceRectHint
作为合适的边界。建议您同时附上 视频播放器的布局更改监听器:Kotlin
val mOnLayoutChangeListener = OnLayoutChangeListener { v: View?, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int, newLeft: Int, newTop: Int, newRight: Int, newBottom: Int -> val sourceRectHint = Rect() mYourVideoView.getGlobalVisibleRect(sourceRectHint) val builder = PictureInPictureParams.Builder() .setSourceRectHint(sourceRectHint) setPictureInPictureParams(builder.build()) } mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener)
Java
private final View.OnLayoutChangeListener mOnLayoutChangeListener = (v, oldLeft, oldTop, oldRight, oldBottom, newLeft, newTop, newRight, newBottom) -> { final Rect sourceRectHint = new Rect(); mYourVideoView.getGlobalVisibleRect(sourceRectHint); final PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder() .setSourceRectHint(sourceRectHint); setPictureInPictureParams(builder.build()); }; mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener);
如有必要,请在系统启动
sourceRectHint
退出过渡。当系统即将退出画中画模式时,activity 的 视图层次结构的布局与其目标配置(例如, 全屏)。应用可以将布局更改监听器附加到其根视图 或目标视图(例如视频播放器视图)来检测事件, 在动画开始前更新sourceRectHint
。Kotlin
// Listener is called immediately after the user exits PiP but before animating. playerView.addOnLayoutChangeListener { _, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom -> if (left != oldLeft || right != oldRight || top != oldTop || bottom != oldBottom) { // The playerView's bounds changed, update the source hint rect to // reflect its new bounds. val sourceRectHint = Rect() playerView.getGlobalVisibleRect(sourceRectHint) setPictureInPictureParams( PictureInPictureParams.Builder() .setSourceRectHint(sourceRectHint) .build() ) } }
Java
// Listener is called right after the user exits PiP but before // animating. playerView.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { if (left != oldLeft || right != oldRight || top != oldTop || bottom != oldBottom) { // The playerView's bounds changed, update the source hint rect to // reflect its new bounds. final Rect sourceRectHint = new Rect(); playerView.getGlobalVisibleRect(sourceRectHint); setPictureInPictureParams( new PictureInPictureParams.Builder() .setSourceRectHint(sourceRectHint) .build()); } });
为非视频内容停用无缝大小调整
Android 12 添加了 setSeamlessResizeEnabled
标记,该标记提供了
在画中画中调整非视频内容的大小时,交替淡变动画更流畅
窗口。以前,在画中画窗口中调整非视频内容的大小会导致
令人不适的视觉伪影。
如需停用非视频内容的无缝大小调整,请编写以下代码:
Kotlin
setPictureInPictureParams(PictureInPictureParams.Builder() .setSeamlessResizeEnabled(false) .build())
Java
setPictureInPictureParams(new PictureInPictureParams.Builder() .setSeamlessResizeEnabled(false) .build());
在画中画期间处理界面
当 Activity 进入或退出画中画模式时,系统会调用
Activity.onPictureInPictureModeChanged()
或
Fragment.onPictureInPictureModeChanged()
。
您应替换这些回调以重新绘制 Activity 的界面元素。请注意,在画中画模式下,您的 activity 会在一个小窗口中显示。在画中画模式下,用户无法与应用的界面元素互动,并且可能很难看清小界面元素的详细信息。界面极简的视频播放 activity 可提供最佳的用户体验。
如果您的应用需要提供自定义操作以使用画中画功能,请参阅添加 控件。在 activity 进入画中画模式之前移除其他界面元素,并在 activity 再次变为全屏时恢复这些元素:
Kotlin
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) { if (isInPictureInPictureMode) { // Hide the full-screen UI (controls, etc.) while in PiP mode. } else { // Restore the full-screen UI. } }
Java
@Override public void onPictureInPictureModeChanged (boolean isInPictureInPictureMode, Configuration newConfig) { if (isInPictureInPictureMode) { // Hide the full-screen UI (controls, etc.) while in PiP mode. } else { // Restore the full-screen UI. ... } }
添加控件
画中画窗口可以在用户打开窗口菜单(通过点按移动设备上的窗口或使用电视遥控器选择菜单)时显示控件。
如果应用具有活跃的媒体 会话,然后播放 暂停、下一个和上一个控件会显示。
您还可以通过构建
PictureInPictureParams
替换为
PictureInPictureParams.Builder.setActions()
,并在使用
enterPictureInPictureMode(android.app.PictureInPictureParams)
或
setPictureInPictureParams(android.app.PictureInPictureParams)
。
请注意,如果您尝试添加的控件数量超过 getMaxNumPictureInPictureActions()
,则系统只会添加上限数量的控件。
在画中画模式下继续播放视频
当您的 activity 切换到画中画模式时,系统会将该 activity 置于“已暂停”状态
并调用 activity 的
onPause()
方法。视频
不应暂停播放,而是继续播放,前提是:
在切换到画中画模式时暂停。
在 Android 7.0 及更高版本中,当系统调用 activity 的 onStop()
时,您应暂停视频播放;当系统调用 activity 的 onStart()
时,您应恢复视频播放。如此一来,
因此,您无需在 onPause()
下检查应用是否处于画中画模式,
继续播放。
如果您尚未将 setAutoEnterEnabled
标志设置为 true
,则需要
在您的 onPause()
实现中暂停播放,通过调用
isInPictureInPictureMode()
并适当处理播放。例如:
Kotlin
override fun onPause() { super.onPause() // If called while in PiP mode, do not pause playback if (isInPictureInPictureMode) { // Continue playback } else { // Use existing playback logic for paused Activity behavior. } }
Java
@Override public void onPause() { // If called while in PiP mode, do not pause playback if (isInPictureInPictureMode()) { // Continue playback ... } else { // Use existing playback logic for paused Activity behavior. ... } }
当您的 activity 从画中画模式切换回全屏模式时,系统会
恢复您的 activity 并调用您的
onResume()
方法。
使用单个播放活动进行画中画
在您的应用中,用户浏览网页上的内容时,可能会选择新视频 主屏幕,而视频播放 activity 处于画中画模式。播放新视频 而不是在现有播放活动中以全屏模式启动 可能会使用户感到困惑的新 activity
为了确保将一个 activity 用于视频播放请求并切换
根据需要进入或退出画中画模式,请将 activity 的 android:launchMode
设置为
singleTask
:
<activity android:name="VideoActivity"
...
android:supportsPictureInPicture="true"
android:launchMode="singleTask"
...
在您的 activity 中,替换 onNewIntent()
并处理新的视频,从而根据需要停止任何现有的视频播放。
最佳做法
低 RAM 设备可能无法使用画中画模式。在应用使用画中画之前,请务必通过调用 hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
进行检查以确保可以使用画中画。
画中画旨在用于播放全屏视频的 activity。将 activity 切换到画中画模式时,请避免显示视频内容以外的任何内容。何时跟踪 您的 Activity 将进入画中画模式并隐藏界面元素,如处理界面 画中画。
当 activity 进入画中画模式后,默认不会获得输入焦点。如需在画中画模式下接收输入事件,请使用 MediaSession.setCallback()
。如需详细了解如何使用 setCallback()
,请参阅显示“闻曲知音”
卡片。
当您的应用处于画中画模式时,画中画窗口中的视频播放可能会导致音频 干扰其他应用(例如音乐播放器应用或语音搜索应用)。 为避免这种情况,请在开始播放视频时请求音频焦点,并处理 音频焦点更改通知,如管理音频中所述 专注。如果您收到通知 在画中画模式下,音频焦点丢失、暂停或停止视频播放。
当您的应用即将进入画中画模式时,请注意只有顶层 activity 会进入
进入画中画模式。在某些情况下(例如在多窗口设备上),此时系统可能会显示下层 activity,在画中画 activity 旁边,您可能会再次看到下层 activity。您应相应地处理这种情况,包括以下 activity 获取 onResume()
或 onPause()
回调。用户也有可能与该 activity 互动。例如,如果您的视频列表 activity 正在显示,视频播放 activity 处于画中画模式,用户可能会从列表中选择新视频,画中画 activity 应相应地进行更新。
更多示例代码
要下载使用 Kotlin 编写的示例应用,请参阅 Android PictureInPicture 示例 (Kotlin)。