Flutter富文本编辑插件quill_native_bridge_syncme的使用

Flutter富文本编辑插件quill_native_bridge_syncme的使用

🪶 Quill Native Bridge

quill_native_bridge_syncme 是一个内部插件,用于访问平台特定的API,主要用于 flutter_quill 包。

注意事项

  • 仅限内部使用:仅限于 flutter_quill 使用。可能会有破坏性更改。

平台支持特性

功能 iOS Android macOS Windows Linux Web
isIOSSimulator
getClipboardHtml
copyHtmlToClipboard
copyImageToClipboard
getClipboardImage
getClipboardGif
getClipboardFiles

🔧 平台配置

为了在Android上支持将图像复制到系统剪贴板,需要进行平台配置设置。如果没有设置,在调试模式下会出现在日志中警告,而在生产模式下则会抛出异常。

重要提示

此配置仅在Android平台上使用 copyImageToClipboard 时需要。如果该方法未被使用,则其他功能在Android上仍然可以正常工作。更多信息,请参阅Android FileProvider文档

1. 更新 AndroidManifest.xml

打开 your_project/android/app/src/main/AndroidManifest.xml 并在 <application> 标签内添加以下内容:

<manifest>
    <application>
        ...
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true" >
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>
        ...
    </application>
</manifest>

2. 创建 file_paths.xml

创建文件 your_project/android/app/src/main/res/xml/file_paths.xml,内容如下:

<paths>
    <cache-path name="cache" path="." />
</paths>

🚧 实验性

尽管该包的版本较高,但由于它之前与 flutter_quill 的版本相同,现在已分离。flutter_quill 发布工作流会为所有包发布一个稳定版本,即使没有引入任何更改。

修复版本需要取消对 quill_native_bridge 的支持并发布新包。

完整示例代码

以下是完整的示例代码,展示了如何使用 quill_native_bridge_syncme 插件的功能。

import 'package:flutter/material.dart';
import 'package:quill_native_bridge/quill_native_bridge_syncme.dart' 
    show QuillNativeBridge, QuillNativeBridgeFeature;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Quill Native Bridge'),
        ),
        body: const Center(
          child: Buttons(),
        ),
      ),
    );
  }
}

class Buttons extends StatelessWidget {
  const Buttons({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        ElevatedButton.icon(
          onPressed: () => _onButtonClick(
            QuillNativeBridgeFeature.isIOSSimulator,
            context: context,
          ),
          label: const Text('Is iOS Simulator'),
          icon: const Icon(Icons.apple),
        ),
        ElevatedButton.icon(
          onPressed: () => _onButtonClick(
            QuillNativeBridgeFeature.getClipboardHtml,
            context: context,
          ),
          label: const Text('Get HTML from Clipboard'),
          icon: const Icon(Icons.html),
        ),
        ElevatedButton.icon(
          onPressed: () => _onButtonClick(
            QuillNativeBridgeFeature.copyHtmlToClipboard,
            context: context,
          ),
          label: const Text('Copy HTML to Clipboard'),
          icon: const Icon(Icons.copy),
        ),
        ElevatedButton.icon(
          onPressed: () => _onButtonClick(
            QuillNativeBridgeFeature.copyImageToClipboard,
            context: context,
          ),
          label: const Text('Copy Image to Clipboard'),
          icon: const Icon(Icons.copy),
        ),
        ElevatedButton.icon(
          onPressed: () => _onButtonClick(
            QuillNativeBridgeFeature.getClipboardImage,
            context: context,
          ),
          label: const Text('Retrieve Image from Clipboard'),
          icon: const Icon(Icons.image),
        ),
        ElevatedButton.icon(
          onPressed: () => _onButtonClick(
            QuillNativeBridgeFeature.getClipboardGif,
            context: context,
          ),
          label: const Text('Retrieve Gif from Clipboard'),
          icon: const Icon(Icons.gif),
        ),
        ElevatedButton.icon(
          onPressed: () => _onButtonClick(
            QuillNativeBridgeFeature.getClipboardFiles,
            context: context,
          ),
          label: const Text('Retrieve Files from Clipboard'),
          icon: const Icon(Icons.file_open),
        ),
      ],
    );
  }

  Future<void> _onButtonClick(
    QuillNativeBridgeFeature feature, {
    required BuildContext context,
  }) async {
    final scaffoldMessenger = ScaffoldMessenger.of(context);

    final isFeatureUnsupported = !(await QuillNativeBridge.isSupported(feature));
    final isFeatureWebUnsupported = isFeatureUnsupported && kIsWeb;
    switch (feature) {
      case QuillNativeBridgeFeature.isIOSSimulator:
        if (isFeatureUnsupported) {
          scaffoldMessenger.showText(
            isFeatureWebUnsupported
                ? "无法在网页上检查设备是否为iOS模拟器。"
                : '必须在iOS设备上才能检查是否为模拟器。',
          );
          return;
        }
        final result = await QuillNativeBridge.isIOSSimulator();
        scaffoldMessenger.showText(result
            ? "你正在运行iOS模拟器的应用。"
            : "你正在运行真实的iOS设备上的应用。");
        break;
      case QuillNativeBridgeFeature.getClipboardHtml:
        if (isFeatureUnsupported) {
          scaffoldMessenger.showText(
            isFeatureWebUnsupported
                ? '在网页上无法从剪贴板检索HTML。'
                : '在${defaultTargetPlatform.name}上无法从剪贴板获取HTML。',
          );
          return;
        }
        final result = await QuillNativeBridge.getClipboardHtml();
        if (result == null) {
          scaffoldMessenger.showText(
            '剪贴板上没有可用的HTML。',
          );
          return;
        }
        scaffoldMessenger.showText(
          '剪贴板上的HTML: $result',
        );
        debugPrint('剪贴板上的HTML: $result');
        break;
      case QuillNativeBridgeFeature.copyHtmlToClipboard:
        if (isFeatureUnsupported) {
          scaffoldMessenger.showText(
            isFeatureWebUnsupported
                ? '在网页上无法将HTML复制到剪贴板。'
                : '在${defaultTargetPlatform.name}上无法将HTML复制到剪贴板。',
          );
          return;
        }
        const html = '''
          <strong>Bold text</strong>
          <em>Italic text</em>
          <u>Underlined text</u>
          <span style="color:red;">Red text</span>
          <span style="background-color:yellow;">Highlighted text</span>
        ''';
        await QuillNativeBridge.copyHtmlToClipboard(html);
        scaffoldMessenger.showText(
          'HTML已复制到剪贴板: $html',
        );
        break;
      case QuillNativeBridgeFeature.copyImageToClipboard:
        if (isFeatureUnsupported) {
          scaffoldMessenger.showText(
            isFeatureWebUnsupported
                ? '在网页上无法将图像复制到剪贴板。'
                : '在${defaultTargetPlatform.name}上无法将图像复制到剪贴板。',
          );
          return;
        }
        final imageBytes = await loadAssetFile('path_to_your_image'); // 替换为实际路径
        await QuillNativeBridge.copyImageToClipboard(imageBytes);

        // 不广泛支持,但一些应用程序会将图像作为文本复制:
        // final file = File(
        //   '${Directory.systemTemp.path}/clipboard-image.png',
        // );
        // await file.create(recursive: true);
        // await file.writeAsBytes(imageBytes);
        // Clipboard.setData(
        //   ClipboardData(
        //     // 目前Android插件不支持content://
        //     text: 'file://${file.absolute.path}',
        //   ),
        // );

        scaffoldMessenger.showText(
          '图像已复制到剪贴板。',
        );
        break;
      case QuillNativeBridgeFeature.getClipboardImage:
        if (isFeatureUnsupported) {
          scaffoldMessenger.showText(
            isFeatureWebUnsupported
                ? '在网页上无法从剪贴板检索图像。'
                : '在${defaultTargetPlatform.name}上无法从剪贴板检索图像。',
          );
          return;
        }
        final imageBytes = await QuillNativeBridge.getClipboardImage();
        if (imageBytes == null) {
          scaffoldMessenger.showText(
            '剪贴板上没有可用的图像。',
          );
          return;
        }
        if (!context.mounted) {
          return;
        }
        showDialog(
          context: context,
          builder: (context) => Dialog(
            child: Image.memory(imageBytes),
          ),
        );
        break;
      case QuillNativeBridgeFeature.getClipboardGif:
        if (isFeatureUnsupported) {
          scaffoldMessenger.showText(
            isFeatureWebUnsupported
                ? '在网页上无法从剪贴板检索GIF。'
                : '在${defaultTargetPlatform.name}上无法从剪贴板检索GIF。',
          );
          return;
        }
        final gifBytes = await QuillNativeBridge.getClipboardGif();
        if (gifBytes == null) {
          scaffoldMessenger.showText(
            '剪贴板上没有可用的GIF。',
          );
          return;
        }
        if (!context.mounted) {
          return;
        }
        showDialog(
          context: context,
          builder: (context) => Dialog(
            child: Image.memory(gifBytes),
          ),
        );
        break;
      case QuillNativeBridgeFeature.getClipboardFiles:
        if (isFeatureUnsupported) {
          scaffoldMessenger.showText(
            isFeatureWebUnsupported
                ? '在网页上无法从剪贴板检索文件。'
                : '在${defaultTargetPlatform.name}上无法从剪贴板检索文件。',
          );
          return;
        }
        final files = await QuillNativeBridge.getClipboardFiles();
        if (files.isEmpty) {
          scaffoldMessenger.showText('剪贴板上没有文件。');
          return;
        }
        scaffoldMessenger.showText(
          '${files.length} 文件来自剪贴板: ${files.toString()}',
        );
        debugPrint('文件来自剪贴板: $files');
        break;
    }
  }
}

extension ScaffoldMessengerX on ScaffoldMessengerState {
  void showText(String text) {
    clearSnackBars();
    showSnackBar(
      SnackBar(
        content: Text(text),
      ),
    );
  }
}

更多关于Flutter富文本编辑插件quill_native_bridge_syncme的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter富文本编辑插件quill_native_bridge_syncme的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter应用中使用quill_native_bridge_syncme插件来实现富文本编辑的代码案例。这个插件允许你使用Quill编辑器与原生平台(如iOS和Android)进行同步。

首先,确保你已经在pubspec.yaml文件中添加了quill_native_bridge_syncme依赖:

dependencies:
  flutter:
    sdk: flutter
  quill_flutter: ^3.0.0  # 确保版本与quill_native_bridge_syncme兼容
  quill_native_bridge_syncme: ^latest_version  # 替换为最新版本号

然后,运行flutter pub get来获取依赖。

接下来,你可以按照以下步骤在你的Flutter应用中使用这个插件:

  1. 设置Quill编辑器

在你的Flutter应用中,创建一个使用Quill编辑器的页面。以下是一个简单的例子:

import 'package:flutter/material.dart';
import 'package:quill_flutter/quill_flutter.dart';
import 'package:quill_native_bridge_syncme/quill_native_bridge_syncme.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: QuillEditorPage(),
    );
  }
}

class QuillEditorPage extends StatefulWidget {
  @override
  _QuillEditorPageState createState() => _QuillEditorPageState();
}

class _QuillEditorPageState extends State<QuillEditorPage> {
  late QuillController _quillController;
  late QuillNativeBridgeSyncme _quillNativeBridge;

  @override
  void initState() {
    super.initState();
    // 初始化QuillController
    _quillController = QuillController(
      document: Document(),
      selection: TextSelection.collapsed(offset: 0),
    );

    // 初始化QuillNativeBridgeSyncme
    _quillNativeBridge = QuillNativeBridgeSyncme(
      quillController: _quillController,
      onNativeTextChanged: (Document newDoc) {
        // 当原生文本改变时,更新QuillController的文档
        setState(() {
          _quillController.replaceText(0, _quillController.length, newDoc.toPlainText(), insert: false);
          _quillController.document = newDoc;
        });
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Quill Editor with Native Bridge'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: QuillEditor.basic(
          controller: _quillController,
          readOnly: false,
          focusNode: FocusNode(),
          padding: EdgeInsets.zero,
          expand: true,
          autoFocus: false,
          scrollController: ScrollController(),
          selectionColor: Colors.deepOrangeAccent,
          cursorColor: Colors.black,
        ),
      ),
    );
  }

  @override
  void dispose() {
    _quillController.dispose();
    _quillNativeBridge.dispose();
    super.dispose();
  }
}

注意:上面的代码示例仅展示了如何在Flutter端设置Quill编辑器,并通过QuillNativeBridgeSyncme类进行初始化。实际的原生同步逻辑(例如在iOS和Android端处理文本变化并发送回Flutter端)需要你在原生代码中进行实现。

  1. 在原生代码中实现同步逻辑

由于quill_native_bridge_syncme插件可能涉及到原生代码的实现,你需要分别在iOS和Android项目中添加相应的代码来处理文本变化,并通过插件的接口将这些变化发送回Flutter端。这通常涉及到设置监听器、处理文本变化事件,并通过插件提供的接口将这些事件发送回Flutter。

由于原生代码的实现细节可能因插件版本和具体需求而异,这里无法给出具体的原生代码示例。但你可以参考插件的官方文档或源代码来了解如何在原生端实现这些功能。

  1. 运行和测试

在完成了Flutter和原生端的代码实现后,你可以运行你的Flutter应用,并在原生编辑器中进行文本编辑。这些变化应该能够通过quill_native_bridge_syncme插件同步回Flutter端的Quill编辑器中。

请确保在实际项目中仔细测试同步功能,以确保它在各种情况下都能正常工作。

回到顶部