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

相关推荐
Kapaseker3 小时前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴3 小时前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭13 小时前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab14 小时前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读
明君8799718 小时前
Flutter 如何给图片添加多行文字水印
前端·flutter
BoomHe19 小时前
Now in Android 架构模式全面分析
android·android jetpack
四眼肥鱼1 天前
flutter 利用flutter_libserialport 实现SQ800 串口通信
前端·flutter
二流小码农1 天前
鸿蒙开发:上传一张参考图片便可实现页面功能
android·ios·harmonyos
鹏程十八少1 天前
4.Android 30分钟手写一个简单版shadow, 从零理解shadow插件化零反射插件化原理
android·前端·面试
Kapaseker1 天前
一杯美式搞定 Kotlin 空安全
android·kotlin