Flutter创建自定义的软键盘

参考代码:

Flutter - Create Custom Keyboard Examples

本文贴出的代码实现了一个输入十六进制数据的键盘:

(1)支持长按退格键连续删除字符;

(2)可通过退格键删除选中的文字;

(3)监听文本变化(包括粘贴剪切导致的变化)。

hex_keyboard.dart

Dart 复制代码
import 'dart:async';

import 'package:flutter/material.dart';

class HexKeyboard extends StatefulWidget {
  final TextEditingController controller;
  final void Function() onDone;
  final void Function(String) onTextChanged;

  const HexKeyboard({
    super.key,
    required this.controller,
    required this.onDone,
    required this.onTextChanged,
  });

  @override
  State<HexKeyboard> createState() => _HexKeyboardState();
}

class _HexKeyboardState extends State<HexKeyboard> {
  late TextEditingController _controller;

  final Widget _horizontalPadding = const SizedBox(width: 1.0);
  final Widget _verticalPadding = const SizedBox(height: 1.0);
  Timer? _timer;

  @override
  void initState() {
    super.initState();
    _controller = widget.controller;
  }

  void _handleType(String text) {
    int position = _controller.selection.base.offset;
    var value = _controller.text;

    if (value.isEmpty) {
      _controller.text = text;
    } else {
      _controller.text = value.substring(0, position) +
          text +
          value.substring(position, value.length);
    }

    _controller.selection =
        TextSelection.fromPosition(TextPosition(offset: position + 1));
    widget.onTextChanged(_controller.text);
  }

  void _handleBackspace() {
    final value = _controller.text;

    if (value.isNotEmpty) {
      final start = _controller.selection.start;
      final end = _controller.selection.end;
      print("selection.start=$start, selection.end=$end");
      final int offset;
      if(end > 0) {
        if(start == end) {
          _controller.text = value.substring(0, start - 1) +
              value.substring(start, value.length);
          offset = start - 1;
        } else {
          _controller.text = value.substring(0, start) +
              value.substring(end, value.length);
          offset = start;
        }
        _controller.selection =
            TextSelection.fromPosition(TextPosition(offset: offset));
        widget.onTextChanged(_controller.text);
      }
    }
  }

  Widget _buildButton(String text,
      {VoidCallback? onPressed,
      VoidCallback? onLongPressStart,
      VoidCallback? onLongPressUp}) {
    return Expanded(
      child: Container(
        color: Colors.white,
        child: GestureDetector(
          onLongPressStart: (e) {
            onLongPressStart?.call();
          },
          onLongPressUp: onLongPressUp,
          child: TextButton(
            onPressed: onPressed ?? () => _handleType(text),
            child: Text(
              text,
              style: const TextStyle(color: Colors.black, fontSize: 16),
            ),
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return _buildButtonKeyboard();
  }

  Widget _buildButtonRow(String key1, String key2, String key3) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        _horizontalPadding,
        _buildButton(key1),
        _horizontalPadding,
        _buildButton(key2),
        _horizontalPadding,
        _buildButton(key3),
        _horizontalPadding,
      ],
    );
  }

  Widget _buildButtonKeyboard() {
    return Container(
      color: const Color(0xffdddddd),
      child: Column(
        children: [
          _verticalPadding,
          _buildButtonRow('A', 'B', 'C'),
          _verticalPadding,
          _buildButtonRow('D', 'E', 'F'),
          _verticalPadding,
          _buildButtonRow('1', '2', '3'),
          _verticalPadding,
          _buildButtonRow('4', '5', '6'),
          _verticalPadding,
          _buildButtonRow('7', '8', '9'),
          _verticalPadding,
          Row(
            children: [
              _horizontalPadding,
              _buildButton(
                '⌫',
                onPressed: _handleBackspace,
                onLongPressStart: () {
                  _timer =
                      Timer.periodic(const Duration(milliseconds: 50), (timer) {
                    _handleBackspace();
                  });
                },
                onLongPressUp: () {
                  _timer?.cancel();
                },
              ),
              _horizontalPadding,
              _buildButton('0'),
              _horizontalPadding,
              _buildButton(
                'Done',
                onPressed: widget.onDone,
              ),
              _horizontalPadding,
            ],
          ),
          _verticalPadding,
        ],
      ),
    );
  }
}

hex_input_screen.dart

Dart 复制代码
import 'package:flutter/material.dart';

class HexInputScreen extends StatefulWidget {
  final String text;

  const HexInputScreen({super.key, required this.text});

  @override
  State<HexInputScreen> createState() => _HexInputScreenState();
}

class _HexInputScreenState extends State<HexInputScreen> {
  late TextEditingController _controller;
  final FocusNode _focus = FocusNode();
  late ValueNotifier<bool> _focusValueNotifier;
  int _byteCount = 0;

  int _toByteCount(String hex) {
    return hex.length % 2 == 0 ? hex.length ~/ 2 : hex.length ~/ 2 + 1;
  }

  void _onTextChanged(String text) {
    //更新字节数
    setState(() {
      _byteCount = _toByteCount(text);
    });
  }

  @override
  void initState() {
    _controller = TextEditingController(text: widget.text);
    _focus.addListener(_handleFocusChange);
    _focusValueNotifier = ValueNotifier<bool>(_focus.hasFocus);
    _focus.requestFocus();
    setState(() {
      _byteCount = widget.text.length;
    });
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
    _focus.removeListener(_handleFocusChange);
    _focus.dispose();
  }

  void _handleFocusChange() {
    _focusValueNotifier.value = _focus.hasFocus;
  }

  void _onDone() {
    print(_controller.text);
    Navigator.pop(context, _controller.text);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('HEX' /*, style: TextStyle(color: Colors.white)*/),
        // backgroundColor: Colors.black,
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.start,
        children: [
          const SizedBox(height: 10),
          Text('已输入 $_byteCount 字节'),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: TextField(
              decoration: const InputDecoration(
                border: OutlineInputBorder(
                  borderSide: BorderSide(
                    color: Colors.grey,
                    width: 1,
                  ),
                ),
              ),
              controller: _controller,
              keyboardType: TextInputType.none,
              focusNode: _focus,
              maxLines: 12,
              maxLength: 1024,
              onChanged: _onTextChanged,//这里监听粘贴剪切导致的变化
            ),
          ),
          const Spacer(),
          ListenableBuilder(
            listenable: _focusValueNotifier,
            builder: (BuildContext context, Widget? child) {
              return _focus.hasFocus
                  ? HexKeyboard(
                      controller: _controller,
                      onDone: _onDone,
                      onTextChanged: _onTextChanged,//这里监听自定义键盘导致的变化
                    )
                  : Container();
            },
          ),
        ],
      ),
    );
  }
}
相关推荐
Geek_Vison6 小时前
保险APP已经用了Flutter,还能接入小程序吗?
flutter·小程序·uni-app·mpaas·小程序容器
FungLeo6 小时前
成为全栈·Flutter App 篇·go_router 与四 Tab App Shell:保留导航状态、深链和登录回跳
flutter·移动开发·成为全栈·go_router·路由设计·深链接
m0_738185827 小时前
Flutter 鸿蒙化实战:screen_protector 适配 OpenHarmony,防截屏/防泄露
flutter·华为·harmonyos·鸿蒙
梦想的颜色12 小时前
【编程实战】AI 时代 APP 开发全栈硬核指南:技术选型 + AI 架构 + 模型落地全维度决策
python·flutter·react native·react.js·ai·桌面应用·milvus
yuanlaile1 天前
2026年全新Flutter 跨平台开发全栈学习资料整理
flutter·getx·鸿蒙 next·flutter开发鸿蒙·flutter入门实战·flutter全栈
恋猫de小郭2 天前
Genkit Dart 1.0 发布,Flutter 原生的 AI Agent 终于完整了
android·前端·flutter
第10086个小白2 天前
flutter桌面端打包方式(Inno Setup Compiler)版本
前端·flutter
陆断枫2 天前
Flutter 3.47 动画引擎盘点:Impeller 全平台,和那些"新特性"
flutter
程序员老刘2 天前
Flet 1.0 思路很好,可惜来晚了
flutter·ai编程·客户端
m0_738185823 天前
Flutter 鸿蒙化实战:qr_code_scanner_plus 适配 OpenHarmony,二维码扫描
flutter·华为·harmonyos·鸿蒙