Flutter数学键盘插件math_keyboard的使用

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

Flutter数学键盘插件math_keyboard的使用

简介

math_keyboard 是一个 Flutter 插件,允许用户通过自定义的数学键盘编辑数学表达式。它支持多种高级功能,如物理键盘输入、焦点管理、表单支持等,并且完全集成在 Flutter 中,无需使用插件或 WebView。

特性

  • 使用自定义的屏幕键盘编辑数学表达式
  • 支持通过物理键盘输入(包括函数和常量的快捷键)
  • 支持数字模式和表达式模式
  • 高级运算符和三角函数(如 sqrtlnsin 等)
  • 视图内边距支持(例如,屏幕键盘覆盖时会推高 Scaffoldbody
  • 完整的焦点树集成:与普通文本字段、手动 FocusNode、Tab 键等兼容
  • 自动聚焦支持
  • 表单字段支持
  • 基于区域设置的小数分隔符
  • 转换 TeX 表达式

你可以查看所有特性在 演示应用 中的实际效果。

使用方法

安装

要使用此插件,请按照 安装指南 进行操作。

基本实现

最简单的方式是在你的应用中添加一个 MathField。这与 Flutter 的 TextField 类似,甚至支持相同的 InputDecoration 装饰功能!

@override
Widget build(BuildContext context) {
  return MathField(
    keyboardType: MathKeyboardType.expression, // 指定键盘类型(表达式或数字)
    variables: const ['x', 'y', 'z'], // 指定用户可以使用的变量(仅在表达式模式下)
    decoration: const InputDecoration(), // 使用熟悉的 InputDecoration 装饰输入字段
    onChanged: (String value) {}, // 响应输入字段的变化
    onSubmitted: (String value) {}, // 响应用户提交输入
    autofocus: true, // 启用或禁用输入字段的自动聚焦
  );
}

现在,点击数学字段(或通过焦点树聚焦)将自动打开数学键盘并开始接受桌面的物理键盘输入。

视图内边距

数学键盘的一个非常有用的功能是它尽可能地模仿常规软件键盘的行为。其中一部分是向 MediaQuery 报告其大小,形式为 视图内边距。这将无缝集成到 Scaffold 和现有软件键盘的视图内边距报告中。

要使用此功能,只需确保包含 MathFieldScaffold 被包裹在 MathKeyboardViewInsets 中:

@override
Widget build(BuildContext context) {
  return MathKeyboardViewInsets(
    child: Scaffold(
      // ...
    ),
  );
}

请参阅 文档 了解更高级的用法。

此外,该包提供了一些方便的方法来检测键盘是否显示:

  • MathKeyboardViewInsetsQuery.mathKeyboardShowingIn(context),报告当前 context 中是否有数学键盘打开。
  • MathKeyboardViewInsetsQuery.keyboardShowingIn(context),报告当前 context 中是否有任何键盘打开。注意,此功能还提供了对常规软件键盘的高级支持。

表单支持

要在 Flutter 表单 中使用 MathField,可以使用 MathFormField 替代普通的 MathField。这与常规字段类似,但具有扩展功能,行为类似于框架中的 TextFormField。请参阅后者以获取高级文档。

@override
Widget build(BuildContext context) {
  return MathFormField(
    // ...
  );
}

自定义控制器

你可以指定一个自定义的 MathFieldEditingController。这允许你清除所有输入等操作。确保在适当的时候释放控制器。

class FooState extends State<FooStatefulWidget> {
  late final _controller = MathFieldEditingController();

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void _onTapClear() {
    _controller.clear();
  }

  @override
  Widget build(BuildContext context) {
    return MathField(
      controller: _controller,
      decoration: InputDecoration(
        suffix: MouseRegion(
          cursor: MaterialStateMouseCursor.clickable,
          child: GestureDetector(
            onTap: _onTapClear,
            child: const Icon(
              Icons.highlight_remove_rounded,
              color: Colors.grey,
            ),
          ),
        ),
      ),
    );
  }
}

自定义焦点节点

如果你希望自行管理焦点,可以指定自己的 FocusNode。这与其他基于焦点的组件(如 TextField)类似。注意,即使提供了自定义焦点节点,autofocus 仍然有效。

class FooState extends State<FooStatefulWidget> {
  late final _focusNode = FocusNode(debugLabel: 'Foo');

  @override
  void dispose() {
    _focusNode.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MathField(
      focusNode: _focusNode,
    );
  }
}

小数分隔符

注意,并非所有国家都使用点 . 作为小数分隔符(参见 参考)。如果通过 Localizations.localeOf 获取的区域设置使用逗号 , 作为小数分隔符,则数学字段中的分隔符以及键盘上的符号都会相应调整。否则,使用点 .。你可以使用 Localizations.override 小部件(将 MathField 包裹在其中)来覆盖区域设置。

注意,物理键盘输入始终接受 .,

数学表达式

要将数学键盘返回的 TeX 字符串转换为数学表达式,可以使用提供的 TeXParser

final mathExpression = TeXParser(texString).parse();

对于相反的操作,即将数学 Expression 转换为 TeX,可以使用提供的 convertMathExpressionToTeXNode

final texNode = convertMathExpressionToTeXNode(expression);

注意,这返回一个内部的 TeXNode 格式,你可以将其转换为 TeX 字符串:

final texString = texNode.buildTexString();

示例代码

以下是一个完整的示例代码,展示了如何在 Flutter 应用中使用 math_keyboard 插件:

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:math_keyboard/math_keyboard.dart';

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

/// 示例应用,展示如何使用 `math_keyboard` 插件。
class ExampleApp extends StatelessWidget {
  /// 创建一个 [ExampleApp] 小部件。
  const ExampleApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Math Keyboard Demo',
      supportedLocales: const [
        Locale('en', 'US'),
        // 提供另一个支持的区域设置(例如 "de_DE"),允许在模拟器上切换区域设置,
        // 并查看不同的小数分隔符。只有在 supportedLocales 中声明的区域设置才会被 Localizations.localeOf 返回。
        // 如果你不想使用逗号作为小数分隔符,不要提供使用逗号作为小数分隔符的支持区域设置。
        Locale('de', 'DE'),
      ],
      localizationsDelegates: const [
        GlobalMaterialLocalizations.delegate,
      ],
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const DemoPage(),
    );
  }
}

/// 展示如何使用 `math_keyboard` 插件的页面小部件。
class DemoPage extends StatefulWidget {
  /// 创建一个 [DemoPage] 小部件。
  const DemoPage({Key? key}) : super(key: key);

  @override
  _DemoPageState createState() => _DemoPageState();
}

class _DemoPageState extends State<DemoPage> {
  var _currentIndex = 0;

  @override
  Widget build(BuildContext context) {
    Widget child;
    if (_currentIndex == 0) {
      child = const _MathFieldTextFieldExample();
    } else if (_currentIndex == 1) {
      child = const Center(
        child: Padding(
          padding: EdgeInsets.all(16),
          child: Text(
            'The math keyboard should be automatically dismissed when '
            'switching to this page.',
            textAlign: TextAlign.center,
          ),
        ),
      );
    } else {
      child = const _ClearableAutofocusExample();
    }

    return MathKeyboardViewInsets(
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Math keyboard demo'),
        ),
        body: Column(
          children: [
            Expanded(
              child: child,
            ),
            // 我们在这里插入底部导航栏,而不是使用 bottomNavigationBar 参数,
            // 以便使其固定在键盘上方。
            BottomNavigationBar(
              currentIndex: _currentIndex,
              onTap: (index) {
                setState(() {
                  _currentIndex = index;
                });
              },
              items: const [
                BottomNavigationBarItem(
                  label: 'Fields',
                  icon: Icon(Icons.text_fields_outlined),
                ),
                BottomNavigationBarItem(
                  label: 'Empty',
                  icon: Icon(Icons.hourglass_empty_outlined),
                ),
                BottomNavigationBarItem(
                  label: 'Autofocus',
                  icon: Icon(Icons.auto_awesome),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

/// 显示一个包含不同数学字段和文本字段的示例列的小部件。
class _MathFieldTextFieldExample extends StatelessWidget {
  /// 构造一个 [_MathFieldTextFieldExample] 小部件。
  const _MathFieldTextFieldExample({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: ListView(
        children: [
          const Padding(
            padding: EdgeInsets.all(16),
            child: TextField(),
          ),
          Padding(
            padding: const EdgeInsets.all(16),
            child: MathField(
              variables: const ['a', 's', 'c'],
              onChanged: (value) {
                String expression;
                try {
                  expression = '${TeXParser(value).parse()}';
                } catch (_) {
                  expression = 'invalid input';
                }

                print('input expression: $value\n'
                    'converted expression: $expression');
              },
            ),
          ),
          const Padding(
            padding: EdgeInsets.all(16),
            child: MathField(
              keyboardType: MathKeyboardType.numberOnly,
            ),
          ),
        ],
      ),
    );
  }
}

/// 显示一个可以清除外部输入并自动接收焦点的数学字段示例列的小部件。
class _ClearableAutofocusExample extends StatefulWidget {
  /// 构造一个 [_ClearableAutofocusExample] 小部件。
  const _ClearableAutofocusExample({Key? key}) : super(key: key);

  @override
  _ClearableAutofocusExampleState createState() =>
      _ClearableAutofocusExampleState();
}

class _ClearableAutofocusExampleState
    extends State<_ClearableAutofocusExample> {
  late final _controller = MathFieldEditingController();

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: ListView(
        children: [
          Padding(
            padding: const EdgeInsets.all(16),
            child: MathField(
              autofocus: true,
              controller: _controller,
              decoration: InputDecoration(
                suffix: MouseRegion(
                  cursor: SystemMouseCursors.click,
                  child: GestureDetector(
                    onTap: _controller.clear,
                    child: const Icon(
                      Icons.highlight_remove_rounded,
                      color: Colors.grey,
                    ),
                  ),
                ),
              ),
            ),
          ),
          const Padding(
            padding: EdgeInsets.all(16),
            child: Text(
              'The math field on this tab should automatically receive '
              'focus.',
              textAlign: TextAlign.center,
            ),
          ),
        ],
      ),
    );
  }
}

以上代码展示了如何在 Flutter 应用中使用 math_keyboard 插件的基本用法和一些高级功能。希望对你有所帮助!


更多关于Flutter数学键盘插件math_keyboard的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter数学键盘插件math_keyboard的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter应用中使用math_keyboard插件的一个代码示例。这个插件允许你集成一个数学键盘到你的应用中,方便用户输入数学表达式。

首先,确保你已经在pubspec.yaml文件中添加了math_keyboard依赖:

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

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

接下来,你可以在你的Flutter应用中使用这个键盘。下面是一个简单的示例,展示如何集成和使用math_keyboard

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

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

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

class MathKeyboardDemo extends StatefulWidget {
  @override
  _MathKeyboardDemoState createState() => _MathKeyboardDemoState();
}

class _MathKeyboardDemoState extends State<MathKeyboardDemo> {
  final TextEditingController _controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Math Keyboard Demo'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            TextField(
              controller: _controller,
              decoration: InputDecoration(
                border: OutlineInputBorder(),
                labelText: 'Enter Math Expression',
              ),
              keyboardType: TextInputType.multiline,
              maxLines: null,
              expands: true,
            ),
            SizedBox(height: 16),
            ElevatedButton(
              onPressed: () {
                showCupertinoModalPopup<void>(
                  context: context,
                  builder: (BuildContext context) {
                    return CupertinoActionSheet(
                      title: Text('Select Action'),
                      actions: <Widget>[
                        CupertinoActionSheetAction(
                          child: Text('Show Math Keyboard'),
                          onPressed: () {
                            Navigator.pop(context);
                            _showMathKeyboard(context);
                          },
                        ),
                      ],
                      cancelButton: CupertinoActionSheetAction(
                        isDefaultAction: true,
                        child: Text('Cancel'),
                        onPressed: () {
                          Navigator.pop(context);
                        },
                      ),
                    );
                  },
                );
              },
              child: Text('Show Keyboard'),
            ),
          ],
        ),
      ),
    );
  }

  Future<void> _showMathKeyboard(BuildContext context) async {
    final String result = await showDialog<String>(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: Text('Math Keyboard'),
          content: MathKeyboard(
            onResult: (String value) {
              Navigator.pop(context, value);
            },
          ),
          actions: <Widget>[
            TextButton(
              onPressed: () {
                Navigator.pop(context, null);
              },
              child: Text('Cancel'),
            ),
            TextButton(
              onPressed: () {
                // This would normally trigger the onResult callback with the current text
                // But since we can't directly access the internal state of the MathKeyboard,
                // we assume the user will handle the result through the onResult callback.
                // Here, we just close the dialog for demonstration purposes.
                Navigator.pop(context); // Normally, this would be called inside onResult
              },
              child: Text('Done'),
            ),
          ],
        );
      },
    );

    if (result != null) {
      setState(() {
        _controller.text = result;
      });
    }
  }
}

在这个示例中,我们创建了一个简单的Flutter应用,其中包含一个TextField用于显示用户输入的数学表达式。通过点击“Show Keyboard”按钮,我们可以显示一个自定义的AlertDialog,其中包含MathKeyboard。当用户完成输入并点击“Done”按钮(尽管在这个示例中“Done”按钮只是关闭了对话框,实际使用中你应该在onResult回调中处理它),数学表达式会被插入到TextField中。

请注意,MathKeyboard的实际使用可能需要根据插件的最新文档进行调整,因为插件的API可能会随版本更新而变化。这个示例仅用于展示基本的集成方法。

回到顶部