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

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

入门

flu_editor 是一个用于照片和视频的颜色滤镜编辑插件。以下是使用 flu_editor 的步骤和示例代码。

功能概述

flu_editor 提供了多种图像编辑功能,以下是工具类的功能概述:

EditorUtil 工具类

页面导航说明

  • goFluEditor:跳转到编辑器页面。

  • 参数说明:

    • context:当前上下文,通常使用 BuildContext 来启动编辑器。
    • orignal:原始图片路径,用于传入需要编辑的图片。
    • type:编辑类型 (null 代表进首页)。
    • singleEditorSave:单独进到某个功能页面,关闭是否保存图片到相册。
    • vipStatusCb:一个回调函数,返回用户是否为 VIP 用户。
    • vipActionCb:一个回调函数,当用户非 VIP 时,触发跳转到订阅页面。
    • saveCb:一个回调函数,用于保存编辑后的图片,参数为保存路径。
    • loadWidgetCb:加载提示回调,用于显示加载动画,传入 islight(是否为浅色模式)、size(进度条大小)、stroke(进度条宽度)。
    • toastActionCb:一个回调函数,显示自定义提示信息(如 “保存成功”)。
    • effectsCb:回调函数,用于获取并处理滤镜配方。
    • saveEffectCb:回调函数,保存自定义滤镜配方。
    • deleteEffectCb:回调函数,删除已保存的滤镜配方。
    • filtersCb:回调函数,用于获取滤镜列表。
    • stickersCb:回调函数,用于获取贴纸列表。
    • fontsCb:回调函数,用于获取字体列表。
    • framesCb:回调函数,用于获取边框列表。
    • homeSavedCb:回调函数,编辑器主页保存图片。
  • 内部页面路由

    • 编辑器首页进入的具体功能区(宿主app不要直接调用,要通过 goFluEditor(type) 进入)
      • goCropPage:跳转到裁剪页面。
      • goColorsPage:跳转到颜色调整页面。
      • goFilterPage:跳转到滤镜编辑页面。
      • goStickerPage:跳转到贴纸编辑页面。
      • goFontPage:跳转到字体编辑页面。
      • goFramePage:跳转到相框编辑页面。

多语言配置

MaterialApp 内添加如下 delegate 以及 supportedLocales

MaterialApp(
  localizationsDelegates: const [
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
    EditorLang.delegate
  ],
  supportedLocales: [...EditorLang.delegate.supportedLocales],
)

使用示例

以下是如何使用 flu_editor 进行图像编辑的示例:

import 'dart:convert';
import 'dart:io';

import 'package:flu_editor_example/route_page.dart';
import 'package:flutter/material.dart';
import 'dart:async';

import 'package:flutter/services.dart';
import 'package:flu_editor/generated/l10n.dart';
import 'package:flu_editor/flu_editor.dart';
import 'package:gallery_saver_plus/gallery_saver.dart';
import 'package:image_picker/image_picker.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

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

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

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

class _MyAppState extends State<MyApp> {
  String _platformVersion = 'Unknown';
  final _fluEditorPlugin = FluEditor();

  /// 当前输入图
  String _currentImage = '';

  bool isVipUser = false;

  [@override](/user/override)
  void initState() {
    super.initState();
    initPlatformState();
  }

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlatformState() async {
    String platformVersion;
    // Platform messages may fail, so we use a try/catch PlatformException.
    // We also handle the message potentially returning null.
    try {
      platformVersion = await _fluEditorPlugin.getPlatformVersion() ?? 'Unknown platform version';
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';
    }

    // If the widget was removed from the tree while the asynchronous platform
    // message was in flight, we want to discard the reply rather than calling
    // setState to update our non-existent appearance.
    if (!mounted) return;

    setState(() {
      _platformVersion = platformVersion;
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: const [
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
        EditorLang.delegate
      ],
      supportedLocales: [...EditorLang.delegate.supportedLocales],
      home: Builder(builder: (context) {
        return Scaffold(
          appBar: AppBar(
            title: const Center(
              child: Text('FluEditorApp'),
            ),
          ),
          body: Column(
            children: [
              Expanded(
                  child: Container(
                      width: double.infinity,
                      color: Colors.grey,
                      child: Stack(alignment: Alignment.center, children: [
                        _currentImage.isEmpty
                            ? const SizedBox()
                            : Image.file(File(_currentImage)),
                        GestureDetector(
                          onTap: () {
                            _pickImage(context);
                          },
                          child: Container(
                            height: 200,
                            width: 200,
                            color: Colors.white.withOpacity(0.4),
                            child: const Column(
                              mainAxisAlignment: MainAxisAlignment.center,
                              crossAxisAlignment: CrossAxisAlignment.center,
                              children: [
                                Icon(
                                  Icons.add_a_photo,
                                  size: 50.0,
                                ),
                                SizedBox(
                                  height: 20,
                                ),
                                Text(
                                  'Add photo',
                                  style: TextStyle(color: Colors.black, fontSize: 24),
                                ),
                              ],
                            ),
                          ),
                        ),
                      ]))),
              const SizedBox(
                height: 20,
              ),
              Material(
                child: SizedBox(
                  width: 200,
                  child: FilledButton(
                      onPressed: () async {
                        if (_currentImage.isEmpty) {
                          ScaffoldMessenger.of(context).showSnackBar(
                              const SnackBar(content: Text('Add photo pleasen!')));
                          return;
                        }
                        _goEditor(context);
                      },
                      child: const Text('Go Editor')),
                ),
              ),
              const SizedBox(
                height: 40,
              ),
            ],
          ),
        );
      }),
    );
  }

  Future<void> _pickImage(BuildContext context) async {
    ImagePicker picker = ImagePicker();
    final XFile? image = await picker.pickImage(source: ImageSource.gallery);

    if (image == null) {
      return;
    }

    _currentImage = image?.path ?? '';
    setState(() {});
  }

  Future<void> _goEditor(BuildContext context) async {
    EditorUtil.goFluEditor(context,
        orignal: _currentImage,
        vipStatusCb: () {
          debugPrint('get vip status: $isVipUser');
          return isVipUser;
        },
        vipActionCb: () {
          debugPrint('go Sub');
          Navigator.of(context).push(MaterialPageRoute(
            builder: (context) {
              return RoutePage(title: 'Sub page',);
            },
          ));
        },
        saveCb: (path) async {
          GallerySaver.saveImage(path, albumName: 'Flu-Editor');
        },
        loadWidgetCb: (islight, size, stroke) => Container(
              width: size,
              height: size,
              alignment: Alignment.center,
              child: CircularProgressIndicator(
                color: islight ? Colors.white : Colors.black,
                strokeWidth: stroke,
              ),
            ),
        toastActionCb: (msg) => ScaffoldMessenger.of(context)
            .showSnackBar(SnackBar(content: Text(msg))),
        effectsCb: (page) async => await _fetchPF(),
        saveEffectCb: (effect) async {
          debugPrint('Save pf:${effect.toJson()}');
          return await true;
        },
        deleteEffectCb: (id) async {
          debugPrint('Delete:$id');
          return await true;
        },
        filtersCb: () => _fetchLJ(),
        stickersCb: () => _fetchStickers(),
        fontsCb: () => _fetchFonts(),
        framesCb: () => _fetchFrames(),
        homeSavedCb: (context, after) {
          Navigator.of(context).push(MaterialPageRoute(
            builder: (context) {
              return RoutePage(savedPath: after, title: 'Saved page',);
            },
          ));
        });
  }

  Future<List<EffectData>> _fetchPF() async {
    return await [
      EffectData.fromJson({
        'name': 'test',
        'image':
            'https://nwdnui.oss-cn-beijing.aliyuncs.com/user/effectSave/da2752d15d0e48359bbc42c7ec845d3d/1730962077026793.jpg',
        'id': 0,
        'params': jsonEncode({
          "Brightness": 0.14719999999999997,
          "Saturation": 1.0,
          "Contrast": 1.0,
          "Sharpen": 0.0,
          "Shadow": 0.0,
          "Temperature": 0.0,
          "Noise": 0.0,
          "Exposure": 0.0,
          "Vibrance": 0.0,
          "Highlight": 0.0,
          "Red": 1.0,
          "Green": 1.0,
          "Blue": 1.0,
          "CenterX": 0.5,
          "CenterY": 0.5,
          "Start": 1.0,
          "End": 1.0
        })
      })
    ];
  }

  Future<List<FilterData>> _fetchLJ() async {
    FilterDetail detail1 = FilterDetail();
    detail1.id = 1;
    detail1.image =
        'https://nwdnui.bigwinepot.com/ui/index/icon/90ad4f7bbd3243c285d4f8aaff5123be.jpg';
    detail1.filterImage = 'luts/01-x.png';
    detail1.name = 'class1';
    detail1.noise = 0.2;
    detail1.vip = 1;
    detail1.lutFrom = 0;

    FilterDetail detail2 = FilterDetail();
    detail2.id = 2;
    detail2.image =
        'https://nwdnui.bigwinepot.com/ui/index/icon/90ad4f7bbd3243c285d4f8aaff5123be.jpg';
    detail2.filterImage = 'luts/03-x.png';
    detail2.name = 'class2';
    detail2.lutFrom = 0;

    FilterData group1 = FilterData();
    group1.groupName = 'class1';

    group1.list = [detail1, detail2];

    return [group1];
  }

  Future<List<StickerData>> _fetchStickers() async {
    StickDetail detail1 = StickDetail();
    detail1.id = 1;
    detail1.image =
        'https://nwdnui.bigwinepot.com/ui/index/icon/e71b319ebce14952a87a40a03f8e7404.png';
    detail1.name = 'sticker1';
    detail1.vip = 0;

    StickDetail detail2 = StickDetail();
    detail2.id = 1;
    detail2.image =
        'https://nwdnui.bigwinepot.com/ui/index/icon/1f0ceb1952a44a4ebd0a8c419a105545.png';
    detail2.name = 'sticker2';
    detail2.vip = 0;

    StickerData group1 = StickerData();
    group1.groupName = 'class1';
    group1.groupImage =
        'https://nwdnui.bigwinepot.com/ui/index/icon/318fa7a144af47f29adbdc73cb7e78b5.png';

    group1.list = [detail1, detail2];

    return [group1];
  }

  Future<List<FontsData>> _fetchFonts() async {
    FontDetail detail1 = FontDetail();
    detail1.id = 1;
    detail1.image =
        'https://nwdnui.bigwinepot.com/ui/index/icon/ca9f5c3e742d49c2bafa28c8808a2280.jpg';
    detail1.file =
        'https://nwdnui.bigwinepot.com/ui/index/icon/7be3f3395e5c49b3aec36071c9bacc03.ttf';
    detail1.name = 'font1';
    detail1.vip = 0;

    FontsData group1 = FontsData();
    group1.groupName = 'Sample';

    group1.list = [detail1];

    return [group1];
  }

  Future<List<FrameData>> _fetchFrames() async {
    FrameDetail detail1 = FrameDetail();
    detail1.id = 1;
    detail1.image =
        'https://nwdnui.bigwinepot.com/ui/index/icon/6c923546f7ff46d9bf613808b9bce72d.png';
    detail1.name = 'frame1';
    detail1.vip = 0;
    FrameSize size = FrameSize();
    size.frameWidth = 560;
    size.frameHeight = 1000;
    size.frameLeft = 94.0;
    size.frameTop = 142.0;
    size.frameRight = 88.0;
    size.frameBottom = 114.0;
    detail1.params = size;

    FrameDetail detail2 = FrameDetail();
    detail2.id = 2;
    detail2.image =
        'https://nwdnui.bigwinepot.com/ui/index/icon/e0ee85fe76e34fd093729428757e0401.png';
    detail2.name = 'frame2';
    detail2.vip = 0;
    FrameSize size2 = FrameSize();
    size2.frameWidth = 672;
    size2.frameHeight = 1000;
    size2.frameLeft = 136.0;
    size2.frameTop = 154.0;
    size2.frameRight = 136.0;
    size2.frameBottom = 156.0;
    detail2.params = size2;

    FrameData group1 = FrameData();
    group1.groupName = 'Sample';

    group1.list = [detail1, detail2];

    return [group1];
  }
}

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

1 回复

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


flu_editor 是一个用于 Flutter 的富文本编辑器插件,它提供了丰富的功能来编辑和格式化文本。使用 flu_editor 可以轻松地在 Flutter 应用中实现富文本编辑功能。

以下是如何在 Flutter 项目中使用 flu_editor 的基本步骤:

1. 添加依赖

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

dependencies:
  flutter:
    sdk: flutter
  flu_editor: ^0.0.1  # 请使用最新版本

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

2. 导入包

在你的 Dart 文件中导入 flu_editor 包:

import 'package:flu_editor/flu_editor.dart';

3. 使用 FluEditor 组件

你可以在你的 Flutter 应用中使用 FluEditor 组件来创建一个富文本编辑器。以下是一个简单的示例:

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('FluEditor Example'),
        ),
        body: Padding(
          padding: const EdgeInsets.all(8.0),
          child: FluEditor(
            controller: FluEditorController(),
            placeholder: 'Enter your text here...',
          ),
        ),
      ),
    );
  }
}

4. 使用 FluEditorController

FluEditorControllerFluEditor 的核心控制器,用于管理编辑器的内容和状态。你可以使用它来获取或设置编辑器的内容,以及执行其他操作。

例如,你可以通过 FluEditorController 来获取当前的文本内容:

FluEditorController _controller = FluEditorController();

void _getText() {
  String text = _controller.getText();
  print(text);
}

5. 自定义工具栏

flu_editor 提供了一个默认的工具栏,但你可以自定义工具栏以满足你的需求。你可以通过 toolbarBuilder 参数来自定义工具栏:

FluEditor(
  controller: _controller,
  placeholder: 'Enter your text here...',
  toolbarBuilder: (BuildContext context, FluEditorController controller) {
    return Row(
      children: [
        IconButton(
          icon: Icon(Icons.format_bold),
          onPressed: () {
            controller.toggleBold();
          },
        ),
        IconButton(
          icon: Icon(Icons.format_italic),
          onPressed: () {
            controller.toggleItalic();
          },
        ),
        // 添加更多按钮...
      ],
    );
  },
)

6. 处理内容变化

你可以监听编辑器的内容变化,以便在用户输入时执行某些操作:

FluEditor(
  controller: _controller,
  placeholder: 'Enter your text here...',
  onChanged: (String text) {
    print('Text changed: $text');
  },
)

7. 其他功能

flu_editor 还提供了许多其他功能,例如插入图片、链接、列表等。你可以查阅官方文档或源代码以了解更多详细信息。

8. 处理键盘事件

你可以通过 onKeyEvent 参数来处理键盘事件,例如在按下回车键时执行某些操作:

FluEditor(
  controller: _controller,
  placeholder: 'Enter your text here...',
  onKeyEvent: (RawKeyEvent event) {
    if (event.logicalKey == LogicalKeyboardKey.enter) {
      print('Enter key pressed');
    }
  },
)

9. 样式定制

你可以通过 style 参数来定制编辑器的样式,例如字体大小、颜色等:

FluEditor(
  controller: _controller,
  placeholder: 'Enter your text here...',
  style: TextStyle(
    fontSize: 16,
    color: Colors.black,
  ),
)
回到顶部