如何在Android 6中实现画中画(Picture-in-Picture)功能支持
Android 6 适配画中画(Picture-in-Picture)方案
Android 6(API 23)本身没有提供系统级的画中画API,市面上支持该系统的第三方应用都是通过自定义悬浮窗口模拟实现的,以下是具体实现方案:
1. 申请悬浮窗权限
Android 6需要动态申请SYSTEM_ALERT_WINDOW权限,代码示例如下:
// 检查是否拥有悬浮窗权限 if (!Settings.canDrawOverlays(context)) { Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + context.getPackageName())); startActivityForResult(intent, REQUEST_CODE_OVERLAY_PERMISSION); }
在onActivityResult中处理权限申请结果:
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE_OVERLAY_PERMISSION) { if (Settings.canDrawOverlays(this)) { // 权限已授予,初始化悬浮窗口 initFloatingWindow(); } } }
2. 创建自定义悬浮窗口
使用WindowManager添加悬浮View,核心代码如下:
private void initFloatingWindow() { WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, // Android 6 适配的窗口类型 Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_PHONE, // 窗口标记:不获取焦点、可触摸、位于屏幕顶层 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, PixelFormat.TRANSLUCENT ); // 设置窗口初始位置(右下角) params.gravity = Gravity.BOTTOM | Gravity.RIGHT; params.x = 50; params.y = 50; // 加载悬浮窗口布局(比如包含视频播放器的布局) View floatingView = LayoutInflater.from(this).inflate(R.layout.layout_floating_window, null); windowManager.addView(floatingView, params); // 处理悬浮窗口拖动逻辑 floatingView.setOnTouchListener(new View.OnTouchListener() { private int lastX, lastY; private int initialX, initialY; private long downTime; @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: lastX = (int) event.getRawX(); lastY = (int) event.getRawY(); initialX = lastX; initialY = lastY; downTime = System.currentTimeMillis(); break; case MotionEvent.ACTION_MOVE: int dx = (int) event.getRawX() - lastX; int dy = (int) event.getRawY() - lastY; params.x += dx; params.y += dy; windowManager.updateViewLayout(v, params); lastX = (int) event.getRawX(); lastY = (int) event.getRawY(); break; case MotionEvent.ACTION_UP: // 判断是点击还是拖动 if (System.currentTimeMillis() - downTime < 300 && Math.abs(event.getRawX() - initialX) < 20 && Math.abs(event.getRawY() - initialY) < 20) { // 点击事件:返回应用 Intent intent = new Intent(getApplicationContext(), MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } break; } return true; } }); }
3. 适配应用生命周期
监听应用前后台状态,控制悬浮窗口的显示/隐藏:
// 在Application中注册生命周期回调 public class MyApplication extends Application implements Application.ActivityLifecycleCallbacks { private int activityCount = 0; @Override public void onCreate() { super.onCreate(); registerActivityLifecycleCallbacks(this); } @Override public void onActivityStarted(Activity activity) { activityCount++; // 应用回到前台,隐藏悬浮窗口 if (activityCount == 1) { hideFloatingWindow(); } } @Override public void onActivityStopped(Activity activity) { activityCount--; // 应用退到后台,显示悬浮窗口(需已获取权限) if (activityCount == 0 && Settings.canDrawOverlays(this)) { showFloatingWindow(); } } // 实现其他生命周期方法(空实现即可) @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {} @Override public void onActivityResumed(Activity activity) {} @Override public void onActivityPaused(Activity activity) {} @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {} @Override public void onActivityDestroyed(Activity activity) {} }
4. 视频播放适配注意事项
- 将视频播放器的
SurfaceView或TextureView嵌入悬浮窗口布局,确保后台播放时不被销毁。 - 处理音频焦点:使用
AudioManager申请音频焦点,避免与其他应用音频冲突。 - 后台播放时,需保持
MediaPlayer或ExoPlayer的实例不被回收,可通过全局单例管理。
关键注意点
- 部分国产Android 6 ROM对悬浮窗权限有额外限制,需在应用内引导用户手动开启权限。
- 悬浮窗口要尽量轻量化,避免内存泄漏,在应用销毁时务必调用
windowManager.removeView(floatingView)移除窗口。 - 不同设备的窗口管理策略可能存在差异,需多机型测试适配。
内容的提问来源于stack exchange,提问作者AbbasAli nahad
相关产品推荐
相关产品推荐

