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();
            },
          ),
        ],
      ),
    );
  }
}
相关推荐
千码君20169 小时前
Trae:一些关于flutter和 go前后端开发构建的分享
android·flutter·gradle·android-studio·trae·vibe code
maaath11 小时前
【maaath】Flutter for OpenHarmony 手表配饰应用实战开发
flutter·华为·harmonyos
maaath11 小时前
【maaath】Flutter for OpenHarmony 跨平台计算器应用开发实践
flutter·华为·harmonyos
maaath17 小时前
【maaath】Flutter for OpenHarmony 闹钟时钟应用开发实战
flutter·华为·harmonyos
maaath17 小时前
【maaath】Flutter for OpenHarmony 短信管理应用实战
flutter·华为·harmonyos
maaath18 小时前
【maaath】Flutter for OpenHarmony打造跨平台便签备忘录应用
flutter·华为·harmonyos
千码君201618 小时前
flutter:与Android Studio模拟器的调试分享
android·flutter
xmdy586619 小时前
Flutter+开源鸿蒙实战|智联邻里Day8 Lottie动画集成+url_launcher跳转拨号+个人中心完善+全局UI统一
flutter·开源·harmonyos
liulian09161 天前
Flutter for OpenHarmony 跨平台开发:颜色选择器功能实战指南
flutter
liulian09161 天前
Flutter for OpenHarmony 跨平台开发:BMI计算器功能实战指南
flutter·华为