I need to scroll page when textfield is focused.
So I used scroll_to_index plugin(https://pub.dev/packages/scroll_to_index).
But It operate well only when keyboard is already opened.
If I tap the textfield when keyboard is not opened, it doesn't scroll the page.
I think it is because page is scrolled before the keyboard is opened.
In my opinion, the problem seems to arise because the code works this way.
1.tap a textfield (linked Focusnode has Focus)
2. It try to scroll the page to the target. But because now keyboard is not opened yet, target is already in view. So It looks like nothing happened.
3. Keyboard is opened, and hide the content.
auto_scroll.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
class MyCustomScrollView extends StatefulWidget {
#override
_MyCustomScrollViewState createState() => _MyCustomScrollViewState();
}
class _MyCustomScrollViewState extends State<MyCustomScrollView> {
AutoScrollController _autoScrollController = new AutoScrollController();
List<FocusNode> nodes = List<FocusNode>.generate(3, (index) => FocusNode());
Future _scrollToIndex(int index) async {
await _autoScrollController.scrollToIndex(index,
preferPosition: AutoScrollPosition.end);
}
#override
void initState() {
super.initState();
for(var i =0;i<3;i++) {
nodes[i].addListener(() {
if(nodes[i].hasFocus) _scrollToIndex(i);
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true,
body: CustomScrollView(
controller: _autoScrollController,
slivers: [
SliverToBoxAdapter(
child: Container(
child: Column(
children: [
SizedBox(height: 100),
CupertinoTextField(
focusNode: nodes[0],
onEditingComplete: (){
FocusScope.of(context).requestFocus(nodes[1]);
},
),
AutoScrollTag(
key: ValueKey(0),
controller: _autoScrollController,
index: 0,
child: Container(height: 300, color : Colors.green)
),
CupertinoTextField(
focusNode: nodes[1],
onEditingComplete: (){
FocusScope.of(context).requestFocus(nodes[2]);
},
),
AutoScrollTag(
key: ValueKey(1),
controller: _autoScrollController,
index: 1,
child: Container(
height: 300,
color : Colors.green,
child: Center(
child: Text("Here should be visible!!!!"),
),
)
),
CupertinoTextField(
focusNode: nodes[2],
),
AutoScrollTag(
key: ValueKey(2),
controller: _autoScrollController,
index: 2,
child: Container(height: 300, color : Colors.green)
),
],
),
)
)
],
),
);
}
}
This is the code. When I tap on the second textfield, it should scroll to show all the red containers below the second textfield, but it doesn't.
But when the keyboard is up, clicking on the second text field works as desired.
What I expected when I tap 2nd Textfield
What actually happened
I know that just giving focus to textfield is enought to scroll to the textfield. But in my application I have a Positioned bar just above the keyboard, so I have a situation where the Positioned bar covers the text field. This is why I use scroll_to_index.
So what I want to do is wait for the keyboard to come up, and then when the keyboard comes up, run the _scrollToIndex function. How can I wait until keyboard is opened? or there are any good solution for this problem? Thank you for your reading.
You can use flutter_keyboard_visibility. There are 2 ways of implementation: with provider and with listener.
Related
In my use case, I have multiple layouts in a particular page. for ex, in a page, i have layout(lets say layout-1) where i get the input data from user using list of textfield and then in the same page, i have another layout (lets say layout-2) in the bottom where table widget is being used to list row by row for the data that we received from user. similarly i have other layouts in the same page. I am trying to provide the keyboard shortcuts for these by using control + D, control + R etc. so the problem that i am facing is, if i focused on the texfield in layout-1 and then i press keyboard keys control + D this is focusing the expected widget correctly. but if i click outside of the texfield (anywhere in the screen on the particular page), then it loose the focus. now if i click the control + D, the shortcuts & action widget not listening the keyboard events.
In order to show this problem i just created simple code here and in this code if i click outside of the text field and if i try to use control + D, its not working.
for example, in the below code i have two textfields and i am focusing in these text fields based on shortcuts (this is just an example to explain the problem). control + R is to focus in textfield1 and control + D is to focus on textfield2. its working fine. but if i click somewhere outside of the text field and then if i try shortcuts, its not listening the keyboard events. it loose the focus.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class KeyboardShortcutTesting extends StatelessWidget {
KeyboardShortcutTesting({Key? key}) : super(key: key);
final FocusNode textfield1FocusNode = FocusNode();
final FocusNode textfield2FocusNode = FocusNode();
#override
Widget build(BuildContext context) {
return Shortcuts(
shortcuts: {
LogicalKeySet(LogicalKeyboardKey.controlLeft, LogicalKeyboardKey.keyR):
Testfield1ShorcutIntent(),
LogicalKeySet(LogicalKeyboardKey.controlLeft, LogicalKeyboardKey.keyD):
Testfield2ShorcutIntent(),
},
child: Actions(
actions: {
Testfield1ShorcutIntent:
CallbackAction<Testfield1ShorcutIntent>(onInvoke: (intent) {
print('clicked cnrl + D');
return textfield1FocusNode.requestFocus();
}),
Testfield2ShorcutIntent:
CallbackAction<Testfield2ShorcutIntent>(onInvoke: (intent) {
print('clicked cnrl + R');
return textfield2FocusNode.requestFocus();
}),
},
child: Focus(
autofocus: true,
child: Scaffold(
appBar: AppBar(title: const Text('Keyboard shortcut testing')),
body: Center(
child: Card(
color: Colors.amber[50],
child: Padding(
padding: const EdgeInsets.all(16.0),
child: SizedBox(
width: 300,
height: 200,
child: Column(
children: [
TextField(
focusNode: textfield1FocusNode,
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
),
const SizedBox(height: 10),
TextField(
focusNode: textfield2FocusNode,
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
),
],
),
),
),
),
)),
),
),
);
}
}
class Testfield1ShorcutIntent extends Intent {}
class Testfield2ShorcutIntent extends Intent {}
This code i am trying for flutter web. i am also attaching an image to show the problem. here if i focus either on textfield1 or texfield2 and then if i press control + D or control + R then the focus is being switched between the textfields as per the logic but i click outside of the textfield (anywhere in the yellow color card or outside of yellow color card) and then if i press then shortcuts are not working as its not even listening the keys.
Note: I also tried using FocusScope widget instead of Focus widget but FocusScope widget is not allowing to focus outside. for ex. if i use FocusScope widget and if i click outside of the textfield, its not allowing because the focus is not coming out from the textfield. it continuously focusing on the same textfield widget.
Appreciate the response!.
Flutter Docs suggests using Stateful Widget for creating FocusNode
Flutter provide pre-build Intent to request focus -> RequestFocusIntent(focusNode1)
Try out the following code,
class TextFieldShortcut extends StatefulWidget {
const TextFieldShortcut({super.key});
#override
State<TextFieldShortcut> createState() => _TextFieldShortcutState();
}
class _TextFieldShortcutState extends State<TextFieldShortcut> {
late final FocusNode focusNode1, focusNode2;
#override
void initState() {
super.initState();
focusNode1 = FocusNode();
focusNode2 = FocusNode();
}
#override
void dispose() {
focusNode1.dispose();
focusNode2.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Shortcuts(
shortcuts: {
LogicalKeySet(LogicalKeyboardKey.controlLeft, LogicalKeyboardKey.keyM):
RequestFocusIntent(focusNode1),
LogicalKeySet(LogicalKeyboardKey.controlLeft, LogicalKeyboardKey.keyQ):
RequestFocusIntent(focusNode2),
},
child: MaterialApp(
home: Scaffold(
body: Center(
child: Column(
children: [
TextField(
autofocus: true,
focusNode: focusNode1,
),
TextField(
focusNode: focusNode2,
),
],
),
),
),
),
);
}
}
Why my bottomsheet is disappearing upon opening keyboard while clicking textfeild? I tried many solutions but none of them is working properly.
I have nothing much to add just trying to complete the words criteria.
"It looks like your post is mostly code; please add some more details".
Container(
padding: EdgeInsets.symmetric(
horizontal: deviceSize.width * 0.1),
child: InkWell(
onTap: () {
showAddressBottomSheet(context);
},
child: ...// Button style
),
void showAddressBottomSheet(BuildContext _context) {
showModalBottomSheet(
backgroundColor: Colors.transparent,
context: _context,
isScrollControlled: true,
elevation: 20,
builder: (context) {
return AddAddressView(
callback: (val) => setState(() => _addrSelectedTitle = val),
);
},
);
}
Actual Buttom sheet
class AddAddressView extends StatefulWidget {
#override
_AddAddressViewState createState() => _AddAddressViewState();
}
class _AddAddressViewState extends State<AddAddressView> {
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Container(
padding: EdgeInsets.all(30),
child: ListView(
shrinkWrap: true,
children: <Widget>[
Text(......),
TextField(
autofocus: true,
style: TextStyle(fontSize:15),
controller: widget.controller,
..........
),
),
),
),
},
}
I have seen this issue before and since you have posted a reproduction of your actual code, I'm assuming this padding value exists somewhere in your widget tree
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
In older versions of flutter, this padding was required for the bottom sheet to move up when the keyboard is in view. They have however been fixed and you don't need to apply the padding anymore and the framework will handle it. Otherwise there will be twice as much padding as seen in your case.
I am new to Flutter. The thing I want is to keep focus on TextField, but not display keyboard. Is it possible?
To give focus to a text field as soon as it’s visible, use the autofocus property.
content_copy
TextField(
autofocus: true,
);
_dismissKeyboard(BuildContext context) {
FocusScope.of(context).requestFocus(new FocusNode());
}
#override
Widget build(BuildContext context) {
return new GestureDetector(
onTap: () {
this._dismissKeyboard(context);
},
child: new Container(
color: Colors.white,
child: new Column(
children: <Widget>[/*...*/],
),
),
);
}
Both of these components should be used together to implement what you are trying to acheive.
In one tab I have a TextFormField and in the other only a list of texts. When I select the Text field the keyboard is open, then I jump to the second Tab and the keyboard is still displayed.
I can even write and when I go back to the Tab 1 I see why I typed.
Do you know how can I give an action to the second Tab in order to take the focus out from the text field?
DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text('Manage Products'),
bottom: TabBar(tabs: <Widget>[
Tab(icon: Icon(Icons.create), text: 'Create Product'),
Tab(icon: Icon(Icons.list), text: 'My Products'),
]),
),
body: TabBarView(
children: <Widget>[
ProductEditPage(addProduct: addProduct),
ProductListPage(products, updateProduct),
],
)),
);
Tab1
Tab2
SOLVING CODE
After applying #nick.tdr suggestion an example code can be as follow:
class _Test extends State<Test> with TickerProviderStateMixin {
TabController _tabController;
#override
void initState() {
super.initState();
_tabController = TabController(vsync: this, length: 2);
_tabController.addListener(() {
if (_tabController.indexIsChanging) {
FocusScope.of(context).requestFocus(new FocusNode());
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('2 Tabs'),
bottom: TabBar(controller: _tabController, tabs: <Widget>[
Tab(text: 'Tab with Text Field'),
Tab(text: 'Empty Tab'),
]),
),
body: TabBarView(
controller: _tabController,
children: <Widget>[
Container(
child: TextFormField(
decoration: InputDecoration(labelText: 'Title'),
),
),
Container()
],
),
);
}
}
You can add gesture detector to you scaffold and remove the focus. But that won't work for the tabs. For the tabs you need to do the following:
controller.addListener((){
if(controller.indexIsChanging)
{
FocusScope.of(context).detach();
}
});
Where controller is your tab controller. Hope that helps
I improved #nick.tdr 's answer.
For the tabs you need to do the following;
controller.addListener((){
if(controller.indexIsChanging)
{
FocusScope.of(context).unfocus();
}
});
If you want to work this when swiping between tabs instead of clicking tab buttons, try the following;
controller.addListener((){
FocusScope.of(context).unfocus();
});
Where the controller is your tab controller.
For me I found the best way is to request a new FocusNode on tabChange listener that has been set in didChangeDependencies() callback:
in build() method:
TabBar(
controller: tabController,
.
.
.
),
didChangeDependencies callback:
#override
void didChangeDependencies() {
setState(() {
tabController.addListener(handleTabChange);
});
super.didChangeDependencies();
}
The listener implementation:
handleTabChange() {
// do whatever handling required first
setState(() {
FocusScope.of(context).requestFocus(new FocusNode());
});
}
I think wrapping up your whole Scaffold body into a GestureDetector should solve your problem.
new Scaffold(
body: new GestureDetector(
onTap: () {
// call this method here to hide keyboard
FocusScope.of(context).requestFocus(new FocusNode());
},
child: new Container(
/*Remaining code goes here*/
)
)
This simply gains focus on the widget you tapped on removing focus from previous one.
The widgets in my ReorderableListView are essentially TextFields. When long pressing on a widget, after the time when the long press should cause the widget to "hover," instead the TextField receives focus. How can I make the drag & drop effect take precedence over the TextField? I would still like a normal tap to activate the TextField.
The code below demonstrates my issue.
I also tried to use this unofficial flutter_reorderable_list package. (To test this one, replace the Text widget on this line of the example code with a TextField.)
I'm willing to use any ugly hacks to get this working, including modifying the Flutter source code!
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final children = List<Widget>();
for (var i = 0; i < 5; i++) {
children.add(Container(
color: Colors.pink, // Only the pink area activates drag & drop
key: Key("$i"),
height: 50.0,
child: Container(
color: Colors.grey,
margin: EdgeInsets.only(left: 50),
child: TextField(),
),
));
}
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: ReorderableListView(
children: children,
onReorder: (oldIndex, newIndex) => null,
),
),
),
);
}
}
You need to do multiple things in there to fix this.
First disable the default handler in ReorderableListView by setting buildDefaultDragHandles: false in its properties.
Wrap you child widget inside ReorderableDragStartListener widget like this
ReorderableDragStartListener(
index: i,
child: Container(
color: Colors.grey,
margin: EdgeInsets.only(left: 50),
child: TextFormField(initialValue: "Child $i", ),
),
),
Then inside this ReorderableDragStartListener wrap your child in InkWell and AbsorbPointer. Then use FocusNode to focus inner TextField on single tap.
Like this
InkWell(
onTap: () => _focusNode.requestFocus(),
onLongPress: () {
print("long pressed");
},
child: AbsorbPointer(
child: TextFormField(initialValue: "Child $i", focusNode: _focusNode,),
),
),
You need to create multiple FocusNode for all the items in list. You can do this by using List or by simpling creating a new FocusNode inside the loop.
Complete code example here https://dartpad.dev/?id=e75b493dae1287757c5e1d77a0dc73f1