Flutter富文本编辑器插件remodl_rte的使用

发布于 1周前 作者 vueper 来自 Flutter

Flutter富文本编辑器插件remodl_rte的使用

Remodl.ai富文本编辑器

这是一个适用于Flutter的所见即所得(WYSIWYG)HTML编辑器,并内置了语音转文字功能。


背后原理

该插件基于html_editor_enhanced进行了重新开发,主要区别包括:

  • 改进了小部件的高度约束:

    • 自动适应内容高度,
    • 扩展到全高,
    • 显式指定。
  • 使用了Squire代替了Summernote和jQuery,这是一款非常流行且维护良好的HTML5富文本编辑库,提供了对生成HTML的强大灵活性。

  • 通过DOMPurify进行XSS防护,这是一款超快且容错性极高的HTML、MathML和SVG XSS清理工具。

  • 使用Flutter自己的webview_flutter代替了in_app_webview

  • 内置的语音转文字功能由speech_to_text包提供支持(主要针对Web平台)。


基本实现

基本实现无需控制器。为了简化和易于使用,[HtmlEditor]提供了以下顶级属性:

字段 类型 描述
height double 设置显式高度
minHeight double 设置最小高度
expandFullHeight bool 使小部件占用所有可用高度
hint String 编辑器为空时显示提示文本
initialValue String 初始HTML或文本
onChanged String 控制器onChanged回调的快捷方式
isReadOnly bool 锁定编辑器并移除工具栏
enableDictation bool 启用或禁用语音转文字功能
import 'package:remodl_rte/remodl_rte.dart';

// ...

// 1. 定义一个变量来存储父类或其他地方的变化结果
String result = 'Hello world!';

// ...

// 2. 在构建方法中添加HtmlEditor
@override
Widget build(BuildContext context) => 
    HtmlEditor(initialValue: result, onChanged: (s) => result = s ?? '');

高级实现

要充分利用整个API,你需要创建并配置一个[HtmlEditorController]实例。该实例提供了以下选项组的访问权限:

  • 样式选项组(所有CSS、HTML和清理)
  • 工具栏选项组(所有工具栏)
  • 编辑器选项组(所有编辑器)

使用控制器时,可以通过controller.setText()方法设置HtmlEditor的文本。这可以在控制器附加到HtmlEditor之前或之后完成。这对于MVVM/MVC情况很有用,在这些情况下,逻辑在UI构建之前被初始化。

可以通过getter同步访问编辑器的内容:

if (controller.contentIsNotEmpty) {
  Navigator.of(context).pop(controller.content);
}

HTML样式选项

[HtmlEditorController]类的stylingOptions参数定义了生成HTML的外观。你可以选择用于段落的标签以及如何设置标签的样式。

var stylingOptions = HtmlStylingOptions(
  // 可选地添加全局样式,可以通过两种方式设置:
  // 1. 提供CSS字符串给`globalStyleSheet`参数:
  globalStyleSheet: '/* Your CSS string contents of style.css file */',

  // 这里定义用于段落的标签。默认值为`p`,但`div`也是可以接受的。
  blockTag: 'p',

  // 定义块标签的`style`和`class`属性
  blockTagAttributes: HtmlTagAttributes(
    // 这将作为每个标签的内联CSS添加
    inlineStyle: 'text-indent:3.5em; text-align:justify;',
    // 定义每个标签的`class`属性值
    cssClass: 'my-custom-pgf'),

  // 接下来我们可以定义其他标签(如li、ul、ol、a等)的属性:
  li: HtmlTagAttributes(
    inlineStyle: 'margin: .5em 1em .5em .5em',
    cssClass: 'my-custom-li-class'),

  // ... 其他HTML标签定义 ... //

  code: HtmlTagAttributes(
    inlineStyle: 'padding: .5em 1em;', cssClass: 'my-custom-code-class'),

  // 当`sanitizeOnPaste`为`true`时,编辑器会清理所有传入的HTML。
  // !!! 危险 !!! 如果将此标志设置为`false`,则会使您的应用程序容易受到XSS攻击。
  sanitizeOnPaste: true,
);

// 2. 另一种添加全局CSS的方式是调用此异步方法:
await stylingOptions.importCssFromFile('path/to/style.css');

// ...

// 现在创建编辑器并传递样式选项
return HtmlEditor(
  controller: HtmlEditorController(stylingOptions: stylingOptions),
  onChanged: (p0) => (p0) {/* TODO */},
  initialValue: '' /* TODO */,
);

上述代码应该会生成以下HTML:

<p style="text-indent:3.5em; text-align:justify;" class="my-custom-pgf"></p>

尺寸和约束

默认情况下,小部件占据所有可用宽度,并根据其内容的高度调整其高度,但不会小于[HtmlEditor]小部件的minHeight属性值。

// 由于未提供显式高度,因此编辑器将根据内容大小调整高度,但不会小于250px
return HtmlEditor(
  controller: controller,
  // ...
  minHeight: 250, // 应该不小于64px
  // ...
);

// 这里可以监听编辑器高度的变化
ValueListenableBuilder<double>(
  valueListenable: controller.totalHeight,
  builder: (BuildContext context, double value, Widget? child) {
    return Text('Height changed to $value\n'
        'Toolbar height is ${controller.toolbarHeight}\n'
        'Content height is ${controller.contentHeight}\n');
  }
);

// 如果提供了显式`height`,则小部件将精确调整到`height`属性的值。在这种情况下,如果内容高度超过小部件高度,则内容将可滚动。
return HtmlEditor(
  height: 250,
);

// 如果设置了`expandFullHeight`为`true`,则小部件将占用所有可用高度。
return HtmlEditor(
  expandFullHeight: true,
);

工具栏位置

所有与工具栏相关的选项都包含在[HtmlEditorController]类的[ToolbarOptions]中。工具栏可以:

  • 上方下方编辑器容器,通过设置toolbarPosition属性;
  • 浮动,即脱离编辑器并位于[HtmlEditor]小部件外部。这种实现允许[ToolbarWidget]附加到多个HtmlEditors。有关这种类型的实现,请参阅包中的示例。

上方编辑器:

下方编辑器:

浮动工具栏:

工具栏类型也可以设置为滚动网格可扩展


工具栏内容和自定义按钮组

可以通过[HtmlToolbarOptions]类的defaultToolbarButtons属性启用/禁用工具栏按钮组。你可以通过覆盖此属性的默认值来自定义工具栏。

要向工具栏添加自己的按钮组,需要向customButtonGroups属性提供一个[CustomButtonGroup]对象列表。每个按钮组由一组[CustomToolbarButton]对象组成,每个对象都有自己的图标、点击回调和isSelected标志,以告知工具栏是否应突出显示该图标按钮。

HtmlEditor(
  controller: HtmlEditorController()
    ..toolbarOptions.customButtonGroups = [
      CustomButtonGroup(
        index: 0, // 放在第一位
        buttons: [
          CustomToolbarButton(
            icon: Icons.save_outlined,
            action: () => /* TODO: 实现你的保存方法 */,
            isSelected: false
          )
        ]
      )
    ],
),

自定义按钮:


语音转文字(Dictation)

语音转文字功能由speech_to_text包提供支持,并且默认情况下已启用。

要禁用语音转文字功能,只需将相应的顶级enableDictation属性设置为false

覆盖controller.toolbarOptions.defaultToolbarButtons值也会覆盖enableDictation标志(显然),因此需要添加const VoiceToTextButtons()以继续显示语音转文字按钮。


特殊注意事项和陷阱

  1. 由于一些框架问题,在Web平台上,此插件仅兼容Flutter 3.3及以上版本。如果你希望在较早版本的Flutter中使用此插件,请确保将项目中的pointer_interceptor依赖项降级为0.9.0+1

  2. 为使每种平台正常工作,需要执行以下操作:

Android

对于语音识别功能,请将以下内容放置到android > app > src > main > AndroidManifest.xml文件中:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.example">

    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.BLUETOOTH" />
    
   <application
    ...

iOS

对于语音识别功能,请在Info.plist文件中添加以下权限:

<key>NSSpeechRecognitionUsageDescription</key>
<string>recognize speech</string>
<key>NSMicrophoneUsageDescription</key>
<string>Need microphone access for uploading videos</string>

Web平台

要在Web上使工具栏水平滚动,需要覆盖默认的滚动行为:

  1. 在你的应用中添加以下类覆盖:
class MyCustomScrollBehavior extends MaterialScrollBehavior {
  @override
  Set<PointerDeviceKind> get dragDevices => {
        PointerDeviceKind.touch,
        PointerDeviceKind.mouse,
      };
}
  1. 在[MaterialApp]小部件中添加以下属性:
return MaterialApp(
    // ...
    scrollBehavior: MyCustomScrollBehavior(),
    // ...
);

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

1 回复

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


当然,以下是如何在Flutter项目中使用remodl_rte富文本编辑器插件的一个基本示例。remodl_rte是一个功能强大的Flutter插件,用于创建富文本编辑器。

首先,你需要在你的pubspec.yaml文件中添加remodl_rte依赖:

dependencies:
  flutter:
    sdk: flutter
  remodl_rte: ^最新版本号  # 请替换为实际可用的最新版本号

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

接下来,在你的Dart文件中(例如main.dart),你可以这样使用remodl_rte

import 'package:flutter/material.dart';
import 'package:remodl_rte/remodl_rte.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Rich Text Editor Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late RTEditorController _controller;

  @override
  void initState() {
    super.initState();
    _controller = RTEditorController(
      initialText: 'Hello, Rich Text Editor!',
      initialTextAlignment: TextAlignment.left,
      initialTextStyle: TextStyle(fontSize: 16, color: Colors.black),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Rich Text Editor Demo'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: RTEditor(
          controller: _controller,
          readOnly: false,
          showPlaceholder: true,
          placeholder: 'Enter your text here...',
          toolbarOptions: [
            RTEToolbarOption.bold,
            RTEToolbarOption.italic,
            RTEToolbarOption.underline,
            RTEToolbarOption.heading1,
            RTEToolbarOption.heading2,
            RTEToolbarOption.paragraph,
            RTEToolbarOption.quote,
            RTEToolbarOption.bulletList,
            RTEToolbarOption.numberedList,
            RTEToolbarOption.link,
            RTEToolbarOption.image,
            // 添加其他你需要的工具栏选项
          ],
          onTextChange: (text) {
            print('Text changed: $text');
          },
          onImagePick: (File? image) async {
            if (image != null) {
              // 处理图片上传或显示逻辑
              // 例如将图片转换为Base64字符串或直接显示
              print('Image picked: ${image.path}');
            }
          },
          onLinkInsert: (String? url) {
            print('Link inserted: $url');
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 获取富文本内容
          final htmlContent = _controller.getHtmlContent();
          // 你可以在这里处理htmlContent,例如保存到服务器或显示在一个WebView中
          print('HTML Content: $htmlContent');
        },
        tooltip: 'Get HTML',
        child: Icon(Icons.copy),
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

代码解释

  1. 添加依赖:在pubspec.yaml文件中添加remodl_rte依赖。
  2. 创建Flutter应用:在main.dart中创建一个基本的Flutter应用。
  3. 初始化RTEditorController:在initState方法中初始化RTEditorController,设置初始文本和样式。
  4. 构建UI:使用RTEditor小部件来创建富文本编辑器,并配置工具栏选项。
  5. 处理文本和图片变化:使用onTextChangeonImagePick回调来处理文本和图片的变化。
  6. 获取HTML内容:在FloatingActionButton的点击事件中获取编辑器生成的HTML内容。

注意事项

  • 确保你使用的remodl_rte版本与Flutter SDK兼容。
  • 根据实际需求调整工具栏选项和回调函数。
  • 你可以扩展这个示例以处理更多复杂的情况,例如将富文本内容保存到服务器或显示在一个WebView中。

希望这个示例能帮助你快速上手remodl_rte富文本编辑器插件!

回到顶部