Skip to content

Instantly share code, notes, and snippets.

@YuerLee
Last active January 13, 2025 11:37
Show Gist options
  • Save YuerLee/3e220a6fc89aa14b4543fd248d9dc0ec to your computer and use it in GitHub Desktop.
Save YuerLee/3e220a6fc89aa14b4543fd248d9dc0ec to your computer and use it in GitHub Desktop.
CustomCursorTextField
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MaterialApp(
home: NameInputField(),
));
}
class NameInputField extends StatefulWidget {
@override
_NameInputFieldState createState() => _NameInputFieldState();
}
class _NameInputFieldState extends State<NameInputField> {
late CustomTextEditingController _controller;
final FocusNode _textFieldFocusNode = FocusNode();
bool _isAdjusting = false; // 防止循環調用
@override
void initState() {
super.initState();
_controller = CustomTextEditingController();
_controller.addListener(_handleTextChanges);
}
@override
void dispose() {
_controller.removeListener(_handleTextChanges);
_controller.dispose();
_textFieldFocusNode.dispose();
super.dispose();
}
// 更新光標位置以確保其在合法位置
void _handleTextChanges() {
if (_isAdjusting) return;
// 獲取所有允許的光標位置
List<int> allowedPositions = _controller.getAllowedCursorPositions();
final selection = _controller.selection;
if (!selection.isCollapsed) return; // 不處理選取範圍
if (!allowedPositions.contains(selection.start)) {
// 找到最接近的允許位置
int newPosition = allowedPositions
.lastWhere((pos) => pos <= selection.start, orElse: () => 0);
_isAdjusting = true;
_controller.value = _controller.value.copyWith(
selection: TextSelection.collapsed(offset: newPosition),
);
_isAdjusting = false;
}
}
// 當 Enter 被按下時,將當前輸入的文字轉換為一個完整的名字
void _addName() {
String text = _controller.text;
int cursorPosition = _controller.selection.baseOffset;
// 找出最後一個分隔符的位置
int lastSeparator = text.lastIndexOf(", ");
String currentInput;
if (lastSeparator != -1 && lastSeparator + 2 < cursorPosition) {
currentInput = text.substring(lastSeparator + 2, cursorPosition).trim();
} else {
currentInput = text.substring(0, cursorPosition).trim();
}
if (currentInput.isEmpty) return;
// 構建新的文本
String before =
lastSeparator != -1 ? text.substring(0, lastSeparator + 2) : "";
String after = text.substring(cursorPosition);
String newText = currentInput.isNotEmpty
? (before.isNotEmpty ? "$before$currentInput, " : "$currentInput, ") +
after
: text;
// 更新文本和光標位置
_isAdjusting = true;
_controller.text = newText;
_controller.selection = TextSelection.collapsed(
offset: before.length + currentInput.length + 2);
_isAdjusting = false;
}
// 處理 Backspace 鍵事件,根據光標位置執行不同的刪除操作
void _handleBackspace() {
String text = _controller.text;
int cursorPosition = _controller.selection.baseOffset;
if (cursorPosition == 0) return;
// 檢查是否光標位於已提交名字後的逗號位置
// 比如 "Name1, Name2, |" 或 "Name1, Name2, "
if (cursorPosition >= 2 &&
text.substring(cursorPosition - 2, cursorPosition) == ", ") {
// 刪除前一個名字及其分隔符
int lastSeparator = text.lastIndexOf(", ", cursorPosition - 3);
if (lastSeparator == -1) {
// 刪除第一個名字
String newText = text.substring(cursorPosition).trimLeft();
_isAdjusting = true;
_controller.text = newText;
_controller.selection = TextSelection.collapsed(offset: 0);
_isAdjusting = false;
} else {
// 刪除前一個名字
String newText = text.substring(0, lastSeparator + 2) +
text.substring(cursorPosition);
_isAdjusting = true;
_controller.text = newText;
_controller.selection =
TextSelection.collapsed(offset: lastSeparator + 2);
_isAdjusting = false;
}
return;
}
// 如果光標不在已提交名字後的位置,允許逐字刪除
String newText =
text.substring(0, cursorPosition - 1) + text.substring(cursorPosition);
_isAdjusting = true;
_controller.text = newText;
_controller.selection = TextSelection.collapsed(offset: cursorPosition - 1);
_isAdjusting = false;
}
// 處理鍵盤按鍵事件
KeyEventResult _handleKeyEvent(FocusNode node, KeyEvent event) {
if (event is KeyDownEvent) {
if (event.logicalKey == LogicalKeyboardKey.enter) {
_addName();
return KeyEventResult.handled;
} else if (event.logicalKey == LogicalKeyboardKey.backspace) {
_handleBackspace();
return KeyEventResult.handled;
}
// 根據最新需求,不處理 Delete 鍵
}
return KeyEventResult.ignored;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('自訂 Name Input TextField'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Focus(
onKeyEvent: _handleKeyEvent, // 使用 onKeyEvent 處理鍵盤事件
child: TextField(
controller: _controller,
focusNode: _textFieldFocusNode,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: '輸入姓名,按 Enter 完成',
),
style: TextStyle(color: Colors.black), // 預設文字顏色
),
),
),
);
}
}
class CustomTextEditingController extends TextEditingController {
CustomTextEditingController({String? text}) : super(text: text);
// 獲取所有允許的光標位置
List<int> getAllowedCursorPositions() {
List<int> positions = [0];
List<String> names = text.split(", ");
int current = 0;
for (var name in names) {
current += name.length;
positions.add(current + 2); // 加上 ", "
}
if (positions.last != text.length) {
positions.add(text.length);
}
return positions;
}
@override
TextSpan buildTextSpan(
{required BuildContext context,
TextStyle? style,
required bool withComposing}) {
List<TextSpan> children = [];
String currentText = this.text;
// 分割名字,以 ", " 作為分隔符
List<String> names = currentText.split(", ");
bool endsWithSeparator = currentText.endsWith(", ");
for (int i = 0; i < names.length; i++) {
String name = names[i];
if (i == names.length - 1 && !endsWithSeparator) {
// 正在輸入的名字,顯示為黑色
children
.add(TextSpan(text: name, style: TextStyle(color: Colors.black)));
} else {
// 已提交的名字,顯示為藍色
children
.add(TextSpan(text: name, style: TextStyle(color: Colors.blue)));
}
if (i != names.length - 1) {
// 添加 ", " 分隔符,顯示為黑色
children
.add(TextSpan(text: ', ', style: TextStyle(color: Colors.black)));
}
}
return TextSpan(
style: style ?? TextStyle(color: Colors.black), children: children);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment