Flutter自定义HTML编辑器插件custom_quill_html_editor的使用

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

Flutter 自定义 HTML 编辑器插件 custom_quill_html_editor 的使用

Quill Html Editor 是一个为 Android、iOS 和 Web 平台设计的强大 HTML 富文本编辑器。它利用了 QuillJs 库的功能,提供了一个功能丰富的现代 Web 应用编辑体验。

特性

  • 高度可定制化的 编辑器工具栏 小部件。
  • 支持 Delta 格式,允许你使用 setDeltagetDelta 方法设置和检索内容。
  • 支持无缝复制粘贴富文本。
  • 可分离的工具栏可以放置在页面的任何位置。
  • 提供了添加自定义按钮到工具栏的灵活性。
  • 支持插入图像、视频和表格。
  • 允许以 HTML 和 Delta 格式设置和检索文本。
  • 支持集成 Google 字体以获得广泛的字体选项。

演示

要体验 Quill Html Editor 的功能,你可以访问我们的 演示页面。探索编辑器的各种功能,并查看如何增强你的 Web 编辑体验。

截图

截图

截图

文档

详情请参阅以下文档:

使用方法

首先,定义一个 QuillEditorController 以访问编辑器的方法,并将其传递给 QuillHtmlEditor 组件。

final QuillEditorController controller = QuillEditorController();
QuillHtmlEditor(
  text: "<h1>Hello</h1>This is a quill html editor example 😊",
  hintText: 'Hint text goes here',
  controller: controller,
  isEnabled: true,
  minHeight: 300,
  textStyle: _editorTextStyle,
  hintTextStyle: _hintTextStyle,
  hintTextAlign: TextAlign.start,
  padding: const EdgeInsets.only(left: 10, top: 5),
  hintTextPadding: EdgeInsets.zero,
  backgroundColor: _backgroundColor,
  onFocusChanged: (hasFocus) => debugPrint('has focus $hasFocus'),
  onTextChanged: (text) => debugPrint('widget text change $text'),
  onEditorCreated: () => debugPrint('Editor has been loaded'),
  onEditingComplete: (s) => debugPrint('Editing completed $s'),
  onEditorResized: (height) =>
      debugPrint('Editor resized $height'),
  onSelectionChanged: (sel) =>
      debugPrint('${sel.index},${sel.length}'),
  loadingBuilder: (context) {
      return const Center(
      child: CircularProgressIndicator(
      strokeWidth: 0.4,
      ));
  },
)

接下来,定义一个 ToolBar 小部件并传递相同的 controller

ToolBar(
  toolBarColor: Colors.cyan.shade50,
  activeIconColor: Colors.green,
  padding: const EdgeInsets.all(8),
  iconSize: 20,
  controller: controller,
  customButtons: [
    InkWell(onTap: () {}, child: const Icon(Icons.favorite)),
    InkWell(onTap: () {}, child: const Icon(Icons.add_circle)),
  ],
)

工具栏滚动配置

ToolBar.scroll 小部件允许你将工具栏按钮显示在单行或单列中,这取决于指定的 direction 参数。默认情况下,direction 设置为 Axis.horizontal,这将按钮排列在一行中。

要更改方向并将按钮显示为单列,可以提供 direction 参数为 Axis.vertical。例如:

ToolBar.scroll(
  toolBarColor: _toolbarColor,
  controller: controller,
  direction: Axis.vertical,
),

在这个例子中,ToolBar.scroll 小部件用于垂直排列工具栏按钮。

自定义工具栏按钮

ToolBar 小部件允许你自定义工具栏中显示的按钮。如果你不提供 toolBarConfig 参数,所有工具栏按钮都会显示。

如果你只想显示特定的按钮,可以通过向 toolBarConfig 参数传递一个 ToolBarStyle 类型的列表来实现。例如:

final customToolBarList = [
  ToolBarStyle.bold,
  ToolBarStyle.italic,
  ToolBarStyle.align,
  ToolBarStyle.color,
];

ToolBar(
  controller: controller,
  toolBarConfig: customToolBarList,
),

在这个例子中,只显示“加粗”、“斜体”、“对齐”和“颜色”按钮。

你还可以通过向 customButtons 参数提供一个包含自定义小部件的列表来添加自定义按钮。例如:

final customButtons = [
  InkWell(onTap: () {}, child: const Icon(Icons.favorite)),
  InkWell(onTap: () {}, child: const Icon(Icons.add_circle)),
];

ToolBar(
  controller: controller,
  customButtons: customButtons,
),

在这个例子中,添加了带有心形和圆形加号图标的心形和圆形加号按钮。

自定义字体

你可以通过以下步骤将自定义字体传递给文本样式:

在你的 Flutter 项目中定义自定义字体家族。你可以使用 Google Fonts。假设你想使用 ‘Roboto’ 字体家族。

final _editorTextStyle = const TextStyle(
  fontSize: 18,
  color: Colors.black,
  fontWeight: FontWeight.normal,
  fontFamily: 'Roboto',
);

通过这些步骤,你可以将自定义字体样式传递给 QuillHtmlEditor 小部件中的文本。fontFamily 属性允许你指定所需的字体家族,如本例中的 ‘Roboto’。

获取和设置 HTML 字符串

获取编辑器中的 HTML 字符串:

String? htmlText = await controller.getText();

设置编辑器中的 HTML 字符串:

await controller.setText(text);

获取 Delta 格式的文本:

await controller.getDelta();

设置 Delta 格式的文本:

controller.setDelta(deltaMap);

将 HTML 字符串插入到编辑器中:

await controller.insertText(text, index: 10);

清除编辑器:

controller.clear();

启用编辑器:

controller.enableEditor(true);

禁用编辑器:

controller.enableEditor(false);

示例代码

import 'dart:convert';

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

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const MaterialApp(debugShowCheckedModeBanner: false, home: MyApp()));
}

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

  [@override](/user/override)
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late QuillEditorController controller;

  final customToolBarList = [
    ToolBarStyle.bold,
    ToolBarStyle.italic,
    ToolBarStyle.align,
    ToolBarStyle.color,
    ToolBarStyle.background,
    ToolBarStyle.listBullet,
    ToolBarStyle.listOrdered,
    ToolBarStyle.clean,
    ToolBarStyle.addTable,
    ToolBarStyle.editTable,
  ];

  final _toolbarColor = Colors.grey.shade200;
  final _backgroundColor = Colors.white70;
  final _toolbarIconColor = Colors.black87;
  final _editorTextStyle = const TextStyle(
      fontSize: 18,
      color: Colors.black,
      fontWeight: FontWeight.normal,
      fontFamily: 'Roboto');
  final _hintTextStyle = const TextStyle(
      fontSize: 18, color: Colors.black38, fontWeight: FontWeight.normal);

  bool _hasFocus = false;

  [@override](/user/override)
  void initState() {
    controller = QuillEditorController();
    controller.onTextChanged((text) {
      debugPrint('listening to $text');
    });
    controller.onEditorLoaded(() {
      debugPrint('Editor Loaded :)');
    });
    super.initState();
  }

  [@override](/user/override)
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        backgroundColor: Colors.white,
        resizeToAvoidBottomInset: true,
        body: Column(
          children: [
            ToolBar(
              toolBarColor: _toolbarColor,
              padding: const EdgeInsets.all(8),
              iconSize: 25,
              iconColor: _toolbarIconColor,
              activeIconColor: Colors.greenAccent.shade400,
              controller: controller,
              crossAxisAlignment: WrapCrossAlignment.start,
              direction: Axis.horizontal,
              customButtons: [
                Container(
                  width: 25,
                  height: 25,
                  decoration: BoxDecoration(
                      color: _hasFocus ? Colors.green : Colors.grey,
                      borderRadius: BorderRadius.circular(15)),
                ),
                InkWell(
                    onTap: () => unFocusEditor(),
                    child: const Icon(
                      Icons.favorite,
                      color: Colors.black,
                    )),
                InkWell(
                    onTap: () async {
                      var selectedText = await controller.getSelectedText();
                      debugPrint('selectedText $selectedText');
                      var selectedHtmlText =
                          await controller.getSelectedHtmlText();
                      debugPrint('selectedHtmlText $selectedHtmlText');
                    },
                    child: const Icon(
                      Icons.add_circle,
                      color: Colors.black,
                    )),
              ],
            ),
            Expanded(
              child: QuillHtmlEditor(
                text: "<h1>Hello</h1>This is a quill html editor example 😊",
                hintText: 'Hint text goes here',
                controller: controller,
                isEnabled: true,
                ensureVisible: false,
                minHeight: 500,
                autoFocus: false,
                textStyle: _editorTextStyle,
                hintTextStyle: _hintTextStyle,
                hintTextAlign: TextAlign.start,
                padding: const EdgeInsets.only(left: 10, top: 10),
                hintTextPadding: const EdgeInsets.only(left: 20),
                backgroundColor: _backgroundColor,
                inputAction: InputAction.newline,
                onEditingComplete: (s) => debugPrint('Editing completed $s'),
                loadingBuilder: (context) {
                  return const Center(
                      child: CircularProgressIndicator(
                    strokeWidth: 1,
                    color: Colors.red,
                  ));
                },
                onFocusChanged: (focus) {
                  debugPrint('has focus $focus');
                  setState(() {
                    _hasFocus = focus;
                  });
                },
                onTextChanged: (text) => debugPrint('widget text change $text'),
                onEditorCreated: () {
                  debugPrint('Editor has been loaded');
                  setHtmlText('Testing text on load');
                },
                onEditorResized: (height) =>
                    debugPrint('Editor resized $height'),
                onSelectionChanged: (sel) =>
                    debugPrint('index ${sel.index}, range ${sel.length}'),
              ),
            ),
          ],
        ),
        bottomNavigationBar: Container(
          width: double.maxFinite,
          color: _toolbarColor,
          padding: const EdgeInsets.all(8),
          child: Wrap(
            children: [
              textButton(
                  text: 'Set Text',
                  onPressed: () {
                    setHtmlText('This text is set by you 🫵');
                  }),
              textButton(
                  text: 'Get Text',
                  onPressed: () {
                    getHtmlText();
                  }),
              textButton(
                  text: 'Insert Video',
                  onPressed: () {
                    insertVideoURL(
                        'https://www.youtube.com/watch?v=4AoFA19gbLo');
                    insertVideoURL('https://vimeo.com/440421754');
                    insertVideoURL(
                        'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4');
                  }),
              textButton(
                  text: 'Insert Image',
                  onPressed: () {
                    insertNetworkImage('https://i.imgur.com/0DVAOec.gif');
                  }),
              textButton(
                  text: 'Insert Index',
                  onPressed: () {
                    insertHtmlText("This text is set by the insertText method",
                        index: 10);
                  }),
              textButton(
                  text: 'Undo',
                  onPressed: () {
                    controller.undo();
                  }),
              textButton(
                  text: 'Redo',
                  onPressed: () {
                    controller.redo();
                  }),
              textButton(
                  text: 'Clear History',
                  onPressed: () async {
                    controller.clearHistory();
                  }),
              textButton(
                  text: 'Clear Editor',
                  onPressed: () {
                    controller.clear();
                  }),
              textButton(
                  text: 'Get Delta',
                  onPressed: () async {
                    var delta = await controller.getDelta();
                    debugPrint('delta');
                    debugPrint(jsonEncode(delta));
                  }),
              textButton(
                  text: 'Set Delta',
                  onPressed: () {
                    final Map<dynamic, dynamic> deltaMap = {
                      "ops": [
                        {
                          "insert": {
                            "video":
                                "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
                          }
                        },
                        {
                          "insert": {
                            "video": "https://www.youtube.com/embed/4AoFA19gbLo"
                          }
                        },
                        {"insert": "Hello"},
                        {
                          "attributes": {"header": 1},
                          "insert": "\n"
                        },
                        {"insert": "You just set the Delta text 😊\n"}
                      ]
                    };
                    controller.setDelta(deltaMap);
                  }),
            ],
          ),
        ),
      ),
    );
  }

  Widget textButton({required String text, required VoidCallback onPressed}) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: MaterialButton(
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
          color: _toolbarIconColor,
          onPressed: onPressed,
          child: Text(
            text,
            style: TextStyle(color: _toolbarColor),
          )),
    );
  }

  void getHtmlText() async {
    String? htmlText = await controller.getText();
    debugPrint(htmlText);
  }

  void setHtmlText(String text) async {
    await controller.setText(text);
  }

  void insertNetworkImage(String url) async {
    await controller.embedImage(url);
  }

  void insertVideoURL(String url) async {
    await controller.embedVideo(url);
  }

  void insertHtmlText(String text, {int? index}) async {
    await controller.insertText(text, index: index);
  }

  void clearEditor() => controller.clear();

  void enableEditor(bool enable) => controller.enableEditor(enable);

  void unFocusEditor() => controller.unFocus();
}

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

1 回复

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


当然,以下是如何在Flutter项目中使用custom_quill_html_editor插件的示例代码。这个插件允许你创建一个自定义的HTML编辑器,用户可以在其中输入和编辑富文本内容,然后将其转换为HTML格式。

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

dependencies:
  flutter:
    sdk: flutter
  custom_quill_html_editor: ^最新版本号  # 替换为实际的最新版本号

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

接下来,你可以在你的Flutter项目中使用CustomQuillHtmlEditor组件。以下是一个简单的示例,展示了如何集成和使用这个插件:

import 'package:flutter/material.dart';
import 'package:custom_quill_html_editor/custom_quill_html_editor.dart';
import 'package:flutter_quill/flutter_quill.dart' as quill;
import 'package:flutter_quill/models/documents/document.dart' as quill;

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

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

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

class _MyHomePageState extends State<MyHomePage> {
  quill.Document? _controllerDocument;
  String _htmlContent = '';

  @override
  void initState() {
    super.initState();
    _controllerDocument = quill.Document();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Custom HTML Editor Demo'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Expanded(
              child: CustomQuillHtmlEditor(
                controller: _CustomQuillController(
                  document: _controllerDocument!,
                  onChanged: (quill.Document doc) {
                    setState(() {
                      _controllerDocument = doc;
                      _htmlContent = doc.toDelta().map((e) => e.toHtml()).join();
                    });
                  },
                ),
              ),
            ),
            SizedBox(height: 16),
            Text(
              'HTML Content:',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            SizedBox(height: 8),
            Expanded(
              child: SelectableText(
                _htmlContent,
                style: TextStyle(fontSize: 16),
                maxLines: null,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _CustomQuillController extends quill.QuillController {
  _CustomQuillController({
    required quill.Document document,
    required VoidCallback Function(quill.Document doc) onChanged,
  }) : super(
          document: document,
          selection: quill.TextSelection.collapsed(offset: 0),
          onChanged: (quill.Delta delta, quill.ChangeSource source) {
            if (source != quill.ChangeSource.silent) {
              onChanged(document);
            }
          },
        );
}

在这个示例中:

  1. 我们定义了一个_CustomQuillController类,它扩展了quill.QuillController,并添加了一个自定义的onChanged回调,用于在文档内容更改时更新HTML内容。
  2. MyHomePage中,我们使用CustomQuillHtmlEditor组件来显示编辑器,并绑定到_CustomQuillController
  3. 当用户编辑内容时,_CustomQuillControlleronChanged回调会被触发,我们更新_htmlContent变量,该变量保存了当前的HTML内容。
  4. 我们还使用了一个SelectableText组件来显示生成的HTML内容,以便用户可以看到他们的编辑结果。

请确保你遵循了custom_quill_html_editor插件的文档和更新日志,因为插件的API可能会随着版本更新而变化。如果你遇到任何问题,请查阅最新的官方文档或提交issue到插件的GitHub仓库。

回到顶部