Flutter: TextField will not scroll down when new lines are added - flutter

Update: this problem may be because of setting the TextField's 'readOnly' attribute to true. If so, I still need to make sure the keyboard does not pop up, since I set readOnly to true to disable the keyboard.
BACKGROUND
My app adds letters and newlines from a custom button widgets to a display that has a TextField widget. I do not use a keyboard for this. I use the Provider package and a ChangeNotifier called AppBrain to manage state. AppBrain has the text information and edits it when a letter is selected from the selection bar.
The textfield is a scrollable widget when there are too many lines to fit in its dimensions. But whenever I add letters and a new line of text is created, the textfield does not scroll down and the cursor and edited line is obscured.
I would like to know an easy way to scroll until the cursor is visible again. (When the cursor is at the end of the text, I can scroll to the bottom. When it is in the middle of the text, I just need to scroll it one line down.)
POSSIBLE FIXES
When I reenable the keyboard to edit the TextField, it does it automatically. If anyone knows how the keyboard edits the TextField, I might be able to add that to my addWord function.
I tried using a ScrollController to scroll the TextField using its animateTo() function. The problem I have is that I usually need to just scroll down 1 line, but I don't have the exact pixel height of my lines of text. I also don't know when a line wraps to a new line, which would require a scroll down.
Perhaps if I had the position of my cursor, I could use it along with my TextField's dimensions to scroll down accordingly.
Picture of my app
CODE
Textfield widget in Text Display
Portion of code in Text Display
...
Positioned(//Text Display
top:0,
left:0,
width:kScreenDim.dx,
height:290,
child: Container(
color: kAppBarBackgroundColor,
child: Container(
padding: EdgeInsets.only(left: kTextMargin),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft:Radius.elliptical(17,20),
topRight: Radius.elliptical(17,20)
),
color:kTextDisplayColor
),
child: Consumer<AppBrain>(
builder: (context,appBrain, child)=> TextField(// <<=====
controller: appBrain.textDisplayController,
scrollController: appBrain.textDisplayScrollController,
readOnly: true,
autofocus: true,
showCursor: true,
maxLines: null,
decoration: null,
cursorColor: Colors.red,
style: TextStyle(fontSize: kTextFontSize,)
),
),
),
),
),
...
Enter Button
Example of a button that adds a letter to the TextField
BottomButton(//ENTER BUTTON
onPressed: () {
var appBrain = Provider.of<AppBrain>(context, listen:false);// <<===
appBrain.addWord('\n');
},
label: 'Enter',
color: kEnterButtonColor,
)
App Brain
Relevant portions of AppBrain, a ChangeNotifier. The main summary of addWord is that I directly edit textDisplayController.text and textDisplayController.selection.
class AppBrain with ChangeNotifier {
...
TextEditingController textDisplayController = TextEditingController();
ScrollController textDisplayScrollController = ScrollController();
...
...
void addWord (String aWord){//Inserts/replaces word into text of TextDisplay
//DIRECTLY EDITS textDisplayController.text and textDisplayController.selection
String displayText = textDisplayController.text;
List<int> selectionRange = getSelectionRange();
int cursorCharIndex = selectionRange[0]; //position in text
//position in _numTChars
int newCursorDisplayIndex = getCursorDisplayIndex(cursorCharIndex);
//Update text and _numTChar.
//If there is a highlighted selection of text to be replaced,
if (selectionRange[0]!=selectionRange[1]){
int lidx = selectionRange[0];
int ridx = selectionRange[1];
int lDisplayIdx = getCursorDisplayIndex(lidx);
int rDisplayIdx = getCursorDisplayIndex(ridx);
textDisplayController.text = displayText.substring(0,lidx) + aWord; <<===
if (ridx < displayText.length) {
textDisplayController.text += displayText.substring(ridx);
}
_numTChars[lDisplayIdx] = aWord.length;
_numTChars = _numTChars.sublist(0, lDisplayIdx+1) +
_numTChars.sublist(rDisplayIdx);
}else {//Insert character at the cursor
if (displayText.length == 0 || cursorCharIndex == displayText.length) {
textDisplayController.text += aWord;
} else {
textDisplayController.text = displayText.substring(0, cursorCharIndex)+
aWord + displayText.substring(cursorCharIndex);
}
_numTChars.insert(newCursorDisplayIndex, aWord.length);
}
//place the cursor in the correct position.
cursorCharIndex += aWord.length;
// <<===
textDisplayController.selection = TextSelection(
baseOffset: cursorCharIndex,
extentOffset: cursorCharIndex
);
notifyListeners();
}
...
}

When keyboard pops up it covers bottom of you widget. you should use MediaQuery to get bottom padding after keyboard pops up.
wrap your page in a Padding widget and set the bottom like this.
Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: YourWidget()
when keyboard is not visible the value of
MediaQuery.of(context).viewInsets.bottom is zero, but when keyboard pops up the value changes to somethong like 253.6.

Related

Get cursor position in a TextField

I want to create a view with a TextField whose default behavior is disabled (i.e. I don't enable the default keyboard on click).
I created my own keyboard because it allows me to do specific actions for my application.
The keyboard works well and populates the TextField.
My problem
I can't manage the blinking cursor in the TextField. Indeed when I type on my keyboard the cursor follows my text so no problem.
On the other hand, if I decide to click manually in the middle of my text, the cursor moves but the new characters entered do not go to the location of the cursor.
How to do this ?
What does it look like ?
My code
My TextField Widget:
InputAtom(
autofocus: true,
controller: controllerInput,
placeholder: "Placeholder",
fontSize: 35,
keyboard: TextInputType.none,
enableInteractiveSelection: true,
showCursor: true,
cursorColor: Colors.red,
enableSuggestions: false,
autocorrect: false,
),
My button keyboard example:
KeyboardButtonAtom.numeric({
Key? key,
required String text,
required TextEditingController controller,
}): super(
key: key,
text: text,
controller: controller,
onPressed: (){
// Here I add the number typed on the keyboard after
controller.text += text.toString();
// Here the cursor moves to the end of my controller.text
controller.selection = TextSelection.collapsed(offset: controller.text.length);
print(controller.selection.baseOffset);
}
);
How to retrieve the cursor position when I type somewhere by hand in my TextField to be able to add my numbers from the cursor position?
EDIT
In my KeyboardButtonAtom, I do that and it works. Thanks to #Kaushik Chandru
Here is the code that works regardless of the position of the cursor even placed by hand in the middle of your character string
String textBeforeCursor = controller.text.substring(0, controller.selection.baseOffset);
String textAfterCursor = controller.text.substring(controller.selection.extentOffset);
controller.text = textBeforeCursor + text.toString() + textAfterCursor;
int cursorPosition = textBeforeCursor.length + 1;
controller.selection = TextSelection.collapsed(offset: cursorPosition);
To get the current position of the cursor you can try
var cursorPos = _textEditController.selection.base.offset;
String textAfterCursor = _textEditController.text.substring(cursorPos);
String textBeforeCursor = _textEditController.text.substring(0, cursorPos);
_textEditController.text = textBeforeCursor + "someText" + textAfterCursor;
_textEditingController.selection = TextSelection.collapsed(offset: _textEditingController.text.length);

Select text in TextField on Double Click in Flutter Windows App

Is there an option to select the text written in TextFormField or TextField on double clicking the field in a Windows App made in Flutter?
Because currently it only works if the text is double clicked, whereas normally in windows application clicking anywhere in the text field selects the entire text written.
Put your TextField inside GestureDetector
GestureDetector(
onDoubleTap:() {
if(_controller.text.isNotEmpty) {
_controller.selection = TextSelection(baseOffset: 0, extentOffset:_controller.text.length);
}
},
child: TextField(controller: _controller, ),
)
Wrap the textfield with an inkwell to provide a double tap. Then on double tap set the selection of the textfield
InkWell(
onDoubleTap:(){
setState((){
_textController.selection = TextSelection(baseOffset:0, extentOffset: _textController.text.length);
});
},
child:TextField(
controller: _textController,
)
)
You Dont need any other extra Widgets. Its pretty simple,You can use onTap property inside of TextField:
TextField(
controller: _controller,
onTap: () {
_controller.selection = TextSelection(baseOffset: 0, extentOffset: _controller.text.length);
}
)

Flutter: How do I stretch the last Element in a Wrap Widget?

I think this doesn't need much explaining so I didn't include any code or screenshots.
As the title, suggests, I have a Wrap Widget and I want the last Element of it to take up all the available horizontal space to the right, (the same way as if you put an Expanded Widget in a Row).
I this case though, I can not use Expanded or Flexible, as Wrap doesn't allow that.
Edit, added a bit of (simplified) code and screenshots:
_buildTileDragTarget is just a function that returns a DragTarget.
void _buildAnswerWidgetsFromStrings() {
_answerWidgets.clear(); //start building from 0
_answerWidgets.add(_buildTileDragTarget(index: 0)); //add initial DragTarget
//for each word in _answerStrings, add Draggable Chip & DragTarget
for (int i = 0; i < _answerStrings.length; i++) {
WordTile wordTile = _answerStrings[i];
_answerWidgets.add(
Draggable<WordTile>(
key: UniqueKey(),
data: wordTile,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
child: Chip(
label: Text(wordTile.word),
padding: EdgeInsets.all(0),
),
onTap: () {
//seems to work: tap from top to bottom (5)
setState(() {
_options[wordTile.word] = true;
_answerStrings.remove(wordTile);
});
},
),
feedback: Material(child: Chip(label: Text(wordTile.word))),
childWhenDragging: Chip(label: Text(' ' * wordTile.word.length)),
),
);
_answerWidgets.add(_buildTileDragTarget(index: i + 1)); //the position AFTER the current word (last)
}
//remove last DragTarget and add one wrapped in Expanded
_answerWidgets.removeLast();
_answerWidgets.add(
SizedBox(
// width: double.infinity,
child: _buildTileDragTarget(index: _answerStrings.length),
),
); //TODO try wrapping the whole thing in 1 big DragTarget (for last index)
// _answerWidgets.add(_buildTileDragTarget(index: _answerStrings.length));
}
Screenshots:
I wrapped the Wrap widget in a purple Container, each DragTarget is wrapped in a green Container. (you can ignore the blue part)
(1) This is what I have (without SizedBox)
(2) This is what I get when I try using SizedBox(width:double.infinity...)
(3) This is what I want
Try SizedBox(width: double.infinity)
There's also expand constructor you might use
SizedBox.Expand(
child: //your widget
)

Output the cursor's position in a Textfield

I have a textfield controller in Flutter and wish to return the position which the cursor occupies in the textfield?
Keep in mind a user may have typed in text then moved the cursor back along the text to say edit the text.
TextField(
onChanged: (value) {
//TODO - Return Cursor Position
}
),
the cursor posistion in text field
first make controller for text field now
TextField(
onChanged: (value) {
int cursorPos = _textController.selection.base.offset;
print(cursorPos);
//TODO - Return Cursor Position
}
),

Scroll Flutter's multi-line TextField to end

I have a periodic function which appends text to a TextField.
For this purpose I set a TextEditingController and invoke: controller.text += someText.
However, when the TextField is scrolled and text is added, the TextField automatically scrolls back to the top and the just appended text gets out of view.
Is there any way to change the scroll behavior?
Set the selection using the TextEditingController.
This will prevent the TextField from rebuilding every time and/or moving the cursor to the start of the TextField.
TextField(
controller: textEditController,
onChanged: (content) {
textEditController..text = someText
..selection = TextSelection.collapsed(offset: textEditController.text.length);
},
)
I don't know if this can serve you already today but in case someone else sees the publication I hope that helps, I'll say that I do this with a SingleChildScrollView() and I set the reverse property to true. I have to say that I don't use a TexField(), you use a Text(), but it behaves well and every time the text increases a line automatically the reverse property of the SingleChildScrollView() causes it to scroll down and always see the last thing that is being written.
This is a small example of how I do it in my code:
Container(
alignment: Alignment.bottomRight,
height: size.height*0.044,
child: SingleChildScrollView(reverse: true,child: Text(valorOperacion1, style: GoogleFonts.exo(color: tema.hintColor, fontSize: 18.0.sp, fontWeight: FontWeight.w600,),))
),