Flutter创建自定义的软键盘

news2024/12/26 23:15:01

参考代码:

Flutter - Create Custom Keyboard Examples

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

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

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

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

hex_keyboard.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

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();
            },
          ),
        ],
      ),
    );
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1634174.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《HelloGitHub》第 97 期

兴趣是最好的老师&#xff0c;HelloGitHub 让你对编程感兴趣&#xff01; 简介 HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。 github.com/521xueweihan/HelloGitHub 这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等&#xff0c;涵盖多种编程语言 Python、…

护航智慧交通安全 | 聚铭精彩亮相2024交通科技创新及信创产品推广交流会

4月26日&#xff0c;石家庄希尔顿酒店内&#xff0c;河北省智能交通协会盛大举办2024年度交通科技创新及信创产品推广交流会。聚铭网络受邀参与&#xff0c;携旗下安全产品及解决方案精彩亮相&#xff0c;为智慧交通安全保驾护航。 为深化高速公路创新驱动发展战略&#xff0…

CUDA架构介绍与设计模式解析

文章目录 **CUDA**架构介绍与设计模式解析**1** CUDA 介绍CUDA发展历程CUDA主要特性CUDA系统架构CUDA应用场景编程语言支持CUDA计算过程线程层次存储层次 **2** CUDA 系统架构分层架构并行计算模式生产-消费者模式工作池模式异步编程模式 **3** CUDA 中的设计模式工厂模式策略模…

ChatGPT理论分析

ChatGPT "ChatGPT"是一个基于GPT&#xff08;Generative Pre-trained Transformer&#xff09;架构的对话系统。GPT 是一个由OpenAI 开发的自然语言处理&#xff08;NLP&#xff09;模型&#xff0c;它使用深度学习来生成文本。以下是对ChatGPT进行理论分析的几个主…

用户中心(上)

文章目录 企业做项目流程需求分析技术选型计划初始化项目数据库设计登录/注册⽤户管理&#xff08;仅管理员可⻅&#xff09; 初始化项目⼀、前端初始化1.下载nodejs2.Ant Design Pro相关问题1.前端项目启动时报错、无法启动&#xff1f;2.如何初始化前端项目&#xff1f;为什么…

SpringCloud之OpenFeign

学习笔记&#xff1a; 官网地址&#xff1a;https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#spring-cloud-feign 源码&#xff1a;https://github.com/spring-cloud/spring-cloud-openfeign 1、概念总结 OpenFeign是一个声明式的Web服务客户端…

[python数据处理系列] 深入理解与实践基于聚类的过采样与欠采样技术:以K-Means为例

目录 一、过采样介绍 (一)什么是过采样 (二)过采样的优点 (三)过采样的缺点 二、欠采样介绍 (一)什么是欠采样 (二)欠采样的优点 (三)欠采样的缺点 三、基于聚类的欠抽样方法(K-Means欠采样/KMeans-Undersampling) (一)KMeans欠采样原理及其步骤介绍 (二)为什么不采…

上海亚商投顾:沪指创年内新高 房地产板块掀涨停潮

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 三大指数昨日继续反弹&#xff0c;沪指盘中涨超1%&#xff0c;重返3100点上方&#xff0c;深成指涨超2%&#…

ArcGIS小技巧—坐标系匹配

坐标系&#xff1a;&#xff08;Coordinate System&#xff09;&#xff1a;在一些书籍和软件中也叫做空间参考&#xff0c;简单来说&#xff0c;有了坐标系&#xff0c;我们才能够用一个或多个“坐标值”来表达和确定空间位置。没有坐标系&#xff0c;坐标值就无从谈起&#x…

IP定位技术企业网络安全检测

随着信息技术的飞速发展&#xff0c;网络安全问题日益凸显&#xff0c;成为企业运营中不可忽视的一环。在众多网络安全技术中&#xff0c;IP定位技术以其独特的优势&#xff0c;为企业网络安全检测提供了强有力的支持。本文将深入探讨IP定位技术在企业网络安全检测中的应用及其…

在idea中连接mysql

IDE&#xff08;集成开发环境&#xff09;是一种软件应用程序&#xff0c;它为开发者提供编程语言的开发环境&#xff0c;通常集成了编码、编译、调试和运行程序的多种功能。一个好的IDE可以大幅提高开发效率&#xff0c;尤其是在进行大型项目开发时。IDE通常包括以下几个核心组…

Excel 批量获取sheet页名称,并创建超链接指向对应sheet页

参考资料 用GET.WORKBOOK函数实现excel批量生成带超链接目录且自动更新 目录 一. 需求二. 名称管理器 → 自定义获取sheet页名称函数三. 配合Index函数&#xff0c;获取所有的sheet页名称四. 添加超链接&#xff0c;指向对应的sheet页 一. 需求 ⏹有如下Excel表&#xff0c;需…

(三十一)第 5 章 数组和广义表(稀疏矩阵的三元组行逻辑链接的顺序存储表示实现)

1. 背景说明 2. 示例代码 1)errorRecord.h // 记录错误宏定义头文件#ifndef ERROR_RECORD_H #define ERROR_RECORD_H#include <stdio.h> #include <string.h> #include <stdint.h>// 从文件路径中提取文件名 #define FILE_NAME(X) strrchr(X, \\) ? strrch…

Servlet(三个核心API介绍以及错误排查)【二】

文章目录 一、三个核心API1.1 HttpServlet【1】地位【2】方法 1.2 HttpServletRequest【1】地位【2】方法【3】关于构造请求 1.3 HttpServletResponse【1】地位【2】方法 四、涉及状态码的错误排查&#xff08;404……&#xff09;五、关于自定义数据 ---- body或query String …

Linux网络抓包工具tcpdump是如何实现抓包的,在哪个位置抓包的?

Linux网络抓包工具tcpdump是如何实现抓包的&#xff0c;在哪个位置抓包的&#xff1f; 1. tcpdump抓包架构2. BPF介绍3. 从内核层面看tcpdump抓包流程3.1. 创建socket套接字3.2. 挂载BPF程序 4. 网络收包抓取5. 网络发包抓取6. 疑问和思考6.1 tcpdump抓包跟网卡、内核之间的顺序…

ffmpeg音视频裁剪

音视频裁剪&#xff0c;通常会依据时间轴为基准&#xff0c;从某个起始点到终止点的音视频截取出来&#xff0c;当然音视频文件中存在多路流&#xff0c;所对每一组流进行裁剪 基础概念&#xff1a; 编码帧的分类&#xff1a; I帧(Intra coded frames): 关键帧&#xff0c;…

linux(ubuntu18.04.2) Qt编译 MySQL(8.0以上版本)链接库 Qt版本 5.12.12及以上 包含Mysql动态库缺失问题

整理这篇文档的意义在于&#xff1a;自己走了很多弯路&#xff0c;淋过雨所以想为别人撑伞&#xff0c;也方便回顾&#xff0c;仅供参考 一、搭建开发环境&#xff1a; 虚拟机&#xff08;ubuntu-20.04.6-desktop-amd64&#xff09;&#xff1a;Mysql数据库 8.0.36Workbench …

pytorch库 01 安装Anaconda、Jupyter,Anaconda虚拟环境连接pycharm

文章目录 一、安装Anaconda1、卸载Anaconda&#xff08;可选&#xff09;2、下载并安装Anaconda3、配置环境变量4、桌面快捷方式 二、安装 PyTorch&#xff08;GPU 版&#xff09;库1、创建虚拟环境&#xff0c;并安装一些常用包2、GPU 基础3、检查驱动4、安装CUDA&#xff08;…

数字化转型新篇章:企业通往智能化的新范式

早在十多年前&#xff0c;一些具有前瞻视野的企业以实现“数字化”为目标启动转型实践。但时至今日&#xff0c;可以说尚无几家企业能够在真正意义上实现“数字化”。 在实现“数字化”的征途上&#xff0c;人们发现&#xff0c;努力愈进&#xff0c;仿佛终点愈远。究其原因&a…

Springboot+Vue项目-基于Java+MySQL的校园外卖服务系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…