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帮助生成的,有些不太准确和完善,但基本路线是对的,后续会持续补充完善。

相关推荐
一人前行5 小时前
Flutter_学习记录_安装第三方包(演示安装 Intl 包)
flutter
_平凡之路_5 小时前
实现限制同一个账号最多只能在3个客户端(有电脑、手机等)登录(附关键源码)
android·java·vue.js·spring·servlet
solarsaber5 小时前
Flutter开发环境配置
flutter
guodashen0077 小时前
安卓使用JExcelApi读取Excel文件
android·excel·jexcelapi
crazymaple2138 小时前
Flutter编译问题记录
flutter
天若子8 小时前
flutter ListView 局部刷新
windows·flutter
if就8 小时前
Java_多线程
android·java·开发语言
ClaNNEd@9 小时前
Flutter项目试水
flutter
键盘被我撸冒烟了9 小时前
Android Knowledge
android