使用画中画 (PIP) 功能添加视频

从 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 标志提供大量 在画中画模式下使用手势过渡到视频内容时更流畅的动画 导航 - 例如,从全屏模式向上滑动到主屏幕时。

请完成以下步骤进行此项更改,并参阅此示例 参考:

  1. 使用 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());
    
  2. 尽早使用最新的 PictureInPictureParams 调用 setPictureInPictureParams。应用不会等到 onUserLeaveHint 回调(就像在 Android 11 中所做的那样)。

    例如,您可能想调用 setPictureInPictureParams, 第一次播放以及之后的任何播放(如果宽高比更改)。

  3. 仅在必要时调用 setAutoEnterEnabled(false)。例如: 如果当前播放已暂停 状态。

设置用于进入和退出画中画模式的适当 sourceRectHint

从 Android 8.0 中引入画中画功能开始,setSourceRectHint 用于表示在从 0 到 15 之间, 画中画 - 例如视频播放器中的视频观看边界。

在 Android 12 中,系统使用 sourceRectHint 实现更流畅 以及退出画中画模式时显示的动画效果。

如需正确设置用于进入和退出画中画模式的 sourceRectHint,请执行以下操作:

  1. 构造 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);
    
  2. 如有必要,请在系统启动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)