效果展示

前言

从 Android 8.0(API 级别 26)开始,Android 允许活动以画中画 (PiP) 模式启动。PiP 是一种特殊类型的多窗口模式,主要用于视频播放。它允许用户在固定在屏幕一角的小窗口中观看视频,同时在应用程序之间导航或浏览主屏幕上的内容。
PiP 利用 Android 7.0 中提供的多窗口 API 来提供固定的视频叠加窗口。要将 PiP 添加到您的应用程序,您需要注册支持 PiP 的 Activity,根据需要将您的 Activity 切换到 PiP 模式,并确保 UI 元素被隐藏并且当 Activity 处于 PiP 模式时视频播放继续。
PiP 窗口出现在屏幕的最顶层,位于系统选择的角落。

用户如何与画中画窗口交互

用户可以将画中画窗口拖到另一个位置。从 Android 12 开始,用户还可以:

单击窗口可显示全屏切换、关闭按钮、设置按钮和应用程序提供的自定义操作(例如,播放控件)。
双击窗口可在当前 PiP 大小和最大 PiP 大小之间切换。通过将窗口拖动到左边缘或右边缘来隐藏窗口;要取消隐藏窗口,请点击隐藏窗口的可见部分或将其拖出。
使用捏合缩放调整画中画窗口的大小。

应用控制当前活动何时进入画中画模式。这里有些例子:

当用户点击主页按钮(在按钮导航模式下)或向上滑动到主页(在手势导航模式下)时,活动可以进入画中画模式。(这就是 Google 地图在用户同时运行另一个活动时继续显示方向的方式。)
当用户从视频返回浏览其他内容时,您的应用可以将视频移至画中画模式。
当用户观看一集内容的结尾时,您的应用可以将视频切换到画中画模式。主屏幕显示有关该系列下一集的宣传或摘要信息。
应用程序可以为用户提供一种在观看视频时排队其他内容的方式。视频继续以画中画模式播放,而主屏幕显示内容选择活动。

声明画中画支持

默认情况下,系统不会自动支持应用程序的画中画。如果想在应用程序中支持画中画,请在清单中注册视频活动,方法是设置 android:supportsPictureInPicturetrue. 此外,指定 Activity 处理布局配置更改,以便在 PiP 模式转换期间发生布局更改时 Activity 不会重新启动。

<activityandroid:name=".TestViewActivity"android:theme="@style/Theme.PictureInPicture"android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"android:supportsPictureInPicture="true"android:exported="true"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter>
</activity>

将您的活动切换为画中画

要进入画中画模式,活动必须调用 enterPictureInPictureMode(). 例如,当用户单击应用程序 UI 中的专用按钮时,以下代码会将Activity切换到画中画模式:

private void minimize() {// Calculate the aspect ratio of the PiP screen.PictureInPictureParams.Builder mPictureInPictureParamsBuilder = new PictureInPictureParams.Builder();Rational aspectRatio = new Rational(binding.videoView.getWidth(), binding.videoView.getHeight());mPictureInPictureParamsBuilder.setAspectRatio(aspectRatio).build();enterPictureInPictureMode(mPictureInPictureParamsBuilder.build());}

如果希望包含将活动切换到画中画模式而不是进入后台的逻辑。例如,如果用户在应用程序导航时按下主页或最近按钮,谷歌地图就会切换到画中画模式。可以通过覆盖来捕获这种情况 onUserLeaveHint():

@Override
public void onUserLeaveHint () {if (iWantToBeInPipModeNow()) {enterPictureInPictureMode();}
}

从 Android 12 开始,您可以使用该 setAutoEnterEnabled 标志在手势导航模式下向上滑动到主页时提供更流畅的画中画模式转换。

要实现此功能:

1.用于setAutoEnterEnabled构造PictureInPictureParams.Builder,如下:

setPictureInPictureParams(new PictureInPictureParams.Builder().setAspectRatio(aspectRatio).setSourceRectHint(sourceRectHint).setAutoEnterEnabled(true).build());

2.setPictureInPictureParams最新 PictureInPictureParams消息。应用不应等待 onUserLeaveHint回调(就像在 Android 11 中所做的那样)。例如,setPictureInPictureParams如果宽高比发生变化,应用程序可能希望在第一次播放和任何后续播放时调用。

3.setAutoEnterEnabled(false)根据需要调用。例如,如果当前播放处于暂停状态,则视频应用进入 PiP 可能不是最佳选择。

画中画期间处理 UI

当activity进入或退出画中画模式时,系统调用 Activity.onPictureInPictureModeChanged() 或Fragment.onPictureInPictureModeChanged()。应该重写这些回调以重绘活动的 UI 元素。在画中画模式下,活动会显示在一个小窗口中,应用程序处于画中画模式时,用户无法与应用程序的 UI 元素进行交互,并且可能难以看到小的 UI 元素的详细信息。具有最少 UI 的视频播放活动可提供最佳用户体验。如果应用需要为画中画提供自定义操作,请参阅本文档中的添加控件。在 Activity 进入 PiP 之前移除其他 UI 元素,并在您的 Activity 再次变为全屏时恢复它们:

@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration configuration) {super.onPictureInPictureModeChanged(isInPictureInPictureMode, configuration);
}

退出画中画模式时支持更流畅的动画

从 Android 12 开始,该SourceRectHint 标志现在被重复使用以在退出画中画模式时实现更流畅的动画。退出时,系统使用当前可用的动画创建动画 sourceRectHint,无论是Rect用于进入画中画的原始动画还是Rect应用程序提供的更新动画。

要实现此功能,请按如下方式:

1.继续构建PictureInPictureParams和以sourceRectHint 获得aspectRatio 平滑的进入动画。

2.如有必要,请sourceRectHint 在系统开始退出转换之前更新。当系统即将退出画中画模式时,活动的视图层次结构将布置到其目标配置(例如,全屏)。应用程序可以将布局更改侦听器附加到其根视图或目标视图(例如视频播放器视图)以检测事件并在动画开始之前更新 sourceRectHint。

// 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());}
});

添加控件

当用户打开窗口的菜单时,画中画窗口可以显示控件(通过在移动设备上点击窗口,或从电视遥控器中选择菜单。)

如果一个应用程序有一个活动的媒体会话,那么将出现播放、暂停、下一个和上一个控件。

还可以在进入 PiP 模式之前通过 build PictureInPictureParams with显式指定自定义操作PictureInPictureParams.Builder.setActions() ,并在进入 PiP 模式时使用 enterPictureInPictureMode(android.app.PictureInPictureParams) or传递参数setPictureInPictureParams(android.app.PictureInPictureParams)。不能超过最大数getMaxNumPictureInPictureActions()

禁用非视频内容的无缝调整大小

Android 12 添加了setSeamlessResizeEnabled 标志,当在画中画窗口中调整非视频内容的大小时,它提供了更加平滑的淡入淡出动画。以前,在画中画窗口中调整非视频内容的大小可能会产生不和谐的视觉伪影。

setSeamlessResizeEnabled标志true默认设置为向后兼容。将此设置保留为true用于视频内容,并将其更改为false用于非视频内容。

要禁用非视频内容的无缝调整大小:

  setPictureInPictureParams(new PictureInPictureParams.Builder().setSeamlessResizeEnabled(false).build());

在画中画中继续播放视频

当活动切换到画中画时,系统会将活动置于暂停状态并调用活动的 onPause()方法。如果活动在画中画模式下暂停,则不应暂停视频播放,并且应继续播放。

在 Android 7.0 及更高版本中,当系统调用 Activity onStop()和 onStart(). 通过这样做,可以避免在 onPause() 中检查应用是否处于画中画模式并显式继续播放。

如果必须在onPause()实现中暂停播放,请通过适当地调用和处理播放来检查画中画模式isInPictureInPictureMode(),例如:

@Override
public void onPause() {// If called while in PiP mode, do not pause playbackif (isInPictureInPictureMode()) {// Continue playback...} else {// Use existing playback logic for paused Activity behavior....}
}

当活动从画中画模式切换回全屏模式时,系统会恢复活动并调用 onResume()方法。

使用单个播放活动进行画中画

在应用程序中,用户可能会在主屏幕上浏览内容时选择新视频,而视频播放活动处于画中画模式。以全屏模式在现有播放活动中播放新视频,而不是启动可能会使用户感到困惑的新活动。

为确保单个活动用于视频播放请求并根据需要切换到或退出画中画模式,请在清单中将活动设置 android:launchModesingleTask

<activity android:name="VideoActivity"...android:supportsPictureInPicture="true"android:launchMode="singleTask"...

在活动中,覆盖 onNewIntent() 并处理新视频,如果需要,停止任何现有视频播放。

最佳实践

在 RAM 较低的设备上可能会禁用 PiP。应用使用 PiP 之前,请通过调用 hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE).

画中画适用于播放全屏视频的活动。将活动切换到画中画模式时,请避免显示除视频内容之外的任何内容。跟踪活动何时进入画中画模式并隐藏 UI 元素,如在画中画 期间处理 UI 中所述。

当活动处于画中画模式时,默认情况下它不会获得输入焦点。要在 PiP 模式下接收输入事件,请使用 MediaSession.setCallback(). 有关使用的更多信息,setCallback()请参阅 显示正在播放的卡片。

当应用处于画中画模式时,画中画窗口中的视频播放可能会导致音频干扰其他应用,例如音乐播放器应用或语音搜索应用。为避免这种情况,请在开始播放视频时请求音频焦点,并处理音频焦点更改通知,如管理音频焦点中所述。如果 PiP 模式下收到音频焦点丢失通知,请暂停或停止视频播放。

当应用即将进入画中画时,请注意只有最上面的活动才会进入画中画。在某些情况下,例如在多窗口设备上,现在可能会显示下面的活动,并与画中画活动一起再次可见。应该相应地处理这种情况,包括下面获取onResume()onPause()回调的活动。用户也可能与活动交互。例如,如果显示了一个视频列表活动并且在画中画模式下正在播放视频活动,用户可能会从列表中选择一个新视频,并且画中画活动应该相应地更新。

Demo

activity

import android.app.PictureInPictureParams;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.util.Rational;
import android.view.View;
import android.widget.MediaController;import com.mz.demo.databinding.ActivityTestViewBinding;import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;public class TestViewActivity extends AppCompatActivity {private ActivityTestViewBinding binding;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);binding = ActivityTestViewBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());binding.button.setOnClickListener(v -> {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {minimize();}});initData();}private void initData() {MediaController mediaController = new MediaController(this);binding.videoView.setMediaController(mediaController);mediaController.setMediaPlayer(binding.videoView);binding.videoView.setVideoPath("https://stream7.iqilu.com/10339/upload_transcode/202002/18/20200218114723HDu3hhxqIT.mp4");binding.videoView.start();}@RequiresApi(api = Build.VERSION_CODES.O)private void minimize() {// Calculate the aspect ratio of the PiP screen.PictureInPictureParams.Builder mPictureInPictureParamsBuilder = new PictureInPictureParams.Builder();Rational aspectRatio = new Rational(binding.videoView.getWidth(), binding.videoView.getHeight());mPictureInPictureParamsBuilder.setAspectRatio(aspectRatio).build();enterPictureInPictureMode(mPictureInPictureParamsBuilder.build());}@Overridepublic void onConfigurationChanged(@NonNull Configuration newConfig) {super.onConfigurationChanged(newConfig);adjustFullScreen(newConfig);}@Overridepublic void onWindowFocusChanged(boolean hasFocus) {super.onWindowFocusChanged(hasFocus);if (hasFocus) {adjustFullScreen(getResources().getConfiguration());}}private void adjustFullScreen(Configuration config) {final WindowInsetsControllerCompat insetsController =ViewCompat.getWindowInsetsController(getWindow().getDecorView());if (insetsController == null)return;if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {insetsController.hide(WindowInsetsCompat.Type.systemBars());binding.button.setVisibility(View.GONE);} else {insetsController.show(WindowInsetsCompat.Type.systemBars());binding.button.setVisibility(View.VISIBLE);}}@Overridepublic void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration configuration) {super.onPictureInPictureModeChanged(isInPictureInPictureMode, configuration);}@Overrideprotected void onDestroy() {super.onDestroy();binding.videoView.stopPlayback();}
}

xml布局

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"xmlns:app="http://schemas.android.com/apk/res-auto"tools:context=".TestViewActivity"><Buttonandroid:id="@+id/button"android:layout_width="wrap_content"android:layout_height="wrap_content"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"/><VideoViewandroid:id="@+id/videoView"android:layout_width="match_parent"android:layout_height="0dp"app:layout_constraintDimensionRatio="w,9:16"app:layout_constraintStart_toStartOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

PS:acvitiy的style设置成无actionBar的

<style name="Theme.PictureInPicture" parent="Theme.MaterialComponents.DayNight.NoActionBar"/>

清单文件中这样添加:

<activityandroid:name=".TestViewActivity"android:theme="@style/Theme.PictureInPicture"android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"android:supportsPictureInPicture="true"android:exported="true"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter>
</activity>

Android悬浮窗口-画中画功能相关推荐

  1. Android 悬浮窗口

    Android 悬浮窗口 一.创建悬浮窗口步骤     1.实现一个ViewGroup类,作为悬浮窗口的界面类,以便在里面重写onInterceptTouchEvent和onTouchEvent方法, ...

  2. android悬浮窗口的实现

    当我们在手机上使用360安全卫士时,手机屏幕上时刻都会出现一个小浮动窗口,点击该浮动窗口可跳转到安全卫士的操作界面,而且该浮动窗口不受其他activity的覆盖影响仍然可见(多米音乐也有相关的和主界面 ...

  3. Android 悬浮窗语音识别功能开发详解

    笔者是一个普通不能再普通的程序员,本着出处兴趣,花时间研究了一下,想实现手机的悬浮窗语音识别功能,这样不影响自己其它操作的,语音识别技术是用百度云语音sdk,应该不难实现,很难实现就是核心语音识别技术 ...

  4. android 悬浮窗口和主界面同时显示,Android 悬浮窗口(及解决6.0以上无法显示问题)...

    思路实现 通过WindowManager添加一个View,创建一个系统顶级的窗口,实现悬浮窗口的效果. 本篇思路,来源于郭霖大神的悬浮窗口教程. 大致介绍WindowManager 类 创建的对象: ...

  5. Android 悬浮窗口(及解决6.0以上无法显示问题)

    思路实现 通过WindowManager添加一个View,创建一个系统顶级的窗口,实现悬浮窗口的效果. 本篇思路,来源于郭霖大神的悬浮窗口教程. 大致介绍WindowManager 类 创建的对象: ...

  6. Android悬浮窗口开发

    注意: 1. 需要Context上下文,在绝大多数非原生系统上,context上下文会影响悬浮窗的显示范围.在MIUI和华为等国产系统上,使用Activity的Context只能显示在Activity ...

  7. Android悬浮窗口

    随时随地技术实战干货,获取项目源码.学习资料,请关注源代码社区公众号(ydmsq666) FloatService: package com.home.floatwindow;import andro ...

  8. Android 悬浮窗口(一)

    前言 这章主要介绍 悬浮窗口 主要分为2种 1:悬浮在所有窗口上 2:悬浮在当前窗口上 这章讲述 悬浮在所有窗口上 1 准备 Android studio 4.1.1 或以上 win7 或以上 2 悬 ...

  9. android 悬浮窗口透明,悬浮视频窗口可调透明度_LG Optimus G Pro_手机Android频道-中关村在线...

    在多媒体方面,由于LG-E985T配备的高通骁龙600有着强劲的CPU.GPU.多媒体支持,所以理论上来说,LG-E985T的多媒体实力理论上来讲自然是不俗.接下来我们实际测试一下它对于高清视频播放的 ...

最新文章

  1. 情怀java手机网游_经典端游移植手游 “情怀”赋予老IP全新活力
  2. 【随机过程】随机过程之泊松过程的推广
  3. mysql putty 备份_Linux下mysql数据库的备份-putty
  4. html获取data-*值,html5 获取和设置data-*属性值的四种方法讲解
  5. put url带参数_一道腾讯面试题:如何快速判断某 URL 是否在 20 亿的网址 URL 集合中?...
  6. 加载osgb数据转换不能用_在ArcGIS Pro中OSGB数据转换及发布服务流程
  7. [bzoj3531][Sdoi2014]旅行
  8. 2.Linux 高性能服务器编程 --- IP 协议详解
  9. java servlet的包下载_javax.servlet jar包下载_javax.servlet jar包官方下载-太平洋下载中心...
  10. 汉王考勤机 二次开发
  11. 《统计学》第八版贾俊平第八章假设检验知识点总结及课后习题答案
  12. eNSP下园区网综合实验分步配置(1)Eth-Trunk、Vlan、Trunk
  13. 华硕Z99jc安装win7出现安装程序无法定位现有系统分区,也无法创建新的系统分区解决办法...
  14. 腾讯AI八篇论文入选顶级医学影像会议MICCAI ,涉及病理癌症图像分类等
  15. 后缀表达式的求值(c语言)
  16. 时间子系统10_hpet时钟初始化
  17. 痞子衡嵌入式:IAR环境下无法直接下载调试i.MXRT分散链接工程的解决方案(宏文件.mac+双Flashloader)...
  18. 常见的电子器件,这篇文章总结得很到位,你还不收藏吗?
  19. 观众关注人数超4万,CIOE信息通信展热度持续高涨
  20. Resetting a lost Admin password

热门文章

  1. 阿里云计算巢软件免费试用中心正式上线,企业用户可免费试用1个月
  2. angularjs2学习教程
  3. 互联网晚报 | 1月13日 星期四 | 恒驰5首车下线;抖音电商测试快递服务“音尊达”;中国移动10086 App月底停止运营...
  4. 如何重写equals方法
  5. Vue自定义组件--输入框的双向绑定--自动切换输入法的录入框
  6. 深度学习优化算法的总结与梳理(从 SGD 到 AdamW 原理和代码解读)
  7. Comparing the Effects of DNS, DoT, and DoH
  8. 计算机毕业设计-生鲜配送超市商城系统
  9. IT战略规划项目方法论(德勤 埃森哲 IBM 凯捷)
  10. javaSE探赜索隐四<数组>