Flutter自定义键盘布局插件super_keyboard_layout的使用

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

Flutter自定义键盘布局插件super_keyboard_layout的使用

特性

  • 支持macOS、Windows和Linux平台。
  • 允许根据当前键盘布局在物理键、逻辑键和平台特定键代码之间进行映射。
  • 提供键盘布局更改的通知。

示例图片

开始使用

super_keyboard_layout 插件内部使用了Rust来实现低级别的平台特定功能。如果你没有安装Rust,插件会自动下载预编译的二进制文件。如果你想从源代码编译Rust代码,可以通过rustup安装Rust。

安装Rust

对于macOS或Linux,执行以下命令:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

对于Windows,可以使用Rust Installer

如果你已经安装了Rust,请确保更新到最新版本:

rustup update

第一次构建可能需要一些时间,因为需要安装所需的Rust目标和其他依赖项。

使用方法

import 'package:super_keyboard_layout/super_keyboard_layout.dart';

void main() async {
    final manager = await KeyboardLayoutManager.instance();
    if (manager.supported) {
        // 运行在支持的平台上
        manager.onLayoutChanged.addListener(() {
            // 键盘布局已更改
            print('Keyboard layout changed');
        });
    }

    final layout = manager.currentLayout;

    // 获取当前布局下带有shift键的物理键1对应的逻辑键
    final logicalKey = layout.getLogicalKeyForPhysicalKey(PhysicalKeyboardKey.digit1, shift: true);

    // 获取逻辑键对应的物理键
    final physicalKey = layout.getPhysicalKeyForLogicalKey(LogicalKeyboardKey.keyA);

    // 获取逻辑键或物理键的平台特定键代码
    final platformCode = layout.getPlatformKeyCode(PhysicalKeyboardKey.digit1);
}

运行示例

示例项目位于 super_keyboard_layout/example。你可以通过以下步骤运行示例:

flutter pub global activate melos # 如果你还没有安装melos
git clone https://github.com/superlistapp/super_native_extensions.git
cd super_native_extensions
melos bootstrap

之后,可以在VSCode中打开该文件夹并运行 super_keyboard_layout 启动配置。

示例代码

下面是一个完整的示例应用,展示如何使用 super_keyboard_layout 插件:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:super_keyboard_layout/super_keyboard_layout.dart';

late KeyboardLayoutManager _keyboardLayoutManager;

void main() async {
  _keyboardLayoutManager = await KeyboardLayoutManager.instance();
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Super Keyboard Layout Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Super Keyboard Layout Example'),
    );
  }
}

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

  @override
  State<LayoutDemoWidget> createState() => _LayoutDemoWidgetState();
}

class _LayoutDemoWidgetState extends State<LayoutDemoWidget> {
  late FocusNode _focusNode;
  PhysicalKeyboardKey? _lastKey;

  @override
  void initState() {
    super.initState();
    _focusNode = FocusNode(debugLabel: 'Example focus node');
    _focusNode.onKeyEvent = _onKeyEvent;
    _focusNode.requestFocus();
    _keyboardLayoutManager.onLayoutChanged.addListener(_layoutChanged);
  }

  KeyEventResult _onKeyEvent(FocusNode node, KeyEvent event) {
    if (event is KeyDownEvent) {
      setState(() {
        _lastKey = event.physicalKey;
      });
    }
    return KeyEventResult.handled;
  }

  @override
  void dispose() {
    _focusNode.dispose();
    _keyboardLayoutManager.onLayoutChanged.removeListener(_layoutChanged);
    super.dispose();
  }

  void _layoutChanged() {
    setState(() {});
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(
        content: Text("Keyboard layout has changed."),
        duration: Duration(milliseconds: 1500),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(10),
      decoration: BoxDecoration(
        border: Border.all(
            color: _focusNode.hasFocus ? Colors.red : Colors.grey, width: 2),
      ),
      child: FocusableActionDetector(
        onFocusChange: (_) {
          setState(() {});
        },
        focusNode: _focusNode,
        child: GestureDetector(
          behavior: HitTestBehavior.opaque,
          onTapDown: (details) {
            _focusNode.requestFocus();
          },
          child: _lastKey == null
              ? const Text('Press any key')
              : Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text("Pressed physical key:",
                        style: TextStyle(fontWeight: FontWeight.bold)),
                    Text(_lastKey!.toString()),
                    const SizedBox(
                      height: 10,
                    ),
                    const Text('Logical key for current keyboard layout:',
                        style: TextStyle(fontWeight: FontWeight.bold)),
                    Text(_keyboardLayoutManager.currentLayout
                            .getLogicalKeyForPhysicalKey(_lastKey!)
                            ?.toString() ??
                        'null'),
                    const Text(
                        'Logical key for current keyboard layout (with shift):',
                        style: TextStyle(fontWeight: FontWeight.bold)),
                    Text(_keyboardLayoutManager.currentLayout
                            .getLogicalKeyForPhysicalKey(_lastKey!, shift: true)
                            ?.toString() ??
                        'null'),
                    const Text(
                        'Logical key for current keyboard layout (with alt):',
                        style: TextStyle(fontWeight: FontWeight.bold)),
                    Text(_keyboardLayoutManager.currentLayout
                            .getLogicalKeyForPhysicalKey(_lastKey!, alt: true)
                            ?.toString() ??
                        'null'),
                    const SizedBox(
                      height: 20,
                    ),
                    const Text(
                      'Because this functionality is intended for '
                      'keyboard shortcuts, only ASCII capable keyboard '
                      'layouts are supported.\nOn Linux switching between '
                      'keyboards is only recognized after pressing a key from'
                      ' new layout.',
                      style: TextStyle(fontSize: 11.5),
                    ),
                  ],
                ),
        ),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              if (!_keyboardLayoutManager.supported)
                const Text(
                  'KeyboardLayoutManager is not supported on this platform.',
                ),
              if (_keyboardLayoutManager.supported) const LayoutDemoWidget(),
            ],
          ),
        ),
      ),
    );
  }
}

额外信息

此插件仍处于开发的早期阶段且具有实验性质。欢迎提交PRsbug reports


更多关于Flutter自定义键盘布局插件super_keyboard_layout的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter自定义键盘布局插件super_keyboard_layout的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter项目中使用super_keyboard_layout插件来自定义键盘布局的示例代码。假设你已经将super_keyboard_layout插件添加到了你的pubspec.yaml文件中。

首先,确保你的pubspec.yaml文件中包含以下依赖:

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

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

接下来,我们创建一个自定义键盘布局的示例。以下是一个完整的Flutter应用程序代码,它展示了如何使用super_keyboard_layout插件。

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

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

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

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final TextEditingController _controller = TextEditingController();
  final FocusNode _focusNode = FocusNode();

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Custom Keyboard Layout Demo'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            TextField(
              controller: _controller,
              focusNode: _focusNode,
              decoration: InputDecoration(
                border: OutlineInputBorder(),
                labelText: 'Enter text here',
              ),
              keyboardType: TextInputType.multiline, // For multiline input to see the effect better
              inputFormatters: [
                FilteringTextInputFormatter.allowed(RegExp(r'[a-zA-Z0-9 ]')),
              ],
            ),
            SizedBox(height: 16),
            ElevatedButton(
              onPressed: () {
                // Show the custom keyboard
                _showCustomKeyboard();
              },
              child: Text('Show Custom Keyboard'),
            ),
          ],
        ),
      ),
    );
  }

  void _showCustomKeyboard() {
    showModalBottomSheet<void>(
      context: context,
      isScrollControlled: true,
      builder: (BuildContext context) {
        return Container(
          height: 300,
          color: Colors.white,
          child: CustomKeyboardLayout(
            onKeyPressed: (String key) {
              _controller.value = _controller.value.copyWith(
                text: _controller.text + key,
                selection: TextSelection.collapsed(
                  offset: _controller.text.length,
                ),
                composing: TextRange.empty,
              );

              // Hide the keyboard after pressing a key
              Navigator.of(context).pop();
            },
            keyLayout: [
              [
                {'key': 'Q', 'color': Colors.black},
                {'key': 'W', 'color': Colors.black},
                {'key': 'E', 'color': Colors.black},
                {'key': 'R', 'color': Colors.black},
                {'key': 'T', 'color': Colors.black},
                {'key': 'Y', 'color': Colors.black},
                {'key': 'U', 'color': Colors.black},
                {'key': 'I', 'color': Colors.black},
                {'key': 'O', 'color': Colors.black},
                {'key': 'P', 'color': Colors.black},
              ],
              [
                {'key': 'A', 'color': Colors.black},
                {'key': 'S', 'color': Colors.black},
                {'key': 'D', 'color': Colors.black},
                {'key': 'F', 'color': Colors.black},
                {'key': 'G', 'color': Colors.black},
                {'key': 'H', 'color': Colors.black},
                {'key': 'J', 'color': Colors.black},
                {'key': 'K', 'color': Colors.black},
                {'key': 'L', 'color': Colors.black},
              ],
              [
                {'key': 'Z', 'color': Colors.black},
                {'key': 'X', 'color': Colors.black},
                {'key': 'C', 'color': Colors.black},
                {'key': 'V', 'color': Colors.black},
                {'key': 'B', 'color': Colors.black},
                {'key': 'N', 'color': Colors.black},
                {'key': 'M', 'color': Colors.black},
                {'key': ' ', 'color': Colors.grey}, // Space bar
              ],
            ],
          ),
        );
      },
    );
  }
}

// CustomKeyboardLayout widget is assumed to be provided by the super_keyboard_layout plugin
// or you may need to implement it based on the plugin's documentation.
// Here's a placeholder for what it might look like:
class CustomKeyboardLayout extends StatelessWidget {
  final List<List<Map<String, dynamic>>> keyLayout;
  final ValueChanged<String> onKeyPressed;

  const CustomKeyboardLayout({
    Key key,
    @required this.keyLayout,
    @required this.onKeyPressed,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 10, // Adjust based on your layout
        crossAxisSpacing: 4.0,
        mainAxisSpacing: 4.0,
      ),
      itemCount: keyLayout.expand((row) => row).length,
      itemBuilder: (context, index) {
        final keyData = keyLayout.expanded.toList()[index];
        return GestureDetector(
          onTap: () => onKeyPressed(keyData['key']),
          child: Container(
            decoration: BoxDecoration(
              color: keyData['color'] ?? Colors.grey,
              borderRadius: BorderRadius.circular(8),
            ),
            alignment: Alignment.center,
            child: Text(
              keyData['key'],
              style: TextStyle(fontSize: 20),
            ),
          ),
        );
      },
    );
  }
}

请注意,上面的CustomKeyboardLayout类是一个假设的实现,因为实际的super_keyboard_layout插件可能提供了不同的API来创建自定义键盘布局。你需要参考该插件的文档来调整代码以匹配其实际的API。

此外,上面的代码示例中,当点击自定义键盘上的键时,键盘会自动隐藏。你可能需要根据实际需求调整这一行为。

务必查阅super_keyboard_layout插件的官方文档,以确保你使用的是最新和最准确的API。

回到顶部