Flutter撤销操作插件nf_flutter_undo的使用

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

Flutter撤销操作插件nf_flutter_undo的使用

Flutter插件nf_flutter_undo提供了基于.NET项目中常见的命令模式的简单撤销/重做系统。

使用

首先,在你的Widget树中添加一个InheritedUndo

/// 省略 ...
InheritedUndo(
    child: Placeholder(),
)

然后创建一个命令,并实现executeunexecute函数:

BasicCommand bc = BasicCommand(
    commandName: "<Command Name>",
    execute: () {
        setState(() => values.add(c));
    },
    canExecute: () => true,
    canUnexecute: () => values.isNotEmpty,
    unexecute: () {
        setState(() => values.removeLast());
    },
);

接着将该命令添加到撤销堆栈并执行它:

InheritedUndo.of(context).undoStack.pushCommand(bc);
bc.execute();

这会将命令推送到UndoStack,现在它可以被弹出和推送。

UndoStack接口

UndoStack具有以下接口:

class UndoStack {
  /// 最大允许在历史堆栈中的元素数量
  ///
  /// 必须是一个正整数
  ///
  /// 较大的值意味着程序会直接消耗更多的内存来存储更多的撤销状态
  final int maxStackSize;

  /// 指向用户已经撤销到的位置
  ///
  /// 该指针用于启用重做功能
  ///
  /// 一个命令可以被“撤销”,此时堆栈指针增加1
  /// 命令的“unexecute()”函数会被运行,但不会从堆栈中实际移除
  ///
  /// 如果用户“重做”该命令,堆栈指针减少1
  /// 命令的“execute()”函数会被运行,但不会将任何东西推入堆栈
  ///
  /// 如果用户“撤销”了一个命令,然后添加了一个新命令,堆栈指针被设置为‘0’,重做不再可能
  int stackPointer = -1;

  /// 当前堆栈中可撤销元素的数量
  int get stackSize;

  bool get canUndo;
  bool get canRedo;

  /// 将此命令添加到堆栈。执行时间在命令内部记录。
  ///
  /// 添加到堆栈并不意味着执行——你需要自行调用`execute()`,可以在添加到堆栈之前或之后执行
  void pushCommand(ICommand command);

  /// 撤销最近的一个可撤销命令
  ///
  /// 如果命令无法撤销,则从列表中删除该命令,并继续搜索直到:
  ///   * 列表为空
  ///   * 找到一个可撤销的元素
  ///
  /// 返回被撤销的`CommandHistory`对象,如果没有撤销则返回`null`
  CommandHistory? undo();

  /// 重做堆栈头部的命令
  CommandHistory? redo();  

  /// 返回最近应用的命令
  ///
  /// 注意:不要执行或取消执行由该方法返回的命令,否则撤销堆栈将失去状态一致性
  CommandHistory? peekCurrent();

  /// 返回最近撤销的命令
  ///
  /// 注意:不要执行或取消执行由该方法返回的命令,否则撤销堆栈将失去状态一致性
  CommandHistory? peekForward();

  /// 返回当前命令堆栈的不可修改列表视图
  List<CommandHistory> listCommands();

  /// 清空当前历史记录。此操作不可撤销。
  void clearHistory();
}

示例Demo

下面是一个完整的示例,展示了如何使用nf_flutter_undo插件实现撤销和重做功能:

import 'package:flutter/material.dart';
import 'package:nf_flutter_undo/src/command/basic_command.dart';
import 'package:nf_flutter_undo/src/undo/inherited_undo.dart';

class UndoView extends StatefulWidget {
  const UndoView({super.key});
  static const String routeName = "UndoTest";

  [@override](/user/override)
  State<UndoView> createState() => _UndoViewState();
}

class _UndoViewState extends State<UndoView> {
  List<String> values = [];
  int pc = 0;

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

  void undo() {
    InheritedUndo.of(context).undoStack.undo();
  }

  void redo() {
    InheritedUndo.of(context).undoStack.redo();
  }

  void reset() {
    InheritedUndo.of(context).undoStack.clearHistory();
    setState(() => values.clear());
  }

  void add() {
    const String chars = "abcdefghijklmnopqrstuvwxyz";
    String c = chars[pc % chars.length];
    pc += 1;
    BasicCommand bc = BasicCommand(
      commandName: "Add '$c'",
      execute: () {
        setState(() => values.add(c));
      },
      canExecute: () => true,
      canUnexecute: () => values.isNotEmpty,
      unexecute: () {
        setState(() => values.removeLast());
      },
    );
    InheritedUndo.of(context).undoStack.pushCommand(bc);
    bc.execute();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return InheritedUndo(
      key: const Key('InheritedUndoSample'),
      child: (Column(
        children: [
          Row(
            children: [
              ElevatedButton(onPressed: undo, child: Text("撤销")),
              ElevatedButton(onPressed: redo, child: Text("重做")),
              ElevatedButton(onPressed: add, child: Text("+")),
              ElevatedButton(onPressed: reset, child: Text("重置")),
            ],
          ),
          Row(
            children: [
              for (final val in values) Text("$val "),
            ],
          ),
          Column(
            children: [
              Text("堆栈信息"),
              Text("堆栈指针: ${InheritedUndo.of(context).undoStack.stackPointer}"),
              Text("堆栈长度(可撤销): ${InheritedUndo.of(context).undoStack.stackSize}"),
              Text("是否可撤销: ${InheritedUndo.of(context).undoStack.canUndo}"),
              Text("是否可重做: ${InheritedUndo.of(context).undoStack.canRedo}"),
              Row(
                children: [
                  Text("["),
                  for (final c in InheritedUndo.of(context).undoStack.listCommands())
                    Text("${c.command.commandName}, "),
                  Text("]"),
                ],
              )
            ],
          ),
        ],
      )),
    );
  }
}

更多关于Flutter撤销操作插件nf_flutter_undo的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter撤销操作插件nf_flutter_undo的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用nf_flutter_undo插件来实现撤销操作的示例代码。nf_flutter_undo是一个用于管理撤销和重做操作的插件,可以帮助你在Flutter应用中轻松实现这些功能。

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

dependencies:
  flutter:
    sdk: flutter
  nf_flutter_undo: ^最新版本号  # 请确保使用最新版本

然后运行flutter pub get来安装依赖。

接下来,我们将编写一个简单的示例,展示如何使用nf_flutter_undo插件。

示例代码

  1. 创建Flutter项目(如果还没有的话):
flutter create undo_example
cd undo_example
  1. 修改main.dart文件
import 'package:flutter/material.dart';
import 'package:nf_flutter_undo/nf_flutter_undo.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Undo Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Undo Example'),
        ),
        body: UndoProvider(
          child: UndoExample(),
        ),
      ),
    );
  }
}

class UndoExample extends StatefulWidget {
  @override
  _UndoExampleState createState() => _UndoExampleState();
}

class _UndoExampleState extends State<UndoExample> with Undoable {
  final List<String> _items = [];
  final TextEditingController _controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    final undoStack = UndoStack.of(context);

    return Padding(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text('Items:', style: TextStyle(fontSize: 18)),
          Expanded(
            child: ListView.builder(
              itemCount: _items.length,
              itemBuilder: (_, index) => ListTile(
                title: Text(_items[index]),
              ),
            ),
          ),
          SizedBox(height: 16),
          TextField(
            controller: _controller,
            decoration: InputDecoration(labelText: 'New Item'),
          ),
          SizedBox(height: 16),
          ElevatedButton(
            onPressed: () {
              if (_controller.text.isNotEmpty) {
                undoStack.withGroup(() {
                  setState(() {
                    _items.add(_controller.text);
                  });
                  _controller.clear();
                });
              }
            },
            child: Text('Add Item'),
          ),
          SizedBox(height: 16),
          Row(
            children: [
              ElevatedButton(
                onPressed: () => undoStack.undo(),
                child: Text('Undo'),
                style: ButtonStyle(
                  overlayColor: MaterialStateProperty.all(Colors.red),
                ),
              ),
              SizedBox(width: 16),
              ElevatedButton(
                onPressed: () => undoStack.redo(),
                child: Text('Redo'),
                style: ButtonStyle(
                  overlayColor: MaterialStateProperty.all(Colors.green),
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

解释

  1. 依赖注入:我们在MyApphome属性中使用了UndoProvider来包装我们的UndoExample组件。这确保了UndoStack在整个UndoExample组件中都是可用的。

  2. 状态管理:在_UndoExampleState中,我们使用了StatefulWidget来管理我们的文本输入和列表项。

  3. 添加项目:当用户点击“Add Item”按钮时,我们会在undoStack.withGroup中包裹我们的状态更新代码。这确保了添加操作和清空文本输入被视为一个原子操作,可以一起撤销。

  4. 撤销和重做:我们提供了两个按钮来触发撤销和重做操作,分别调用undoStack.undo()undoStack.redo()

这样,你就已经成功地在Flutter项目中集成了nf_flutter_undo插件,并实现了基本的撤销和重做功能。希望这个示例对你有所帮助!

回到顶部