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

相关推荐
兄弟加油,别颓废了。15 小时前
ctf.show_web3
android
火柴就是我15 小时前
代码记录android怎么实现状态栏导航栏隐藏
android·flutter
梦里花开知多少15 小时前
浅谈ThreadPool
android·面试
weixin_4434785115 小时前
FLUTTER组件学习之进度指示器
学习·flutter
帅次15 小时前
单例初始化中的耗时操作如何拖死主线程
android·webview·android runtime
始持16 小时前
第十九讲 深度布局原理与优化
前端·flutter
人月神话Lee16 小时前
一个iOS开发者对Flutter中Widget、Element和RenderObject的理解
前端·flutter·ios
用户08748819991716 小时前
Android 资源类型全解析及四大常用布局资源深度指南
android
始持16 小时前
第二十讲 权限与设备能力
前端·flutter
火锅鸡的味道16 小时前
解决AOSP工程Android Studio打开卡顿
android·python·android studio