如何在 TextField 获得焦点时滚动到 SingleChildScrollView 的底部?
How to scroll to bottom of SingleChildScrollView when TextField gets focus?
因此,我有一个包含两个 TextField 的登录页面,然后在最底部有一个用于登录的 RaisedButton。当我点击电子邮件字段并弹出键盘时,我希望 SingleChildScrollView(页面上所有内容的父级)滚动到 maxScrollExtent。
我尝试过但没有奏效的方法:
- 利用 Scaffold 的能力自动执行此操作(Scaffold 是应用程序中所有内容的父小部件)
- 使用 this tutorial 在其中创建了一个辅助小部件。还使用了 WidgetBindingsObserver,但整个教程对我不起作用。不过,我想知道 WidgetBindingsObserver 是否仍然有用。
几乎有效的方法:
- 将一个 FocusNode 附加到 TextForm,然后在 initState() 中附加一个侦听器,该侦听器将在获得焦点时为 maxScrollExtent 设置动画。
几乎,这就是我的意思(请原谅 GIF 变色):
如您所见,它在第一次聚焦时不起作用,所以我必须点击密码字段,然后重新点击电子邮件字段以使其具有动画效果。我曾尝试添加延迟(甚至长达 500 毫秒),以便视口有时间在执行此操作之前完全调整大小,但这也不起作用。
如果你认识这个登录主题,那是因为我改编自here。该文件很长,但这里是相关位:
@override
void initState() {
super.initState();
scrollController = ScrollController();
focusNode = FocusNode();
focusNode.addListener(() {
if (focusNode.hasFocus) {
scrollController.animateTo(scrollController.position.maxScrollExtent,
duration: Duration(milliseconds: 500), curve: Curves.ease);
}
});
_emailFieldController = TextEditingController();
_passFieldController = TextEditingController();
_emailFieldController.addListener(() {
_emailText = _emailFieldController.text;
});
_passFieldController.addListener(() {
_passText = _passFieldController.text;
});
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
controller: scrollController,
child: Container(
height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
color: Colors.white,
image: DecorationImage(
colorFilter: ColorFilter.mode(
Colors.black.withOpacity(0.05), BlendMode.dstATop),
image: AssetImage('assets/images/mountains.jpg'),
fit: BoxFit.cover,
),
),
child: new Column(
children: <Widget>[
// this is where all other widgets in the file are
Container(
width: MediaQuery.of(context).size.width,
margin: const EdgeInsets.only(left: 40.0, right: 40.0, top: 10.0),
alignment: Alignment.center,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.deepPurple,
width: 0.5,
style: BorderStyle.solid),
),
),
padding: const EdgeInsets.only(left: 0.0, right: 10.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
child: TextField(
controller: _emailFieldController,
keyboardType: TextInputType.emailAddress,
focusNode: focusNode,
obscureText: false,
textAlign: TextAlign.left,
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'coolname@bestemail.com',
hintStyle: TextStyle(color: Colors.grey),
),
),
),
],
),
),
任何指导将不胜感激。谢谢!
使用 addPostFrameCallback
在构建小部件后进行监听。
_onLayoutDone(_){
FocusScope.of(context).requestFocus(focusNode);
}
@override
void initState() {
//... your stuff
WidgetsBinding.instance.addPostFrameCallback(_onLayoutDone);
super.initState();
}
更新
我看到了错误,第一次使用 scrollController.position.maxScrollExtent
时,值为 0,在您点击密码文本字段并将焦点更改为 email
后,现在 maxScrollExtent
不同是因为键盘是打开的。
如果你想让它工作,做一个逻辑来计算space并直接设置值。
如果你使用
scrollController.animateTo(180.0,
duration: Duration(milliseconds: 500), curve: Curves.ease);
应该可以。
因此,我有一个包含两个 TextField 的登录页面,然后在最底部有一个用于登录的 RaisedButton。当我点击电子邮件字段并弹出键盘时,我希望 SingleChildScrollView(页面上所有内容的父级)滚动到 maxScrollExtent。
我尝试过但没有奏效的方法:
- 利用 Scaffold 的能力自动执行此操作(Scaffold 是应用程序中所有内容的父小部件)
- 使用 this tutorial 在其中创建了一个辅助小部件。还使用了 WidgetBindingsObserver,但整个教程对我不起作用。不过,我想知道 WidgetBindingsObserver 是否仍然有用。
几乎有效的方法:
- 将一个 FocusNode 附加到 TextForm,然后在 initState() 中附加一个侦听器,该侦听器将在获得焦点时为 maxScrollExtent 设置动画。
几乎,这就是我的意思(请原谅 GIF 变色):
如您所见,它在第一次聚焦时不起作用,所以我必须点击密码字段,然后重新点击电子邮件字段以使其具有动画效果。我曾尝试添加延迟(甚至长达 500 毫秒),以便视口有时间在执行此操作之前完全调整大小,但这也不起作用。
如果你认识这个登录主题,那是因为我改编自here。该文件很长,但这里是相关位:
@override
void initState() {
super.initState();
scrollController = ScrollController();
focusNode = FocusNode();
focusNode.addListener(() {
if (focusNode.hasFocus) {
scrollController.animateTo(scrollController.position.maxScrollExtent,
duration: Duration(milliseconds: 500), curve: Curves.ease);
}
});
_emailFieldController = TextEditingController();
_passFieldController = TextEditingController();
_emailFieldController.addListener(() {
_emailText = _emailFieldController.text;
});
_passFieldController.addListener(() {
_passText = _passFieldController.text;
});
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
controller: scrollController,
child: Container(
height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
color: Colors.white,
image: DecorationImage(
colorFilter: ColorFilter.mode(
Colors.black.withOpacity(0.05), BlendMode.dstATop),
image: AssetImage('assets/images/mountains.jpg'),
fit: BoxFit.cover,
),
),
child: new Column(
children: <Widget>[
// this is where all other widgets in the file are
Container(
width: MediaQuery.of(context).size.width,
margin: const EdgeInsets.only(left: 40.0, right: 40.0, top: 10.0),
alignment: Alignment.center,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.deepPurple,
width: 0.5,
style: BorderStyle.solid),
),
),
padding: const EdgeInsets.only(left: 0.0, right: 10.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
child: TextField(
controller: _emailFieldController,
keyboardType: TextInputType.emailAddress,
focusNode: focusNode,
obscureText: false,
textAlign: TextAlign.left,
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'coolname@bestemail.com',
hintStyle: TextStyle(color: Colors.grey),
),
),
),
],
),
),
任何指导将不胜感激。谢谢!
使用 addPostFrameCallback
在构建小部件后进行监听。
_onLayoutDone(_){
FocusScope.of(context).requestFocus(focusNode);
}
@override
void initState() {
//... your stuff
WidgetsBinding.instance.addPostFrameCallback(_onLayoutDone);
super.initState();
}
更新
我看到了错误,第一次使用 scrollController.position.maxScrollExtent
时,值为 0,在您点击密码文本字段并将焦点更改为 email
后,现在 maxScrollExtent
不同是因为键盘是打开的。
如果你想让它工作,做一个逻辑来计算space并直接设置值。
如果你使用
scrollController.animateTo(180.0,
duration: Duration(milliseconds: 500), curve: Curves.ease);
应该可以。