void scrollToWord(String word) {
final text = _controller.text;
// Find the position of the word in the text
int index = text.indexOf(word);
if (index == -1) {
// Word not found, return early
return;
}
// Move the cursor to the start of the word
_controller.selection = TextSelection.fromPosition(TextPosition(offset: index));
// Calculate the scroll position (find position of word's start)
final textPainter = TextPainter(
text: TextSpan(text: text),
textDirection: TextDirection.ltr,
);
textPainter.layout(maxWidth: MediaQuery.of(context).size.width);
// Calculate the word's start position on screen
final wordStartOffset = textPainter.getOffsetForCaret(TextPosition(offset: index));
final wordHeight = textPainter.size.height;
// Scroll to the word's position
_scrollController.animateTo(
wordStartOffset.dy, // Scroll position
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
}
但这不起作用,因为这个词以某种方式离开了。
您想:
将单词作为用户(或任何其他资源)的输入。在存储在
startOffset
的文本中的文字中找到了这个词。
在TextField
controller.text
offset
:控制TextField
的滚动行为。
offset
:管理文本和光标位置
ScrollController
。
TextField
然后创建将控制滚动的方法:
TextEditingController
2。 UI部分
-创建textfielwidget组件
TextField
您必须将高度定义到一个字段或给它一个约束以防止其扩展
-创建按钮小部件组件
// Controller that will catch the target Word from the User
// Also will be used to control the cursor position
late final TextEditingController _searchController;
// The Target contrller that we will scroll from it
late final TextEditingController _targetController;
// The ScrollController of the TextField Widget
late final ScrollController _scrollController;
// Initialize the Controllers in the initState
@override
void initState() {
_searchController = TextEditingController();
_targetController = TextEditingController();
_scrollController = ScrollController();
super.initState();
}
@override
void dispose() {
_searchController.dispose();
_targetController.dispose();
_scrollController.dispose();
super.dispose();
}
void get _scrollToSearchedText {
log("Start to Scroll .....");
final String contentText = _targetController.text;
final String searchText = _searchController.text;
final int indexOfTextinContent = contentText.indexOf(searchText);
if (indexOfTextinContent != -1 || contentText.isNotEmpty) {
log("trying ....");
// ensure that all frames has been build and there are no Widgets need to bind
WidgetsBinding.instance.addPostFrameCallback(
(_) {
final textPainter = TextPainter(
text: TextSpan(
text: contentText.substring(0, indexOfTextinContent),
style: const TextStyle(fontSize: 16),
),
textDirection: TextDirection.ltr,
);
// Layout the text to calculate the size
// Computes the visual position of the glyphs for painting the text.
textPainter.layout();
// Calculate the scroll offset based on the text width and height
final double scrollOffset = textPainter.size.height *
(textPainter.size.width /
_scrollController.position.viewportDimension);
// Scroll to the calculated offset
_scrollController.jumpTo(scrollOffset.clamp(
0.0, _scrollController.position.maxScrollExtent));
// Move the cursor to the start of the searched word
_targetController.selection = TextSelection.collapsed(
offset: indexOfTextinContent,
);
},
);
}
}
您可以忽略代码的某些部分,例如class CustomTextFielWidget extends StatelessWidget {
const CustomTextFielWidget({
super.key,
required this.controller,
required this.hintText,
this.isTargetField = false,
this.scrollController,
});
final TextEditingController controller;
final String hintText;
final bool isTargetField;
final ScrollController? scrollController;
@override
Widget build(BuildContext context) {
return Container(
height: isTargetField ? context.screenHeight * .5 : null, <---- Height is must
padding: const EdgeInsets.all(10),
child: TextField(
controller: controller,
scrollController: scrollController,
scrollPhysics: const AlwaysScrollableScrollPhysics(),
maxLines: isTargetField ? null : 1,
style: TextStyle(
fontSize: isTargetField ? 20 : 18,
fontWeight: FontWeight.bold,
fontFamily: isTargetField ? FontFamily.verlaFont : null,
),
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(
color: Colors.grey.withOpacity(0.6),
width: 1.3,
),
),
hintText: hintText,
fillColor: Colors.grey.withOpacity(0.3),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(
color: Colors.grey.withOpacity(0.6),
width: 2.0,
),
),
),
),
);
}
}
,
class ScrollButtonWidget extends StatelessWidget {
const ScrollButtonWidget({
super.key,
required this.onScrollTap,
});
final void Function() onScrollTap;
@override
Widget build(BuildContext context) {
return SizedBox(
width: context.screenWidth * .7,
height: context.screenHeight * .07,
child: MaterialButton(
onPressed: onScrollTap,
color: Colors.blue,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: const Text("Scroll To Target Word"),
),
);
}
}
或import 'dart:developer';
import 'package:flutter/material.dart';
class ScrollToTextWidget extends StatefulWidget {
const ScrollToTextWidget({super.key});
@override
State<ScrollToTextWidget> createState() => _ScrollToTextWidgetState();
}
class _ScrollToTextWidgetState extends State<ScrollToTextWidget> {
// Controller that will catch the target Word from the User
late final TextEditingController _searchController;
// The Target contrller that we will scroll from it
late final TextEditingController _targetController;
// The ScrollController of the TextField Widget
late final ScrollController _scrollController;
@override
void initState() {
_searchController = TextEditingController();
_targetController = TextEditingController();
_scrollController = ScrollController();
super.initState();
}
@override
void dispose() {
_searchController.dispose();
_targetController.dispose();
_scrollController.dispose();
super.dispose();
}
void get _scrollToSearchedText {
log("Start to Scroll .....");
final String contentText = _targetController.text;
final String searchText = _searchController.text;
final int indexOfTextinContent = contentText.indexOf(searchText);
if (indexOfTextinContent != -1 || contentText.isNotEmpty) {
log("trying ....");
WidgetsBinding.instance.addPostFrameCallback(
(_) {
final TextPainter textPainter = TextPainter(
text: TextSpan(
text: contentText.substring(0, indexOfTextinContent),
style: const TextStyle(fontSize: 16),
),
textDirection: TextDirection.ltr,
);
// Layout the text to calculate the size
// Computes the visual position of the glyphs for painting the text.
textPainter.layout();
// Calculate the scroll offset based on the text width and height
final double scrollOffset = textPainter.size.height *
(textPainter.size.width /
_scrollController.position.viewportDimension);
// Scroll to the calculated offset
_scrollController.jumpTo(scrollOffset.clamp(
0.0, _scrollController.position.maxScrollExtent));
// Move the cursor to the start of the searched word
_targetController.selection = TextSelection.collapsed(
offset: indexOfTextinContent,
);
},
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Scroll To Text"),
automaticallyImplyLeading: false,
backgroundColor: Colors.green,
),
body: Column(
children: <Widget>[
Expanded(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
gapH1,
CustomTextFielWidget(
controller: _searchController,
hintText: "Enter Target Text",
),
gapH1,
CustomTextFielWidget(
controller: _targetController,
hintText: "Content Text",
scrollController: _scrollController,
isTargetField: true,
),
gapH2,
],
),
),
),
ScrollButtonWidget(
onScrollTap: () {
_scrollToSearchedText;
},
)
],
),
);
}
}
class CustomTextFielWidget extends StatelessWidget {
const CustomTextFielWidget({
super.key,
required this.controller,
required this.hintText,
this.isTargetField = false,
this.scrollController,
});
final TextEditingController controller;
final String hintText;
final bool isTargetField;
final ScrollController? scrollController;
@override
Widget build(BuildContext context) {
return Container(
height: isTargetField ? context.screenHeight * .5 : null,
padding: const EdgeInsets.all(10),
child: TextField(
controller: controller,
scrollController: scrollController,
scrollPhysics: const AlwaysScrollableScrollPhysics(),
maxLines: isTargetField ? null : 1,
style: TextStyle(
fontSize: isTargetField ? 20 : 18,
fontWeight: FontWeight.bold,
fontFamily: isTargetField ? FontFamily.verlaFont : null,
),
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(
color: Colors.grey.withOpacity(0.6),
width: 1.3,
),
),
hintText: hintText,
fillColor: Colors.grey.withOpacity(0.3),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(
color: Colors.grey.withOpacity(0.6),
width: 2.0,
),
),
),
),
);
}
}
class ScrollButtonWidget extends StatelessWidget {
const ScrollButtonWidget({
super.key,
required this.onScrollTap,
});
final void Function() onScrollTap;
@override
Widget build(BuildContext context) {
return SizedBox(
width: context.screenWidth * .7,
height: context.screenHeight * .07,
child: MaterialButton(
onPressed: onScrollTap,
color: Colors.blue,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: const Text("Scroll To Target Word"),
),
);
}
}
这是我创建的属性
有一些关于光标的注释,如果您希望光标处于单词的开头,您的目标文本字段必须是焦点,则可以通过用
gapH1
小部件包裹screenWidth
screenHeight
来处理
TextField
当用户单击按钮时将其形成