You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter:自定义键盘绑定TextField,如何在光标位置插入文本?

问题

我开发了一个固定在屏幕上的自定义数字键盘,绑定到一个通过指定keyboardType: TextInputType.none实现只读的TextField。当前问题是:当将光标移动到TextField内的任意位置后,点击自定义键盘输入数字,内容总是被追加到字符串末尾,且光标会自动回到字段开头。请问该如何修复这个问题?

相关代码

CustomTextField 实现

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

class CustomTextField extends StatelessWidget {
  var w;
  var limit;
  var textFieldHint;
  var minL;
  var maxL;
  var cont;
  var kbtype;
  var dispCursor;
  CustomTextField(
      {Key? key,
      required this.w,
      required this.limit,
      required this.textFieldHint,
      required this.minL,
      required this.maxL,
      required this.cont,
      required this.kbtype,
      required this.dispCursor})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 10.0),
      child: SizedBox(
          width: MediaQuery.of(context).size.width * w,
          child: TextField(
            showCursor: dispCursor,
            controller: cont,
            keyboardType: kbtype,
            decoration: InputDecoration(
                fillColor: Colors.white,
                filled: true,
                hintText: textFieldHint,
                hintStyle: TextStyle(fontSize: 18, color: Colors.grey),
                border: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(10),
                    borderSide: BorderSide.none)),
            inputFormatters: [
              LengthLimitingTextInputFormatter(limit),
            ],
            minLines: minL,
            maxLines: maxL,
          )),
    );
  }
}

CustomButton 实现

class CustomButton extends StatelessWidget {
  final int number;
  final TextEditingController controller;

  const CustomButton({
    Key? key,
    required this.number,
    required this.controller,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 5.0, right: 3),
      child: ElevatedButton(
        onPressed: () {
          controller.text += number.toString();
        },
        child: Text(
          number.toString(),
          style: TextStyle(color: Colors.blueGrey),
        ),
        style: ButtonStyle(
            elevation: MaterialStateProperty.all(10),
            shape: MaterialStateProperty.all<RoundedRectangleBorder>(
              RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(10),
              ),
            ),
            fixedSize: MaterialStateProperty.all(Size.square(40)),
            backgroundColor: MaterialStateProperty.all(Colors.white)),
      ),
    );
  }
}

使用代码片段

import 'package:expensetracker/Screens/home.dart';
import 'package:expensetracker/Widgets/customButton.dart';
import 'package:expensetracker/Widgets/customDate.dart';
import 'package:expensetracker/Widgets/customTime.dart';
import 'package:expensetracker/Widgets/customTextField.dart';
import 'package:flutter/material.dart';

class AddScreen extends StatefulWidget {
  const AddScreen({Key? key}) : super(key: key);

  @override
  State<AddScreen> createState() => _AddScreenState();
}

var choice = [textConst.income, textConst.expense];
var dropdownvalue = textConst.income;
late DateTime _selectedDate;
TextEditingController title = TextEditingController();
TextEditingController descrip = TextEditingController();
TextEditingController amount = TextEditingController();
TextEditingController date = TextEditingController();
TextEditingController time = TextEditingController();

class _AddScreenState extends State<AddScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(body: ...
...CustomTextField(
                dispCursor: true,
                kbtype: TextInputType.none,
                cont: amount,
                w: .8,
                limit: null,
                textFieldHint: textConst.amount,
                minL: 1,
                maxL: 1),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                CustomButton(
                  number: 1,
                  controller: amount,
                ),
                CustomButton(
                  number: 2,
                  controller: amount,
                ),
                CustomButton(
                  number: 3,
                  controller: amount,
                )
              ],
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                CustomButton(number: 4, controller: amount),
                CustomButton(number: 5, controller: amount),
                CustomButton(
                  number: 6,
                  controller: amount,
                ),
              ],
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                CustomButton(
                  number: 7,
                  controller: amount,
                ),
                CustomButton(
                  number: 8,
                  controller: amount,
                ),
                CustomButton(
                  number: 9,
                  controller: amount,
                )
              ],
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                CustomButton(
                  number: 0,
                  controller: amount,
                ),
                ElevatedButton(
                    onPressed: () {
                      amount.text =
                          amount.text.substring(0, amount.text.length - 1);
                    },
                    child: Text(textConst.delete),
                    style: ButtonStyle(
                      elevation: MaterialStateProperty.all(10),
                      shape: MaterialStateProperty.all<RoundedRectangleBorder>(
                        RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(10),
                        ),
                      ),
                      minimumSize: MaterialStateProperty.all(
                        Size(120, 40),
                      ),
                      backgroundColor: MaterialStateProperty.all(Colors.red),
                    ))
              ],
            )
修复方案

问题根源

直接通过controller.text += ...修改文本时,会完全替换原有内容,导致系统丢失当前光标位置,默认将光标重置到文本开头,同时只能将内容追加到末尾。正确的做法是利用TextEditingController的selection属性获取当前光标/选中范围,在对应位置插入内容并更新光标位置。

具体修改

1. 修复CustomButton的输入逻辑

修改CustomButton中的onPressed方法,实现光标位置插入文本并更新光标:

onPressed: () {
  final currentText = controller.text;
  final currentSelection = controller.selection;
  
  // 替换选中内容(如果有选中)或在光标位置插入文本
  final newText = currentText.replaceRange(
    currentSelection.start,
    currentSelection.end,
    number.toString(),
  );
  
  controller.text = newText;
  
  // 将光标移动到插入文本的末尾
  controller.selection = TextSelection(
    baseOffset: currentSelection.start + number.toString().length,
    extentOffset: currentSelection.start + number.toString().length,
  );
},

2. 修复删除按钮的逻辑

修改删除按钮的onPressed方法,支持删除选中内容或光标前的字符,并保持光标位置正确:

onPressed: () {
  final currentText = amount.text;
  final currentSelection = amount.selection;
  
  if (currentSelection.start == currentSelection.end) {
    // 无选中内容,删除光标前的字符
    if (currentSelection.start > 0) {
      final newText = currentText.replaceRange(
        currentSelection.start - 1,
        currentSelection.start,
        '',
      );
      amount.text = newText;
      // 光标前移一位
      amount.selection = TextSelection(
        baseOffset: currentSelection.start - 1,
        extentOffset: currentSelection.start - 1,
      );
    }
  } else {
    // 有选中内容,删除选中部分
    final newText = currentText.replaceRange(
      currentSelection.start,
      currentSelection.end,
      '',
    );
    amount.text = newText;
    // 光标停在删除位置的起始处
    amount.selection = TextSelection(
      baseOffset: currentSelection.start,
      extentOffset: currentSelection.start,
    );
  }
},

3. 确保TextField支持光标交互

在CustomTextField的TextField组件中添加enableInteractiveSelection: true(默认已开启,显式声明避免意外配置):

TextField(
  showCursor: dispCursor,
  controller: cont,
  keyboardType: kbtype,
  enableInteractiveSelection: true, // 确保可移动光标和选中文本
  // 原有配置保持不变...
)

修改效果

  • 点击自定义键盘数字时,文本会插入到当前光标位置,光标自动移动到插入内容的末尾
  • 支持选中文本并替换为数字
  • 删除操作支持删除选中内容或光标前的字符,光标位置保持合理

内容的提问来源于stack exchange,提问作者Abdul Karim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 13:54:21