I build a scrolling textfield like proposed in https://github.com/flutter/flutter/issues/9365. Now I want the maxHeight of the ConstrainedBox change dynamically, according to the shown or not shown keyboard. Is there a way to accomplish this?
Widget _buildTextInput() {
return new Container(
padding: new EdgeInsets.all(7.0),
child: new ConstrainedBox(
constraints: new BoxConstraints(
maxHeight: 150.0 <-- This should be dynamic
),
child: new SingleChildScrollView(
scrollDirection: Axis.vertical,
reverse: true,
// here's the actual text box
child: new TextField(
keyboardType: TextInputType.multiline,
maxLines: null, //grow automatically
decoration: new InputDecoration.collapsed(
hintText: 'Please enter a lot of text',
),
),
),
),
);
}
The red box should be the constrained box with open keyboard.
And like so with a closed keyboard.
EDIT:
I'm trying to build an input field that's kind of like posting on Twitter. I need to combine a CircleAvatar, a TextField and a GridView to display the user's avatar, his post and a few images. Like on Twitter, I want the whole thing to scroll, not only the TextField - both while typing and while reviewing what the user typed or uploaded. Besides, the (multiline) TextField should scroll while typing in the visible area (keeping the open or closed keyboard in mind), so the user can see what he's typing.
Even though the Flutter TextField autoscrolls now, I can't get this whole cluster working. Any idea?
Autoscroll has been supported natively in the textfield widget since I believe the beginning of June (2018) - I think this is the commit that added it. You may need to update to the most recent flutter build for it to work, but that is included in version 5.5.
This simplifies matters a bit - this should be all you need to do to get it to work as you want:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => new MyAppState();
}
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text("Example"),
),
body: new Container(
padding: new EdgeInsets.all(7.0),
child: new TextField(
keyboardType: TextInputType.multiline,
maxLines: null,
decoration: new InputDecoration.collapsed(
hintText: 'Please enter a lot of text',
),
),
),
),
);
}
}
EDIT: To answer the OP's edited question - wanting to have other elements within the same scrolling pane as the textview, I've made this:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => new MyAppState();
}
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text("Example"),
),
body: new SingleChildScrollView(
child: new Container(
padding: new EdgeInsets.all(7.0),
child: new Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
new CircleAvatar(
backgroundColor: Colors.blue,
child: new Text("AB"),
),
new Expanded(
child: new Column(
children: [
new TextField(
keyboardType: TextInputType.multiline,
maxLines: null,
decoration: new InputDecoration.collapsed(
hintText: 'Please enter a lot of text',
),
),
new Container(
height: 300.0,
width: 100.0,
color: Colors.green,
),
],
),
),
],
),
),
),
),
);
}
}
By using a SingleChildScrollView, we're still allowing the children to set the size of the viewport (as opposed to a MultiChildLayoutDelegate etc which has to have that size set). The textview grows as big as it needs to be, but doesn't scroll itself as its height is not constrained. The Expanded is needed within the row to make sure that the right side (with the text & pictures) is as large as it can be horizontally.
Related
Here is my main.dart
class _MyHomePageState extends State {
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true,
body: SingleChildScrollView(
child: SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: const WebviewController(),
),
),
);
}
Does anyone who know this answer???
plz.. tell me your solutions...
I used Single child scroll view to scoll up my screens when soft keyboard appears in android..
Also use Adjust Resizing but doesn't work.
IOS device has no problem but only in android device...
ps. If you needed, I'll attach webview_controller.dart too..
I also cant make it scrollable using SingleChildScrollView only but I found a workaround to do that. I kept a flag when keyboard opens and modified my widgets accordingly. Here is the example.
class _MyHomePageState extends State {
bool _keyboardOpen = false;
#override
void initState() {
super.initState();
FocusManager.instance.primaryFocus?.unfocus();
}
#override
Widget build(BuildContext context) {
_keyboardOpen = MediaQuery.of(context).viewInsets.bottom == 0;
return Scaffold(
resizeToAvoidBottomInset: true,
body: SingleChildScrollView(
child: SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Visibility(
visible: _keyboardOpen,
child: const SizedBox(
height: 10,
),
),
),
),
);
}
Here you can make non-visible sizedBox when keyboard opens, you can also decrease the text's size when keyboard appears like this.
Text('your text', textAlign: TextAlign.center,
style: TextStyle(fontSize: (_keyboardOpen)? 22 : 9, fontWeight:
FontWeight.w500)
),
Let me know if this helps.
I'm having trouble understanding the keyboard avoidance dynamic.
Could anyone explain the behavior shown in this example below?
I think ultimately, I would like the whole screen to be scrollable, rather than removing the expandable space when the virtual keyboard pops up. (for example, the spacing between "Log in" and the text fields)
To fix this issue you have to warp your widgets with SingleChildScrollView, I've made an example to explain in detail how to implement it. You can try it with your code:
import 'package:flutter/material.dart';
class TestPage extends StatelessWidget {
const TestPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return SafeArea(
child: SingleChildScrollView(
child: Column(
children: List.generate(
6,
(index) => const Padding(
padding: EdgeInsets.all(8.0),
child: TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'Example',
),
),
),
)),
),
);
}
}
The above code result would outpot:
I'm brand new to flutter and building a form in Flutter containing multiple TextFormFields, and a radio button. But, something weird is happening where the textformfields above the radio buttons are not uniform and keep changing randomly based on other changes I make. In my screenshot 1, you can see the first 3 text fields are uniform, but the one after the radio button is lighter. This is the closest I've come to achieving uniform text field underlines. I've tried adding and removing a lot of things such as padding, sized boxes, containers, etc, and they affect the underlines in different ways.
For instance, changing the mainAxisAlignment under Form->Column->Padding to 'start' instead of spaceEvenly causes the 1st 3 underlines to have thickness in increasing order and the last text box is fine Check screenshot 2. How can I make it so that all the underlines of the TextFormFields are equally thick and uniform irrespective of any other changes I make? Is there any better way of doing radio buttons than what I have done? Is there a better element in flutter than radio buttons for what I have done? I would also love it if I could make the radio buttons horizontal instead of vertical. The underlines are working fine and not fading in chrome, but they are in android emulator (Pixel 5 API30). I want this to work regardless of platform.
main.dart:
import 'package:flutter/material.dart';
import 'package:hospital_finance_app/pages/OPD.dart';
import 'package:hospital_finance_app/pages/login_page.dart';
import 'package:hospital_finance_app/utils/routes.dart';
import 'package:google_fonts/google_fonts.dart';
void main() {
runApp(const hospApp());
}
class hospApp extends StatelessWidget {
const hospApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Login Demo',
home: OPD(),
);
}
}
OPD.dart:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hospital_finance_app/utils/routes.dart';
enum paymentMethod { Cash, Other }
class OPD extends StatefulWidget {
#override
State<OPD> createState() => _OPDState();
}
class _OPDState extends State<OPD> {
paymentMethod? _method = paymentMethod.Cash;
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
appBar: AppBar(backgroundColor: Colors.black),
body: Form(
child:Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextField(
decoration: InputDecoration(
labelText: "Name",
hintText: "Name",
),
),
TextFormField(
decoration: InputDecoration(
hintText: "Mobile number",
labelText: "Mobile Number"),
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly],
),
TextFormField(
decoration: InputDecoration(
labelText: "Amount",
hintText: "Amount"),
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly],
),
Container(
child: Text("Mode of Payment", style: TextStyle(fontSize: 16, color: Colors.black54, fontFamily: "OpenSans", fontWeight: FontWeight.w400 ), textAlign: TextAlign.left),alignment: Alignment.centerLeft,),
Column(
children: <Widget>[
ListTile(
title: const Text('Cash'),
leading: Radio<paymentMethod>(
value: paymentMethod.Cash,
groupValue: _method,
onChanged: (paymentMethod? value) {
setState(() {
_method = value;
});
},
),
),
ListTile(
title: const Text('Other'),
leading: Radio<paymentMethod>(
value: paymentMethod.Other,
groupValue: _method,
onChanged: (paymentMethod? value) {
setState(() {
_method = value;
});
},
),
),
],
),
TextFormField(
decoration: InputDecoration(labelText: "Comments/Notes",hintText: "Comments/Notes"),
),
ElevatedButton(
onPressed:(){ Navigator.pushNamed(context, MyRoutes.opdRoute); },
child: Text("Submit"),
style: TextButton.styleFrom(minimumSize: Size(100,30)),)
]
),
)
),
));
}
}
Screen shot of my application on android emulator with mainAxisAlignment spaceEvenly
Screen shot of my application on android emulator with mainAxisAlignment start.
this is a bug from the android emulator. try running your app on a real device.
When you use mainAxisAlignment as 'start', all the TextFormFields are placed one after another without any spacing between them, so the line thickness is increased (two lines overlap).
Instead, when you spaceEvenly, TextFormFields are separated and the line appears thin, the original thickness.
I've recently started using Flutter just for fun, and I'm stuck on adding actual functionality to the code without having everything inside one class.
Essentially, I'm trying to use a FloatingActionButton to increment the value of a Text Widget which stores the value of the user's level as an integer, but I don't want to have the whole app as a StatefulWidget because only the level is going to be updated. When the button is pressed, the value should increment by 1 and then show the new value on the screen.
I have the Level Text Widget inside a StatefulWidget class along with a function to update the level by one and set the state; the MaterialApp inside a StatelessWidget class; and the main body code inside another StatelessWidget class.
If this isn't the best way to do it please do let me know so I can improve for future projects, thanks.
main.dart
import 'package:flutter/material.dart';
main() => runApp(Start());
/// The Material App
class Start extends StatelessWidget{
#override
Widget build(BuildContext context){
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
backgroundColor: Colors.grey[800],
appBar: AppBar(
title: Text("Home Page"),
backgroundColor: Colors.cyan,
centerTitle: true,
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
backgroundColor: Colors.orange,
child: Icon(Icons.add, color: Colors.black,),
),
body: HomePage(),
),
);
}
}
/// Main Content for the page (body)
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// removed other children so there's less code to scan through for you :)
Padding(
padding: EdgeInsets.fromLTRB(30, 0, 0, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// Text that just says "Level"
Text(
"Level",
style: TextStyle(
color: Colors.orange,
fontWeight: FontWeight.bold,
fontSize: 32,
),
),
// space between text and actual level value
SizedBox(height: 10),
// Create new level widget
Level(),
],
),
),
],
),
);
}
}
/// Updating level using a Stateful Widget
class Level extends StatefulWidget{
#override
State<StatefulWidget> createState(){
return _LevelState();
}
}
class _LevelState extends State<Level>{
int level = 0;
void incrementLevel(){
setState(() {
level += 1;
});
}
#override
Widget build(BuildContext context){
return Text(
"$level",
style: TextStyle(
color: Colors.grey[900],
fontWeight: FontWeight.normal,
fontSize: 28,
),
);
}
}
It actually is a weird way of doing it. However, there is various ways of achieving this
To give an example:
You can use KEYs to remotely redraw the child state
If you want an advanced solution that can assist you in bigger projects. You can use state management tecniques. You can find a lot of tutorials in the internet but these are some of them. BLOC, Provider, InheritedWidget.
Basicaly all of them does the same thing. Lifts up the state data so the place of the redrawn widget on the widget tree will not be important.
I strongly encourage you to watch some tutorials starting with the Provider. I hope this helps
I'm building a simple soundboard app in Flutter, which includes a search bar at the top of the application.
At the point where the search is activated, the interesting bit of the widget tree is as follows:
home: Scaffold(
appBar: AppBar(
title: new TextField(
controller: _filter,
decoration: new InputDecoration(
prefixIcon: new Icon(Icons.search, color: Colors.white),
hintText: 'Search...'
),
As text is typed into the search bar, a listener on the _filter updates a list of quotes which is used to build the body of the app. This is all working fine.
However, I would now like the app to show a count of the returned results, and since this is sitting in my header bar I'd like it to sit in-line with the bar itself, something like:
Things I've tried, all within the InputDecoration:
Setting suffixText - this is styled right and in the right place, however it doesn't update as the _filter changes because I'm not reconstructing the TextField every time (and I can't do this as it messes up what's been typed in).
Setting suffix to a full TextField widget with its own controller. This gets it auto-updating, but for some reason obscures my actual search text. I tried making the background transparent but that hasn't helped - new TextField(controller: _searchCountController, readOnly: true, textAlign: TextAlign.end, style: TextStyle(backgroundColor: Colors.transparent),). In the below screenshot, I've typed a 'w' which has updated the count to 84 (but I can't see the 'w' I've typed...)
Setting counter instead of suffix. I can get this to auto-update and not obscure my search, but it appears under the search bar which makes the whole thing look naff. Doesn't really seem appropriate for something sat in the title bar.
Any suggestions for how I can achieve what I'm after? Very new to Flutter so very possible that I've missed something obvious. Hoping there's a simple solution to this :)
Well, as expected it turned out I was missing something obvious and there was a simple solution. I was having trouble getting a simple suffixText to update because I was ultimately caching a component and therefore not giving Flutter a chance to re-render it.
In case it helps anyone, I had followed this post to implement a search bar. The pattern they use is to store an _appBarTitle widget which only gets changed when search is initiated and cancelled. I moved away from this approach, instead having a simple _searching boolean and allowing flutter to do the rest:
Widget _buildAppBar() {
if (_searching) {
return new TextField(
controller: _filter,
decoration: new InputDecoration(
prefixIcon: new Icon(Icons.search, color: Colors.white),
hintText: 'Search...',
suffixText: '${_filteredQuotes.length}',
),
autofocus: true,
style: TextStyle(color: Colors.white));
} else {
return new Text('Pocket Scat');
}
}
With this approach, I don't need to specify a component for the suffix at all. It also has the advantage of automatically scooping up hintColor from my app's style.
You can copy paste run full code below
You can use Row with Expanded(TextField) and Text()
code snippet
AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: TextField(
controller: _filter,
decoration: InputDecoration(
prefixIcon: Icon(Icons.search, color: Colors.white),
hintText: 'Search...',
)),
),
Text('$_counter'),
],
))
working demo
full code
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
TextEditingController _filter = TextEditingController();
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: TextField(
controller: _filter,
decoration: InputDecoration(
prefixIcon: Icon(Icons.search, color: Colors.white),
hintText: 'Search...',
)),
),
Text('$_counter'),
],
)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}