Flutter富文本编辑器插件html_editor_plus的使用
Flutter Html Editor Plus
Flutter HTML Editor Plus is a text editor for Android, iOS, and Web to help write WYSIWYG HTML code with the Summernote JavaScript wrapper.
This is a fork from html-editor-enhanced. A big thanks to tneotia for keeping the project alive.
I have removed documentation and will update it with the new API in time. In the meantime, you can read it in the original repo.
Main Goals
- Keep the package updated to the latest stable versions of Flutter and dependencies (Summernote editor included).
- Re-write the package in a more readable and maintainable format.
- Improve functionalities.
- Fix known issues.
- Add support for desktop platforms.
Setup
Platform Requirements
- Android:
minSdkVersion >= 19
,compileSdk >= 34
,AGP version >= 7.3.0
- iOS 9.0+:
--ios-language swift
,Xcode version >= 14.3
- MacOS 10.11+:
Xcode version >= 14.3
Additional Setup
- Add
html_editor_plus: ^0.0.1
as a dependency to yourpubspec.yaml
. - Make sure to declare internet support inside
AndroidManifest.xml
:<uses-permission android:name="android.permission.INTERNET"/>
. - Additional setup is required on iOS to allow the user to pick files from storage. See here for more details.
- For images, the package uses
FileType.image
, for videoFileType.video
, for audioFileType.audio
, and for any other fileFileType.any
. You can just complete setup for the specific buttons you plan to enable in the editor.
Basic Usage
import 'package:html_editor/html_editor.dart';
HtmlEditorController controller = HtmlEditorController();
@override
Widget build(BuildContext context) {
return HtmlEditor(
controller: controller, //required
htmlEditorOptions: HtmlEditorOptions(
hint: "Your text here...",
//initialText: "text content initial, if any",
),
otherOptions: OtherOptions(
height: 400,
),
);
}
Important Note for Web
At the moment, there is quite a bit of flickering and repainting when having many UI elements draw over IframeElement
s. See here for more details.
The current workaround is to build and/or run your Web app with flutter run --web-renderer html
and flutter build web --web-renderer html
.
Follow here for updates on a potential fix. In the meantime, the above solution should resolve the majority of the flickering issues.
API Reference
For the full API reference, see here.
For a full example, see here.
PLUS Version
The PLUS version is the current package re-written using the current Flutter version, standards, and patterns. While some similarities will remain, most of the API will be different.
Keep in mind that the new version is WORK IN PROGRESS. This means that breaking changes will most likely occur on every release!
Some Notable Changes in the New API (WIP)
HtmlEditorController
- Is implemented similar to other Flutter controllers, meaning it extends
ValueNotifier
and the value will be stored into aHtmlEditorValue
. - Side effects of this change:
- If initialized, the controller will require manual disposal through the
dispose()
method. - Listeners can be attached to the controller to react when the value has changed.
- The value can be used with
ValueListenableBuilder
. - Some methods have been renamed, and method signatures have changed to use named parameters.
- If initialized, the controller will require manual disposal through the
Other Changes
- Features removed:
- Developers will not have access to InAppWebViewController, because the editor means to be a common interface for different platforms. As such, it makes no sense to expose the controller for mobile. At least not in the current phase.
- Editor notifications will not be implemented. The editor should be exactly that, a HTML RICH text editor which outputs the text as a HTML string. Notifications should be implemented separately through Flutter.
Implementation Progress
- ✅ Editor field which accepts HTML code
- ✅ Controller which supports the original API
- ✅ Theme-based CSS, with CSS builder available to developers
- ✅ JavaScript builder property which allows developers to add custom JavaScript code
- ✅ Summernote callbacks
- ❌ Editor toolbar
- ❌ Summernote mentions
- ❌ macOS support using InAppWebView
- ❌ Custom editor events
- ❌ Custom editor callbacks
License
This project is licensed under the MIT License - see the LICENSE file for details.
Contribution Guide
PRs are always welcome.
Full Example
Here is a full example demonstrating how to use the html_editor_plus
package:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:html_editor_plus/html_editor.dart';
import 'package:file_picker/file_picker.dart';
import 'plus/example_scaffold.dart';
void main() => runApp(HtmlEditorExampleApp(showPlusExample: false));
class HtmlEditorExampleApp extends StatelessWidget {
final bool showPlusExample;
const HtmlEditorExampleApp({this.showPlusExample = false});
@override
Widget build(BuildContext context) => MaterialApp(
title: 'Flutter Demo',
theme: ThemeData.dark(useMaterial3: false),
home: showPlusExample
? const HtmlEditorPlusExample()
: const HtmlEditorExample(title: 'Flutter HTML Editor Example'),
);
}
class HtmlEditorExample extends StatefulWidget {
const HtmlEditorExample({super.key, required this.title});
final String title;
@override
_HtmlEditorExampleState createState() => _HtmlEditorExampleState();
}
class _HtmlEditorExampleState extends State<HtmlEditorExample> {
String result = '';
late final HtmlEditorController _controller;
@override
void initState() {
super.initState();
_controller = HtmlEditorController();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
if (!kIsWeb) {
_controller.clearFocus();
}
},
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
elevation: 0,
actions: [
IconButton(
icon: Icon(Icons.refresh),
onPressed: () {
if (kIsWeb) {
_controller.reloadWeb();
} else {
_controller.editorController!.reload();
}
})
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_controller.toggleCodeView();
},
child: Text(r'<\>', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
),
body: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
HtmlEditor(
controller: _controller,
htmlEditorOptions: HtmlEditorOptions(
hint: 'Your text here...',
shouldEnsureVisible: true,
),
htmlToolbarOptions: HtmlToolbarOptions(
toolbarPosition: ToolbarPosition.aboveEditor,
toolbarType: ToolbarType.nativeScrollable,
onButtonPressed: (ButtonType type, bool? status, Function? updateStatus) {
print("button '${type.name}' pressed, the current selected status is $status");
return true;
},
onDropdownChanged: (DropdownType type, dynamic changed, Function(dynamic)? updateSelectedItem) {
print("dropdown '${type.name}' changed to $changed");
return true;
},
mediaLinkInsertInterceptor: (String url, InsertFileType type) {
print(url);
return true;
},
mediaUploadInterceptor: (PlatformFile file, InsertFileType type) async {
print(file.name); //filename
print(file.size); //size in bytes
print(file.extension); //file extension (eg jpeg or mp4)
return true;
},
),
otherOptions: OtherOptions(height: 550),
callbacks: Callbacks(
onBeforeCommand: (String? currentHtml) {
print('html before change is $currentHtml');
},
onChangeContent: (String? changed) {
print('content changed to $changed');
},
onChangeCodeview: (String? changed) {
print('code changed to $changed');
},
onChangeSelection: (EditorSettings settings) {
print('parent element is ${settings.parentElement}');
print('font name is ${settings.fontName}');
},
onDialogShown: () {
print('dialog shown');
},
onEnter: () {
print('enter/return pressed');
},
onFocus: () {
print('editor focused');
},
onBlur: () {
print('editor unfocused');
},
onBlurCodeview: () {
print('codeview either focused or unfocused');
},
onInit: () {
print('init');
},
onImageUploadError: (FileUpload? file, String? base64Str, UploadError error) {
print(error.name);
print(base64Str ?? '');
if (file != null) {
print(file.name);
print(file.size);
print(file.type);
}
},
onKeyDown: (int? keyCode) {
print('$keyCode key downed');
print('current character count: ${_controller.characterCount}');
},
onKeyUp: (int? keyCode) {
print('$keyCode key released');
},
onMouseDown: () {
print('mouse downed');
},
onMouseUp: () {
print('mouse released');
},
onNavigationRequestMobile: (String url) {
print(url);
return NavigationActionPolicy.ALLOW;
},
onPaste: () {
print('pasted into editor');
},
onScroll: () {
print('editor scrolled');
},
),
plugins: [
SummernoteAtMention(
getSuggestionsMobile: (String value) {
var mentions = ['test1', 'test2', 'test3'];
return mentions.where((element) => element.contains(value)).toList();
},
mentionsWeb: ['test1', 'test2', 'test3'],
onSelect: (String value) {
print(value);
},
),
],
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
style: TextButton.styleFrom(backgroundColor: Colors.blueGrey),
onPressed: () {
_controller.undo();
},
child: Text('Undo', style: TextStyle(color: Colors.white)),
),
SizedBox(width: 16),
TextButton(
style: TextButton.styleFrom(backgroundColor: Colors.blueGrey),
onPressed: () {
_controller.clear();
},
child: Text('Reset', style: TextStyle(color: Colors.white)),
),
SizedBox(width: 16),
TextButton(
style: TextButton.styleFrom(backgroundColor: Theme.of(context).colorScheme.secondary),
onPressed: () async {
var txt = await _controller.getText();
if (txt.contains('src=\"data:')) {
txt = '<text removed due to base-64 data, displaying the text could cause the app to crash>';
}
setState(() {
result = txt;
});
},
child: Text('Submit', style: TextStyle(color: Colors.white)),
),
SizedBox(width: 16),
TextButton(
style: TextButton.styleFrom(backgroundColor: Theme.of(context).colorScheme.secondary),
onPressed: () {
_controller.redo();
},
child: Text('Redo', style: TextStyle(color: Colors.white)),
),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(result),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
style: TextButton.styleFrom(backgroundColor: Colors.blueGrey),
onPressed: () {
_controller.disable();
},
child: Text('Disable', style: TextStyle(color: Colors.white)),
),
SizedBox(width: 16),
TextButton(
style: TextButton.styleFrom(backgroundColor: Theme.of(context).colorScheme.secondary),
onPressed: () async {
_controller.enable();
},
child: Text('Enable', style: TextStyle(color: Colors.white)),
),
],
),
),
SizedBox(height: 16),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
style: TextButton.styleFrom(backgroundColor: Theme.of(context).colorScheme.secondary),
onPressed: () {
_controller.insertText('Google');
},
child: Text('Insert Text', style: TextStyle(color: Colors.white)),
),
SizedBox(width: 16),
TextButton(
style: TextButton.styleFrom(backgroundColor: Theme.of(context).colorScheme.secondary),
onPressed: () {
_controller.insertHtml('<p style="color: blue">Google in blue</p>');
},
child: Text('Insert HTML', style: TextStyle(color: Colors.white)),
),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
style: TextButton.styleFrom(backgroundColor: Theme.of(context).colorScheme.secondary),
onPressed: () async {
_controller.insertLink('Google linked', 'https://google.com', true);
},
child: Text('Insert Link', style: TextStyle(color: Colors.white)),
),
SizedBox(width: 16),
TextButton(
style: TextButton.styleFrom(backgroundColor: Theme.of(context).colorScheme.secondary),
onPressed: () {
_controller.insertNetworkImage(
'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png',
filename: 'Google network image');
},
child: Text('Insert network image', style: TextStyle(color: Colors.white)),
),
],
),
),
SizedBox(height: 16),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
style: TextButton.styleFrom(backgroundColor: Colors.blueGrey),
onPressed: () {
_controller.addNotification('Info notification', NotificationType.info);
},
child: Text('Info', style: TextStyle(color: Colors.white)),
),
SizedBox(width: 16),
TextButton(
style: TextButton.styleFrom(backgroundColor: Colors.blueGrey),
onPressed: () {
_controller.addNotification('Warning notification', NotificationType.warning);
},
child: Text('Warning', style: TextStyle(color: Colors.white)),
),
SizedBox(width: 16),
TextButton(
style: TextButton.styleFrom(backgroundColor: Theme.of(context).colorScheme.secondary),
onPressed: () async {
_controller.addNotification('Success notification', NotificationType.success);
},
child: Text('Success', style: TextStyle(color: Colors.white)),
),
SizedBox(width: 16),
TextButton(
style: TextButton.styleFrom(backgroundColor: Theme.of(context).colorScheme.secondary),
onPressed: () {
_controller.addNotification('Danger notification', NotificationType.danger);
},
child: Text('Danger', style: TextStyle(color: Colors.white)),
),
],
),
),
SizedBox(height: 16),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextButton(
style: TextButton.styleFrom(backgroundColor: Colors.blueGrey),
onPressed: () {
_controller.addNotification('Plaintext notification', NotificationType.plaintext);
},
child: Text('Plaintext', style: TextStyle(color: Colors.white)),
),
SizedBox(width: 16),
TextButton(
style: TextButton.styleFrom(backgroundColor: Theme.of(context).colorScheme.secondary),
onPressed: () async {
_controller.removeNotification();
},
child: Text('Remove', style: TextStyle(color: Colors.white)),
),
],
),
),
],
),
),
),
);
}
}
This example demonstrates how to set up and use the html_editor_plus
package, including various operations like inserting text, HTML, links, and images, as well as handling notifications and other editor functionalities.
更多关于Flutter富文本编辑器插件html_editor_plus的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter富文本编辑器插件html_editor_plus的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何在Flutter项目中使用html_editor_plus
插件来实现富文本编辑功能的代码案例。这个插件允许你嵌入一个功能齐全的HTML编辑器到你的Flutter应用中。
1. 添加依赖
首先,你需要在你的pubspec.yaml
文件中添加html_editor_plus
依赖:
dependencies:
flutter:
sdk: flutter
html_editor_plus: ^3.0.0 # 请检查最新版本号
然后运行flutter pub get
来安装依赖。
2. 导入插件
在你的Dart文件中导入html_editor_plus
:
import 'package:flutter/material.dart';
import 'package:html_editor_plus/html_editor_plus.dart';
3. 使用HtmlEditorPlus
下面是一个完整的示例,展示如何在Flutter应用中集成和使用HtmlEditorPlus
:
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Html Editor Plus Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
HtmlEditorController _controller = HtmlEditorController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Html Editor Plus Example'),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
Expanded(
child: HtmlEditorPlus(
controller: _controller,
// 可选配置
htmlEditorOptions: HtmlEditorOptions(
hint: 'Enter some HTML...',
initialHtml: '<p>Hello, <strong>world</strong>!</p>',
),
),
),
ElevatedButton(
onPressed: () {
// 获取编辑器内容
_controller.getHtml().then((html) {
print("HTML Content: $html");
});
},
child: Text('Get HTML'),
),
],
),
),
);
}
@override
void dispose() {
// 释放控制器资源
_controller.dispose();
super.dispose();
}
}
4. 运行应用
保存上述代码并运行你的Flutter应用。你应该会看到一个带有富文本编辑器的界面,以及一个按钮用于获取编辑器中的HTML内容。
5. 自定义配置
HtmlEditorOptions
类提供了多种配置选项,允许你自定义编辑器的行为,比如设置工具栏按钮、初始化内容、占位符等。你可以根据需求调整这些配置。
这个示例展示了如何在Flutter中使用html_editor_plus
插件来实现基本的富文本编辑功能。根据你的具体需求,你可以进一步扩展和自定义这个编辑器。