Flutter PIP 插件 ---- Android

在 Flutter Android 应用中实现画中画功能

画中画(Picture-in-Picture, PiP)模式允许您的应用在一个固定在屏幕角落的小窗口中运行,同时用户可以与其他应用进行交互。本指南将介绍如何在 Flutter Android 应用中实现画中画功能,包括其局限性和解决方案。

项目地址

flutter_pip

前提条件

  • 需要 Android 8.0 (API level 26) 或更高版本才能完全支持画中画功能

  • 基本的 Flutter 插件开发知识

  • 基本的 Android 开发知识

实现概述

实现包含两个主要组件:

  1. FlutterPipController: 处理画中画功能和状态管理

  2. FlutterPipPlugin: 桥接 Flutter 和原生 Android 代码

主要特性

  • 画中画模式支持检测

  • 自定义宽高比配置

  • 平滑过渡的源矩形提示

  • 画中画状态监控和回调

  • 非视频内容的交叉淡入淡出动画

核心实现

1. 检查画中画支持

在使用画中画之前,我们需要检查设备是否支持:

java 复制代码
public boolean isSupported() {
    Activity activity = mActivity.get();
    if (activity == null) {
        return false;
    }

    // Requires Android 8.0 (API 26) or higher
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
        return false;
    }

    final PackageManager pm = activity.getApplicationContext().getPackageManager();
    return pm != null && pm.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE);
}

2. 配置画中画参数

画中画模式可以自定义几个参数:

java 复制代码
public boolean setup(@Nullable Rational aspectRatio,
                    @Nullable Boolean autoEnterEnabled,
                    @Nullable Rect sourceRectHint) {
    // ... version checks and null checks ...

    PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder();
    
    if (aspectRatio != null) {
        builder.setAspectRatio(aspectRatio);
    }
    
    if (sourceRectHint != null) {
        builder.setSourceRectHint(sourceRectHint);
    }
    
    // Disable seamless resize for non-video content
    builder.setSeamlessResizeEnabled(false);
    
    activity.setPictureInPictureParams(builder.build());
}

Flutter 集成限制和解决方案

1. 自动进入画中画模式限制

Flutter 不正确地委托 Android 生命周期事件,如 onPauseonPiPModeChanged。这给实现自动进入画中画模式带来了挑战。

限制:

java 复制代码
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S)
public boolean isAutoEnterSupported() {
    // We could support this on Android 12+, but Flutter limitations prevent it
    return false;
}

解决方案:

我们不依赖自动进入画中画模式,而是提供显式的方法来进入和退出画中画模式,这些方法可以从 Flutter 代码中调用:

java 复制代码
public boolean start() {
    if (!isSupported() || isActived() || !isPipEnabled()) {
        return false;
    }

    Activity activity = mActivity.get();
    if (activity == null) {
        return false;
    }

    activity.enterPictureInPictureMode(mParamsBuilder.build());
    return true;
}

2. 画中画状态变化检测

由于 Flutter 不提供画中画状态变化事件,我们实现了一个轮询机制来检测状态变化。

解决方案:

java 复制代码
private void startStateMonitoring() {
    // Poll every 100ms to check PiP state
    mCheckStateTask = new Runnable() {
        @Override
        public void run() {
            checkPipState();
            mHandler.postDelayed(this, CHECK_INTERVAL_MS);
        }
    };
    mHandler.post(mCheckStateTask);
}

3. 画中画退出处理

Android 不提供直接退出画中画模式的方法。

解决方案:

java 复制代码
public void stop() {
    if (!isSupported() || !isActived()) {
        return;
    }

    Activity activity = mActivity.get();
    if (activity == null) {
        return;
    }

    // Move the activity to background instead of truly stopping PiP
    activity.moveTaskToBack(false);
}

最佳实践

  1. 资源管理: 始终正确释放资源:
java 复制代码
public void dispose() {
    stopStateMonitoring();
    mPipParams = null;
    mParamsBuilder = null;
    mHandler = null;
    mLastPipState = false;
    mCheckStateTask = null;
}
  1. 状态监控: 跟踪画中画状态变化并通知 Flutter:
java 复制代码
private void checkPipState() {
    boolean currentState = isActived();
    if (currentState != mLastPipState) {
        mLastPipState = currentState;
        notifyPipStateChanged(currentState ? PipState.Started : PipState.Stopped);
    }
}
  1. 交叉淡入淡出动画: 对于非视频内容,禁用无缝调整大小:
java 复制代码
mParamsBuilder.setSeamlessResizeEnabled(false);

结论

虽然在 Flutter Android 应用中实现画中画功能受到 Flutter 处理 Android 生命周期事件的一些限制,但我们可以通过轮询状态检测和显式控制方法来解决这些问题。这里提供的解决方案提供了一个可靠且稳定的实现,同时保持良好的用户体验。

请记住要在不同的 Android 版本和设备配置上进行全面测试,因为画中画行为在不同的 Android 实现中可能会有所不同。

参考

PS

这个项目会持续维护下去,而且已经在准备发布pub.dev, 目前上面的文档是AI帮助生成的,有些不太准确和完善,但基本路线是对的,后续会持续补充完善。

相关推荐
没有了遇见1 小时前
DrawerLayout 滑动冲突
android
玲小珑2 小时前
Auto.js 入门指南(六)多线程与异步操作
android·前端
用户2018792831674 小时前
通俗易懂理解Java注解
android
用户2018792831674 小时前
通俗易懂理解泛型
android
linversion4 小时前
如何手动上传Android混淆映射文件Mapping.txt到Firebase
android
慕晨4 小时前
RecyclerView + SnapHelper 滚动差异问题
android
玲小珑4 小时前
Auto.js 入门指南(三)第一个 Auto.js 脚本
android·前端
aningxiaoxixi5 小时前
Android Studio 之基础代码解析
android·ide·android studio
A-花开堪折5 小时前
Android7 Input(十)View 处理Input事件pipeline
android·嵌入式硬件
Shujie_L6 小时前
Android基础回顾】六:安卓显示机制Surface 、 SurfaceFlinger、Choreographer
android