Flutter自定义用户命令执行插件user_command的使用

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

Flutter自定义用户命令执行插件user_command的使用

Command 类

Command 类是一个统一定义的用户命令。一个 Command 包含以下动态属性:

  • name:命令名称。
  • action:当用户点击命令时要执行的代码。
  • (可选)icon:命令图标。
  • (可选)visibility:命令可见性。

注意,Command 类没有禁用状态,因为禁用按钮体验不好。

Command 小部件

Command 可以在以下小部件中使用:

  • CommandTextButton
  • CommandElevatedButton
  • CommandOutlinedButton
  • CommandPopupMenuItem(例如,在 CommandPopupMenu 中)
  • CommandPopupMenuButton
  • CommandPopupMenuWrapper
  • CommandToolbarButton(例如,在 CommandToolbar 中)
  • CommandTile(例如,在 CommandListView 中)
  • 等等…

Command 小部件样式

这些 Command 小部件都有一个单一的样式类,该类:

  • 在没有提供样式参数时使用合理的默认格式。
  • 包含所有格式的样式参数:
    • 大小
    • 颜色
    • 字体
    • 填充
    • 对齐
    • 高度
    • 等等

安装和使用

请参阅相关文档了解安装和使用方法。

示例

以下是完整的示例 demo:

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:user_command/user_command.dart';

void main() {
  return runApp(const App());
}

final ThemeData darkTheme = ThemeData(
  brightness: Brightness.dark,
  primarySwatch: Colors.blue,
);

final ThemeData lightTheme = ThemeData(
  brightness: Brightness.light,
  primarySwatch: Colors.blue,
);

const List<IconData> numberedIcons = [
  Icons.looks_one,
  Icons.looks_two,
  Icons.looks_3,
  Icons.looks_4,
  Icons.looks_5,
  Icons.looks_6
];

// 创建示例命令列表
List<Command> createExampleCommands(BuildContext context) => [
      Command(
          name: "Example without icon",
          action: () {
            showSnackBar(context, 'You selected: Example without icon');
          }),
      Command.dynamic(
          name: () => "Sometimes visible example",
          icon: () => Icons.casino,
          visible: () => Random().nextBool(),
          action: () {
            showSnackBar(context, 'You selected: Sometimes visible example');
          }),
      for (int index = 0; index < 6; index++)
        Command(
            name: "Example ${index + 1}",
            icon: numberedIcons[index],
            action: () {
              showSnackBar(context, 'You selected: Example ${index + 1}');
            }),
    ];

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

  [@override](/user/override)
  AppState createState() => AppState();
}

class AppState extends State<App> {
  Widget _page = const WelcomePage();

  set page(Widget newPage) {
    setState(() {
      _page = newPage;
    });
  }

  ThemeData _theme = lightTheme;

  ThemeData get theme => _theme;

  set theme(ThemeData newTheme) {
    setState(() {
      _theme = newTheme;
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: theme,
      home: Scaffold(
          appBar: AppBar(title: Text(pageTitle(_page.runtimeType))),
          drawer: DrawerMenu(this),
          body: _page),
    );
  }
}

class WelcomePage extends StatelessWidget {
  const WelcomePage({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) => const Center(
          child: Text(
        'Welcome the user_command package examples.\n\n'
        'Please select an example from the menu...',
        style: TextStyle(fontSize: 20),
      ));
}

class TextButtonExamplePage extends StatelessWidget {
  const TextButtonExamplePage({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) => Center(
          child: CommandTextButton(
        Command(
            name: "Text Button",
            icon: Icons.thumb_up,
            action: () {
              showSnackBar(context, 'You have clicked on the text button');
            }),
      ));
}

class ElevatedButtonExamplePage extends StatelessWidget {
  const ElevatedButtonExamplePage({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) => Center(
          child: CommandElevatedButton(
        Command(
            name: "Elevated Button",
            icon: Icons.thumb_up,
            action: () {
              showSnackBar(context, 'You have clicked on the elevated button');
            }),
      ));
}

class OutlinedButtonExamplePage extends StatelessWidget {
  const OutlinedButtonExamplePage({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) => Center(
          child: CommandOutlinedButton(
        Command(
            name: "Outlined Button",
            icon: Icons.thumb_up,
            action: () {
              showSnackBar(context, 'You have clicked on the outlined button');
            }),
      ));
}

class PopupMenuExamplePage extends StatelessWidget {
  const PopupMenuExamplePage({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Center(
        child: ElevatedButton(
      child: const Text('Click me to open the popup menu'),
      onPressed: () {
        CommandPopupMenu(context, createExampleCommands(context),
            title: "Popup Menu");
      },
    ));
  }
}

class PopupMenuButtonExamplePage extends StatelessWidget {
  const PopupMenuButtonExamplePage({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Center(
        child: Column(
      children: [
        CommandPopupMenuButton(
            iconData: Icons.more_vert,
            commands: createExampleCommands(context)),
        const SizedBox(height: 48),
        CommandPopupMenuButton(
          iconData: Icons.more_vert,
          commands: createExampleCommands(context),
          anchorPosition: AnchorPosition.left,
        ),
      ],
    ));
  }
}

class PopupMenuWidgetForContainerExamplePage extends StatelessWidget {
  const PopupMenuWidgetForContainerExamplePage({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Center(
      child: CommandPopupMenuWrapper(
        commands: createExampleCommands(context),
        //Important: Inkwell will not be visible when the child has a background color.
        // Use CommandPopupMenuWrapperStyle.backgroundColor instead
        style: CommandPopupMenuWrapperStyle(
            backgroundColor: Theme.of(context).colorScheme.primary),
        child: const SizedBox(
          width: 200,
          height: 200,
          child: Text(
            'Click me anywhere',
            textAlign: TextAlign.center,
          ),
        ),
      ),
    );
  }
}

class PopupMenuWidgetForListViewExamplePage extends StatelessWidget {
  const PopupMenuWidgetForListViewExamplePage({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return ListView(
      children: [
        for (int rowNr = 1; rowNr <= 20; rowNr++)
          CommandPopupMenuWrapper(
              popupMenuTitle: 'Row:$rowNr',
              commands: [
                for (int commandNr = 1; commandNr <= 5; commandNr++)
                  Command(
                      name: 'Row:$rowNr, Command:$commandNr',
                      icon: numberedIcons[commandNr - 1],
                      action: () {
                        showSnackBar(context,
                            'You selected Row:$rowNr, Command:$commandNr');
                      })
              ],
              child: ListTile(title: Text('Row:$rowNr')))
      ],
    );
  }
}

class PopupMenuButtonInsideTextFieldExamplePage extends StatelessWidget {
  final TextEditingController nameController = TextEditingController();
  final textFieldKey = GlobalKey();

  PopupMenuButtonInsideTextFieldExamplePage({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Center(
      child: ConstrainedBox(
        constraints: const BoxConstraints(maxWidth: 300),
        child: TextField(
          key: textFieldKey,
          controller: nameController,
          decoration: InputDecoration(
              labelText: 'Please enter your name',
              suffixIcon: CommandPopupMenuButton(
                // important: we want the popup menu to position relative to
                // the TextField by providing its global key.
                anchorWidgetKey: textFieldKey,
                // limiting the button height because its inside a TextField.
                style: const CommandPopupMenuButtonStyle(
                    iconButtonStyle: CommandPopupMenuIconButtonStyle(
                        constraints: BoxConstraints(minHeight: 10))),
                iconData: Icons.more_vert,
                commands: [
                  Command(
                      name: 'Clear',
                      icon: Icons.clear,
                      action: () {
                        nameController.text = '';
                      }),
                  Command(
                      name: 'Say hallo',
                      icon: Icons.chat_bubble_outlined,
                      action: () {
                        showSnackBar(context, 'Hello ${nameController.text}!');
                      })
                ],
              )),
        ),
      ),
    );
  }
}

class ToolbarExamplePage extends StatelessWidget {
  const ToolbarExamplePage({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) =>
      Center(child: CommandToolbar(createExampleCommands(context)));
}

class ListViewExamplePage extends StatelessWidget {
  const ListViewExamplePage({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return CommandListView(createExampleCommands(context));
  }
}

class DrawerMenu extends StatelessWidget {
  final AppState _appState;

  const DrawerMenu(this._appState, {super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Drawer(
        child: Scaffold(
      appBar: AppBar(title: const Text('Menu')),
      body: CommandListView([
        Command.dynamic(
          name: () => 'Switch to dark theme',
          icon: () => Icons.dark_mode,
          visible: () => _appState.theme == lightTheme,
          action: () {
            _appState.theme = darkTheme;
            closeMenu(context);
          },
        ),
        Command.dynamic(
          name: () => 'Switch to light theme',
          icon: () => Icons.light_mode,
          visible: () => _appState.theme == darkTheme,
          action: () {
            _appState.theme = lightTheme;
            closeMenu(context);
          },
        ),
        Command(
          name: 'Text Button',
          action: () {
            _appState.page = const TextButtonExamplePage();
            closeMenu(context);
          },
        ),
        Command(
          name: 'Elevated Button',
          action: () {
            _appState.page = const ElevatedButtonExamplePage();
            closeMenu(context);
          },
        ),
        Command(
          name: 'Outlined Button',
          action: () {
            _appState.page = const OutlinedButtonExamplePage();
            closeMenu(context);
          },
        ),
        Command(
          name: 'PopUp Menu',
          action: () {
            _appState.page = const PopupMenuExamplePage();
            closeMenu(context);
          },
        ),
        Command(
          name: 'Popup Menu Button',
          action: () {
            _appState.page = const PopupMenuButtonExamplePage();
            closeMenu(context);
          },
        ),
        Command(
          name: 'Nested Popup Menu Button',
          action: () {
            _appState.page = PopupMenuButtonInsideTextFieldExamplePage();
            closeMenu(context);
          },
        ),
        Command(
          name: 'Popup Menu Widget 1',
          action: () {
            _appState.page = const PopupMenuWidgetForContainerExamplePage();
            closeMenu(context);
          },
        ),
        Command(
          name: 'Popup Menu Widget 2',
          action: () {
            _appState.page = const PopupMenuWidgetForListViewExamplePage();
            closeMenu(context);
          },
        ),
        Command(
          name: 'Toolbar',
          action: () {
            _appState.page = const ToolbarExamplePage();
            closeMenu(context);
          },
        ),
        Command(
          name: 'List View and List Tile',
          action: () {
            _appState.page = const ListViewExamplePage();
            closeMenu(context);
          },
        ),
      ]),
    ));
  }
}

String pageTitle(Type pageType) {
  final pageSuffix = RegExp(r"Page$");
  final beforeCapitalLetter = RegExp(r"(?=[A-Z])");
  String titleWithoutSpaces = pageType.toString().replaceAll(pageSuffix, '');
  var titleWords = titleWithoutSpaces.split(beforeCapitalLetter);
  return 'user_command   ${titleWords.join(' ')}';
}

closeMenu(BuildContext context) {
  try {
    Navigator.pop(context);
  } catch (e) {
    // failed: not the end of the world.
  }
}

showSnackBar(BuildContext context, String message) {
  ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message)));
}

更多关于Flutter自定义用户命令执行插件user_command的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter自定义用户命令执行插件user_command的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter中使用自定义用户命令执行插件 user_command 的示例代码。请注意,这里假设你已经有一个名为 user_command 的Flutter插件,该插件允许你执行自定义的命令行指令。

1. 添加依赖

首先,你需要在你的 pubspec.yaml 文件中添加 user_command 插件的依赖。由于这是一个假设的插件,你可能需要自己实现或者找到一个类似的插件。这里假设它已经存在:

dependencies:
  flutter:
    sdk: flutter
  user_command: ^0.1.0  # 假设的版本号

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

2. 插件使用示例

接下来,在你的Flutter应用中导入并使用这个插件。下面是一个简单的示例,展示如何执行一个自定义命令并处理结果。

import 'package:flutter/material.dart';
import 'package:user_command/user_command.dart';  // 假设的导入路径

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('User Command Example'),
        ),
        body: Center(
          child: UserCommandExample(),
        ),
      ),
    );
  }
}

class UserCommandExample extends StatefulWidget {
  @override
  _UserCommandExampleState createState() => _UserCommandExampleState();
}

class _UserCommandExampleState extends State<UserCommandExample> {
  String _result = '';

  void _executeCommand() async {
    // 假设的插件方法,用于执行自定义命令
    String command = 'echo Hello from user_command!';
    try {
      String result = await UserCommand.run(command);
      setState(() {
        _result = result;
      });
    } catch (e) {
      setState(() {
        _result = 'Error: ${e.message}';
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        ElevatedButton(
          onPressed: _executeCommand,
          child: Text('Execute Command'),
        ),
        SizedBox(height: 20),
        Text(
          _result,
          style: TextStyle(fontSize: 18),
        ),
      ],
    );
  }
}

3. 插件实现(假设)

由于 user_command 是一个假设的插件,这里提供一个简化的插件实现思路,仅用于说明。实际的插件实现会复杂得多,并且需要处理平台通道(Platform Channels)来与原生代码进行通信。

3.1. iOS 实现(假设)

ios/Classes/UserCommandPlugin.swift 中:

import Flutter

public class UserCommandPlugin: NSObject, FlutterPlugin {
  public static func register(with registrar: FlutterRegistrar) {
    let channel = FlutterMethodChannel(name: "user_command", binaryMessenger: registrar.messenger())
    let instance = UserCommandPlugin()
    registrar.addMethodCallDelegate(instance, channel: channel)
  }

  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    if call.method == "run" {
      guard let arguments = call.arguments as? String else {
        result(FlutterError(code: "Invalid argument", message: "Expected a string argument", details: nil))
        return
      }
      
      let task = Process()
      task.launchPath = "/bin/bash"
      task.arguments = ["-c", arguments]
      
      let pipe = Pipe()
      task.standardOutput = pipe
      task.standardError = pipe
      
      task.launch()
      
      let data = pipe.fileHandleForReading.readDataToEndOfFile()
      let output = String(data: data, encoding: .utf8) ?? ""
      
      result(output)
    } else {
      result(FlutterMethodNotImplementedError(methodName: call.method))
    }
  }
}

3.2. Android 实现(假设)

android/src/main/java/com/example/user_command/UserCommandPlugin.java 中:

package com.example.user_command;

import android.os.AsyncTask;
import android.os.Bundle;
import androidx.annotation.NonNull;

import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;

public class UserCommandPlugin implements FlutterPlugin, MethodCallHandler, ActivityAware {
    private MethodChannel channel;
    private ActivityPluginBinding activityBinding;

    @Override
    public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
        channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "user_command");
        channel.setMethodCallHandler(this);
    }

    @Override
    public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
        if (call.method.equals("run")) {
            String command = call.argument("command");
            if (command != null) {
                new ExecuteCommandTask(result).execute(command);
            } else {
                result.error("INVALID_ARGUMENT", "Expected a command argument", null);
            }
        } else {
            result.notImplemented();
        }
    }

    @Override
    public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
        channel.setMethodCallHandler(null);
    }

    @Override
    public void onAttachedToActivity(ActivityPluginBinding binding) {
        this.activityBinding = binding;
    }

    @Override
    public void onDetachedFromActivityForConfigChanges() {
        this.activityBinding = null;
    }

    @Override
    public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {
        this.activityBinding = binding;
    }

    @Override
    public void onDetachedFromActivity() {
        this.activityBinding = null;
    }

    private static class ExecuteCommandTask extends AsyncTask<String, Void, String> {
        private Result result;

        public ExecuteCommandTask(Result result) {
            this.result = result;
        }

        @Override
        protected String doInBackground(String... params) {
            String command = params[0];
            try {
                Process process = Runtime.getRuntime().exec(command);
                process.waitFor();
                java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(process.getInputStream()));
                StringBuilder stringBuilder = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) {
                    stringBuilder.append(line).append("\n");
                }
                return stringBuilder.toString();
            } catch (Exception e) {
                return "Error: " + e.getMessage();
            }
        }

        @Override
        protected void onPostExecute(String resultString) {
            result.success(resultString);
        }
    }
}

总结

以上代码展示了如何在Flutter中使用一个假设的 user_command 插件来执行自定义命令。实际的插件实现会依赖于具体的平台通道代码和原生平台(iOS和Android)的实现细节。希望这个示例能帮助你理解如何在Flutter中集成和使用自定义命令执行插件。

回到顶部