You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter Android/iOS应用中为系统截图添加水印方案问询

实现Flutter应用系统截图自动加水印方案

核心限制说明

纯Flutter层面无法直接拦截系统截图事件或修改系统生成的截图文件——因为系统截图的触发、文件存储属于系统级权限和事件,Flutter框架本身没有暴露对应的API。完全脱离原生代码的方案目前不存在,但可以通过轻量原生代码配合Platform Channels实现,同时尽量减少原生代码量,保持跨平台逻辑统一。

可行方案:Platform Channels + 原生事件监听 + Flutter图片处理

1. 关键思路

  • 原生端:监听系统截图事件,获取截图文件路径,通过Platform Channels传给Flutter
  • Flutter端:读取截图文件,使用图片处理包添加水印
  • 原生端:将处理后的图片同步到系统相册(或替换原截图文件)

2. 代码实现示例

Flutter端代码

先在pubspec.yaml添加依赖:

dependencies:
  flutter:
    sdk: flutter
  image: ^4.0.17
  path_provider: ^2.1.1

编写核心处理逻辑:

import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:image/image.dart' as img;
import 'package:path_provider/path_provider.dart';

class ScreenshotWatermarkHandler {
  static const MethodChannel _channel = MethodChannel('screenshot_watermark');

  static Future<void> init() async {
    // 监听原生端传来的截图事件
    _channel.setMethodCallHandler((call) async {
      if (call.method == 'onSystemScreenshot') {
        final String screenshotPath = call.arguments;
        await _addWatermark(screenshotPath);
        // 通知原生端同步更新
        await _channel.invokeMethod('syncScreenshot', screenshotPath);
      }
    });
    // 启动原生端监听
    await _channel.invokeMethod('startListening');
  }

  static Future<void> _addWatermark(String imagePath) async {
    final File imageFile = File(imagePath);
    final Uint8List imageBytes = await imageFile.readAsBytes();
    img.Image? originalImage = img.decodeImage(imageBytes);

    if (originalImage == null) return;

    // 添加文本水印(示例:右下角添加应用标识)
    final img.Font font = img.arial8;
    const String watermarkText = 'My Flutter App';
    final int textWidth = img.measureText(font, watermarkText);
    final int textHeight = font.height;

    // 计算水印位置:右下角留10px边距
    final int x = originalImage.width - textWidth - 10;
    final int y = originalImage.height - textHeight - 10;

    // 绘制带阴影的水印,增强可读性
    img.drawString(originalImage, font, x + 1, y + 1, watermarkText, color: img.Color.fromRgba(0, 0, 0, 150));
    img.drawString(originalImage, font, x, y, watermarkText, color: img.Color.fromRgba(255, 255, 255, 200));

    // 覆盖原截图文件
    await imageFile.writeAsBytes(img.encodeJpg(originalImage, quality: 90));
  }
}

// 在应用启动时初始化
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await ScreenshotWatermarkHandler.init();
  runApp(const MyApp());
}

Android端原生代码(Kotlin)

在MainActivity.kt中添加监听与通道逻辑:

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.os.Environment
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity : FlutterActivity() {
    private val CHANNEL = "screenshot_watermark"
    private lateinit var channel: MethodChannel
    private var screenshotReceiver: BroadcastReceiver? = null

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
        channel.setMethodCallHandler { call, result ->
            when (call.method) {
                "startListening" -> {
                    registerScreenshotReceiver()
                    result.success(null)
                }
                "syncScreenshot" -> {
                    val path = call.arguments as String
                    // 通知媒体扫描更新截图
                    sendBroadcast(Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(File(path))))
                    result.success(null)
                }
                else -> result.notImplemented()
            }
        }
    }

    private fun registerScreenshotReceiver() {
        val filter = IntentFilter().apply {
            addAction("android.intent.action.SCREENSHOT")
            // 适配主流厂商自定义广播
            addAction("com.android.systemui.screenshot")
            addAction("com.huawei.systemui.screenshot")
            addAction("com.miui.screenshot")
        }

        screenshotReceiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context?, intent: Intent?) {
                // 通用截图路径,部分厂商可能需要调整
                val screenshotPath = "${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)}/Screenshots/Screenshot_${System.currentTimeMillis()}.png"
                channel.invokeMethod("onSystemScreenshot", screenshotPath)
            }
        }
        registerReceiver(screenshotReceiver, filter)
    }

    override fun onDestroy() {
        super.onDestroy()
        screenshotReceiver?.let { unregisterReceiver(it) }
    }
}

iOS端原生代码(Swift)

在AppDelegate.swift中添加监听逻辑:

import UIKit
import Flutter
import Photos

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    private let CHANNEL = "screenshot_watermark"
    private var screenshotObserver: NSObjectProtocol?

    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        let controller = window?.rootViewController as! FlutterViewController
        let channel = FlutterMethodChannel(name: CHANNEL, binaryMessenger: controller.binaryMessenger)
        
        channel.setMethodCallHandler { [weak self] call, result in
            switch call.method {
            case "startListening":
                self?.setupScreenshotObserver(channel: channel)
                result.success(nil)
            case "syncScreenshot":
                if let path = call.arguments as? String {
                    let url = URL(fileURLWithPath: path)
                    // 更新相册
                    PHPhotoLibrary.shared().performChanges({
                        PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: url)
                    }) { success, _ in
                        if success {
                            try? FileManager.default.removeItem(at: url)
                        }
                    }
                }
                result.success(nil)
            default:
                result.notImplemented()
            }
        }

        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
    
    private func setupScreenshotObserver(channel: FlutterMethodChannel) {
        screenshotObserver = NotificationCenter.default.addObserver(
            forName: UIApplication.userDidTakeScreenshotNotification,
            object: nil,
            queue: .main
        ) { _ in
            if let latestScreenshotPath = self.getLatestScreenshotPath() {
                channel.invokeMethod("onSystemScreenshot", arguments: latestScreenshotPath)
            }
        }
    }
    
    private func getLatestScreenshotPath() -> String? {
        // 请求相册权限
        if PHPhotoLibrary.authorizationStatus() == .notDetermined {
            PHPhotoLibrary.requestAuthorization { _ in }
        }
        
        let options = PHFetchOptions()
        options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
        options.fetchLimit = 1
        
        guard let asset = PHAsset.fetchAssets(with: .image, options: options).firstObject else {
            return nil
        }
        
        var filePath: String?
        PHImageManager.default().requestImageDataAndOrientation(for: asset, options: nil) { _, _, _, info in
            filePath = info?["PHImageFileURLKey"] as? String
        }
        return filePath
    }
    
    deinit {
        if let observer = screenshotObserver {
            NotificationCenter.default.removeObserver(observer)
        }
    }
}

3. 关键注意事项

  • 权限配置:Android需在AndroidManifest.xml添加READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE权限;iOS需在Info.plist中添加NSPhotoLibraryUsageDescription和NSPhotoLibraryAddUsageDescription权限描述
  • 厂商适配:Android不同品牌的系统截图广播、存储路径存在差异,需额外测试适配
  • 性能优化:图片处理逻辑放在异步线程,避免阻塞UI
  • iOS限制:iOS无法直接覆盖系统截图文件,只能将处理后的图片添加到相册,原截图可选择删除

无原生代码的折中方案

如果完全不想编写原生代码,可以在应用界面上叠加固定半透明水印层——用户截图时水印会被一同截取。这种方式实现简单,但水印会持续显示在界面上:

Stack(
  children: [
    // 你的主界面内容
    const HomeScreen(),
    // 右下角水印
    Positioned(
      bottom: 16,
      right: 16,
      child: Text(
        "My Flutter App",
        style: TextStyle(
          color: Colors.white.withOpacity(0.7),
          fontSize: 14,
          shadows: [Shadow(color: Colors.black.withOpacity(0.5), offset: const Offset(1, 1))]
        ),
      ),
    ),
  ],
)

内容的提问来源于stack exchange,提问作者Abir Ahsan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 05:33:24