I am trying to make a button bellow Stepper. The problem is, if I wrap it in a Column or ListView, scrolling in Stepper doesn't work. I tried to wrap them by NestedScrollView, scrolling is working, but the problem is that the button posted above Stepper. There are two example of _MyHomePageState in code, first with ListView and second with NestedView, both do not work for me. How can I implement Stepper with Button under it?
This is what I want
enter image description here
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#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();
}
// 1 case with ListView (doesn't scroll)
class _MyHomePageState extends State<MyHomePage> {
int _currentStep = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
iconTheme: IconThemeData(color: Colors.red),
),
body: Form(
child: ListView(
children: <Widget>[
Stepper(
type: StepperType.vertical,
currentStep: _currentStep,
onStepTapped: (int index) {
setState(() {
_currentStep = index;
});
},
steps: [
Step(
title: Text('Step 1'),
content: Column(
children: <Widget>[
TextFormField(
decoration: InputDecoration(
labelText: 'City', border: UnderlineInputBorder()),
),
TextFormField(
decoration: InputDecoration(labelText: 'Name'),
),
TextFormField(
decoration: InputDecoration(labelText: 'Address'),
),
],
),
isActive: true,
),
Step(
title: Text('Step 2'),
content: Column(
children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: 'City'),
),
TextFormField(
decoration: InputDecoration(labelText: 'Name'),
),
TextFormField(
decoration: InputDecoration(labelText: 'Address'),
),
],
),
isActive: true,
),
Step(
title: Text('Step 3'),
content: Column(
children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: 'Width'),
),
TextFormField(
decoration: InputDecoration(labelText: 'Length'),
),
TextFormField(
decoration: InputDecoration(labelText: 'Height'),
),
TextFormField(
decoration: InputDecoration(labelText: 'Weigth'),
),
],
),
isActive: true,
),
],
),
RaisedButton(
child: Text('Button'),
onPressed: () {},
)
],
),
),
);
}
}
// 2 case with NestedScrollView
class _MyHomePageState extends State<MyHomePage> {
int _currentStep = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
iconTheme: IconThemeData(color: Colors.red),
),
body: Form(
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverList(
delegate: SliverChildListDelegate(<Widget>[
RaisedButton(
child: Text('Button'),
onPressed: () {},
)
]),
),
];
},
body: Stepper(
type: StepperType.vertical,
currentStep: _currentStep,
onStepTapped: (int index) {
setState(() {
_currentStep = index;
});
},
steps: [
Step(
title: Text('Step 1'),
content: Column(
children: <Widget>[
TextFormField(
decoration: InputDecoration(
labelText: 'City', border: UnderlineInputBorder()),
),
TextFormField(
decoration: InputDecoration(labelText: 'Name'),
),
TextFormField(
decoration: InputDecoration(labelText: 'Address'),
),
],
),
isActive: true,
),
Step(
title: Text('Step 2'),
content: Column(
children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: 'City'),
),
TextFormField(
decoration: InputDecoration(labelText: 'Name'),
),
TextFormField(
decoration: InputDecoration(labelText: 'Address'),
),
],
),
isActive: true,
),
Step(
title: Text('Step 3'),
content: Column(
children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: 'Width'),
),
TextFormField(
decoration: InputDecoration(labelText: 'Length'),
),
TextFormField(
decoration: InputDecoration(labelText: 'Height'),
),
TextFormField(
decoration: InputDecoration(labelText: 'Weigth'),
),
],
),
isActive: true,
),
],
),
),
),
);
}
}
All you need is to add
physics: ClampingScrollPhysics(),
property inside Stepper since Listview is a scrollable widget.
I hope it solves your problem
If the button should be persistant on the bottom of the screen you could use the persistentFooterButtons property of the Scaffold widget, or the bottomNavigationBar property with a custom widget.
Related
I need 80 pixel top padding (so AppBar to be shown) for my bottom sheet when keyboard is visible and when keyboard is not visible.
This is my code:
import 'package:flutter/material.dart';
class Temp2Screen extends StatelessWidget {
const Temp2Screen({super.key});
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: MyWidget(),
),
);
}
}
class MyWidget extends StatefulWidget {
MyWidget({Key? key}) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
late ScrollController scrollController;
List<String> messages = [
"msg1", "msg2", "msg3", "msg4", "msg5", "msg6", "msg7", "msg8", "msg9", "msg10",
];
#override
void initState() {
super.initState();
scrollController = new ScrollController();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
showModalBottomSheet(
shape: const RoundedRectangleBorder(
borderRadius:
BorderRadius.vertical(top: Radius.circular(25.0))),
backgroundColor: Colors.yellow,
context: context,
isScrollControlled: true,
builder: (context) => Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: SizedBox(
height: MediaQuery.of(context).size.height - 80,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SingleChildScrollView(
child: Column(
children: [
for (var m in this.messages) ...[
Text(m)
]
],
),
),
TextField(
textAlign: TextAlign.left,
decoration: InputDecoration(
hintText: 'Message',
contentPadding: EdgeInsets.all(10),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
),
isDense: true,
),
keyboardType: TextInputType.multiline,
maxLines: 4,
minLines: 1,
//controller: textController,
textInputAction: TextInputAction.send,
onSubmitted: (value) {
this.setState(() {
this.messages.add(value);
});
},
)
],
),
),
)
);
},
child: const Text('Show Modal Bottom Sheet'),
),
));
}
}
When keyboard is not visible everything is ok (system top panel is visible and AppBar is visible):
However, when keyboard is visible I have a problem as bottom sheet covers both top panel and and AppBar:
Could anyone say how to fix this problem so top panel and AppBar to be visible in both cases (when keyboard is on and when it is off)?
Instead of wrap your whole bottom sheet with padding, try wrap your textField with padding, like this:
builder: (context) => SizedBox(
height: MediaQuery.of(context).size.height - 80,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SingleChildScrollView(
child: Column(
children: [
for (var m in this.messages) ...[Text(m)]
],
),
),
Padding(
padding: EdgeInsets.only(
bottom:
MediaQuery.of(context).viewInsets.bottom),
child: TextField(
textAlign: TextAlign.left,
decoration: InputDecoration(
hintText: 'Message',
contentPadding: EdgeInsets.all(10),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
),
isDense: true,
),
keyboardType: TextInputType.multiline,
maxLines: 4,
minLines: 1,
//controller: textController,
textInputAction: TextInputAction.send,
onSubmitted: (value) {
this.setState(() {
this.messages.add(value);
});
},
),
)
],
),
));
The widget is from the package 'flutter_platform_widgets'
As you can see, the floating icon that shows input location doesn't go behind app bar when screen is scrolled down. I need to either get rid of it completely or at least have it disappear properly. I tried dismissing the keyboard on screen drag, but it gets a bit annoying as all the content fits on one screen, it isn't actually scrollable (scrollview necessary because there is content behind the keyboard and I was getting pixel overflow errors without it).
Some code if that helps
EDIT:
This is the widget seen on the first two screenshots.
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 16.0),
child: Form(
key: widget.formKey,
child: SingleChildScrollView(
// keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
child: Column(
children: [
EmailTextFormField(
controller: widget.emailController,
),
PasswordTextFormField(
labelText: AppLocalizations.of(context)!.password,
controller: widget.passwordController,
),
PasswordTextFormField(
labelText: AppLocalizations.of(context)!.confirmPassword,
controller: widget.passwordConfirmationController,
password: widget.passwordController,
),
Padding(
padding: const EdgeInsets.only(top: 20.0),
child: Button(
text: AppLocalizations.of(context)!.next,
onPressed: () {
FocusScope.of(context).unfocus();
_validateLoginInformation();
},
),
),
,
SizedBox(
height: MediaQuery.of(context).size.height / 2.75,
),
const TermsOfUse(),
],
),
),
),
);
}
This is the emailtextformfield. The code for passwordtextformfield is nearly identical.
#override
Widget build(BuildContext context) {
return Row(
children: [
Padding(
padding: const EdgeInsets.only(right: 16.0),
child: Icon(
Icons.mail_outline,
color: iconColor,
),
),
Expanded(
child: AutofillGroup(
child: PlatformTextFormField(
hintText: AppLocalizations.of(context)!.email,
controller: widget.controller,
focusNode: _focusNode,
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
autofillHints: const [AutofillHints.email],
onFieldSubmitted: (_) => _focusNode.nextFocus(),
validator: (email) {
if (email == null || email.isEmpty) {
return AppLocalizations.of(context)!.emailIsNull;
} else if (!EmailValidator.validate(email)) {
return AppLocalizations.of(context)!.emailInvalid;
} else {
return null;
}
},
material: (_, __) => MaterialTextFormFieldData(
decoration: InputDecoration(
floatingLabelBehavior: FloatingLabelBehavior.auto,
labelText: AppLocalizations.of(context)!.email,
suffixIcon: widget.controller.text.isNotEmpty
? ClearTextFieldIcon(controller: widget.controller)
: null,
),
),
cupertino: (_, __) => CupertinoTextFormFieldData(
textInputAction: TextInputAction.next,
focusNode: _focusNode,
decoration: BoxDecoration(
color: CupertinoColors.extraLightBackgroundGray,
borderRadius: BorderRadius.circular(10.0),
),
),
),
),
),
],
);
}
I think it is not possible to remove it. But you can make it invisible by setting its color to transparent.
You can set the color globally:
textSelectionTheme: const TextSelectionThemeData(
selectionHandleColor: Colors.transparent,
),
main.dart
#override
Widget build(BuildContext context) => MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
textSelectionTheme: const TextSelectionThemeData(
selectionHandleColor: Colors.transparent,
),
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
I also tried to set it locally using the Theme widget, but it seems buggy.
You can also access the complete source code through GitHub.
If you have further questions, please don't hesitate to write in the comments.
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
class Reservation extends StatelessWidget {
#override
Widget build(BuildContext context) {
final appTitle = 'Car Reservation';
return MaterialApp(
title: appTitle,
home: Scaffold(
appBar: AppBar(
title: Text(appTitle),
),
body: MyCustomForm(),
),
);
}
}
// Create a Form widget.
class MyCustomForm extends StatefulWidget {
#override
MyCustomFormState createState() {
return MyCustomFormState();
}
}
// Create a corresponding State class. This class holds data related to the form.
class MyCustomFormState extends State<MyCustomForm> {
// Create a global key that uniquely identifies the Form widget
// and allows validation of the form.
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
// Build a Form widget using the _formKey created above.
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextFormField(
decoration: const InputDecoration(
icon: const Icon(Icons.person),
hintText: 'Enter your name',
labelText: 'Name',
),
),
TextFormField(
decoration: const InputDecoration(
icon: const Icon(Icons.phone),
hintText: 'Enter a phone number',
labelText: 'Phone',
),
),
TextFormField(
decoration: const InputDecoration(
icon: const Icon(Icons.car_repair_outlined),
hintText: 'Enter your car plate',
labelText: 'Plate Number',
),
),
TextFormField(
decoration: const InputDecoration(
icon: const Icon(Icons.punch_clock),
hintText: 'How many hours',
labelText: 'Hours',
),
),
TextFormField(
decoration: const InputDecoration(
icon: const Icon(Icons.space_bar),
hintText: '',
labelText: 'Reservation Spot create as ListTile',
),
),
ListView(
children: const <Widget>[
Card(child: ListTile(title: Text('One-line ListTile'))),
Card(
child: ListTile(
leading: FlutterLogo(),
title: Text('One-line with leading widget'),
),
),
Card(
child: ListTile(
title: Text('One-line with trailing widget'),
trailing: Icon(Icons.more_vert),
),
),
Card(
child: ListTile(
leading: FlutterLogo(),
title: Text('One-line with both widgets'),
trailing: Icon(Icons.more_vert),
),
),
Card(
child: ListTile(
title: Text('One-line dense ListTile'),
dense: true,
),
),
Card(
child: ListTile(
leading: FlutterLogo(size: 56.0),
title: Text('Two-line ListTile'),
subtitle: Text('Here is a second line'),
trailing: Icon(Icons.more_vert),
),
),
Card(
child: ListTile(
leading: FlutterLogo(size: 72.0),
title: Text('Three-line ListTile'),
subtitle: Text(
'A sufficiently long subtitle warrants three lines.'
),
trailing: Icon(Icons.more_vert),
isThreeLine: true,
),
),
],
),
new Container(
padding: const EdgeInsets.only(left: 40.0, top: 40.0),
child: new RaisedButton(
child: const Text('Submit'),
onPressed: (){},
)),
],
),
);
}
}
The app won't show after I add the list tile inside, can anyone tell me what went wrong?
Here is after I add my list tile code inside
And here's before I add the list tile code inside
I am wondering if anyone can help me with this one. I still don't understand the flow of it, to be honest.
The issue is coming after adding ListView inside Column widget. You can wrap ListView with Expanded.
Expanded(
child: ListView(
children: const <Widget>[
...
I will encourage you to check Unbounded height / width | Decoding Flutter.
I have created a addNewProducts function which contains my showModalBottomSheet. I used for my Add a new product form I add a Form which contains different TextFormField for taking input value. When I click on my floatingActionButton it shows the form but when I want to write something the input keyboard cover the Form area so I can not add anything. it's not scrolling.
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
class UserScreen extends StatefulWidget {
UserScreen({Key? key}) : super(key: key);
#override
_UserScreenState createState() => _UserScreenState();
}
class _UserScreenState extends State<UserScreen> {
void addNewProducts(BuildContext ctx) {
showModalBottomSheet(
context: ctx,
builder: (_) {
return GestureDetector(
onTap: () {},
behavior: HitTestBehavior.opaque,
child: Card(
child: SingleChildScrollView(
child: Form(
child: Column(
children: <Widget>[
TextFormField(
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Product Name',
icon: Icon(Icons.insert_chart)),
),
TextFormField(
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Product Description',
icon: Icon(Icons.description)),
),
TextFormField(
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Minimum Bid Price',
icon: Icon(Icons.price_check)),
),
TextFormField(
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Auction End DateTime',
icon: Icon(Icons.data_saver_off)),
),
SizedBox(
height: 20,
),
ElevatedButton(
onPressed: () {},
child: Text('Add Product'),
),
],
),
),
),
),
);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('User Screen'),
actions: [
DropdownButton(
items: [
DropdownMenuItem(
child: Container(
child: Row(
children: <Widget>[
Icon(
Icons.exit_to_app,
color: Theme.of(context).indicatorColor,
),
SizedBox(
width: 8,
),
Text('Logout')
],
),
),
value: 'logout',
),
],
onChanged: (itemIdentifire) {
if (itemIdentifire == 'logout') {
FirebaseAuth.instance.signOut();
}
},
)
],
),
body: Card(),
// ListView.builder(
// itemCount: 10,
// itemBuilder: (ctx, index) => Container(
// padding: EdgeInsets.all(8),
// child: Text('This Works!'),
// ),
// ),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
addNewProducts(context);
},
),
);
}
}
I have changed my Column to ListView now its perfectly work and its now scrollable.
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/rendering.dart';
class UserScreen extends StatefulWidget {
UserScreen({Key? key}) : super(key: key);
#override
_UserScreenState createState() => _UserScreenState();
}
class _UserScreenState extends State<UserScreen> {
bool isFabVisibale = false;
void addNewProducts(BuildContext ctx) {
showModalBottomSheet(
isScrollControlled: true,
context: ctx,
builder: (_) {
return GestureDetector(
onTap: () {},
behavior: HitTestBehavior.opaque,
child: Form(
child: ListView(
children: <Widget>[
TextFormField(
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Product Name',
icon: Icon(Icons.insert_chart)),
),
TextFormField(
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Product Description',
icon: Icon(Icons.description)),
),
TextFormField(
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Minimum Bid Price',
icon: Icon(Icons.price_check)),
),
TextFormField(
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Auction End DateTime',
icon: Icon(Icons.data_saver_off)),
),
SizedBox(
height: 20,
),
ElevatedButton(
onPressed: () {},
child: Text('Add Product'),
),
],
),
),
);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('User Screen'),
actions: [
DropdownButton(
items: [
DropdownMenuItem(
child: Container(
child: Row(
children: <Widget>[
Icon(
Icons.exit_to_app,
color: Theme.of(context).indicatorColor,
),
SizedBox(
width: 8,
),
Text('Logout')
],
),
),
value: 'logout',
),
],
onChanged: (itemIdentifire) {
if (itemIdentifire == 'logout') {
FirebaseAuth.instance.signOut();
}
},
),
// DropdownButton(
// items: [
// DropdownMenuItem(
// child: Container(
// child: Row(
// children: <Widget>[
// Icon(
// Icons.add,
// color: Theme.of(context).indicatorColor,
// ),
// SizedBox(
// width: 8,
// ),
// Text('Add New Items')
// ],
// ),
// ),
// value: 'AddProduct',
// ),
// ],
// onChanged: (itemIdentifire) {
// if (itemIdentifire == 'AddProduct') {
// addNewProducts(context);
// }
// },
// ),
],
),
body: NotificationListener<UserScrollNotification>(
onNotification: (notification) {
if (notification.direction == ScrollDirection.forward) {
setState(() {
isFabVisibale = true;
});
}
if (notification.direction == ScrollDirection.reverse) {
setState(() {
isFabVisibale = false;
});
}
return true;
},
child: ListView.builder(
itemCount: 100,
itemBuilder: (ctx, index) => Container(
padding: EdgeInsets.all(8),
child: Text('This Works!'),
),
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: isFabVisibale
? FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
addNewProducts(context);
},
)
: null,
);
}
}
im trying to use an Editable text widget in Flutter and i cant select, copy, cut and paste don't work with it, i don't know why this is happening I've enabled everything that is related to selecting and copy pasting, but it still doesn't work
class EditProfile extends StatefulWidget {
#override
_EditProfileState createState() => _EditProfileState();
}
class _EditProfileState extends State<EditProfile> {
var _controller = TextEditingController();
TextSelectionControls _mainContentSelectionController;
#override
Widget _backButton() {
return InkWell(
onTap: () {
Navigator.pop(context);
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Row(
children: <Widget>[
Icon(
Icons.clear,
size: 30.0,
color: Color(0xff8729e6),
)
],
),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
color: Colors.white,
child: EditableText(
controller: _controller,
toolbarOptions: ToolbarOptions(
paste: true,
copy: true,
selectAll: true,
),
readOnly : false,
focusNode: FocusNode(),
cursorColor: Colors.blue,
style: TextStyle(
color: Colors.black
),
keyboardType: TextInputType.text,
autocorrect: false,
autofocus: false,
backgroundCursorColor: Colors.red,
maxLines: 10,
minLines: 1,
enableInteractiveSelection: true,
selectionColor: Colors.red,
selectionControls: _mainContentSelectionController,
),
)
],
)
),
),
);
}
}
i hope this code is enough, please let me know if you want me to provide more code or if you have any questions, Thank you!
Try Selectable Text instead of simple text widget. more info here.
You can use TextField instead of Editable Text, this code works now.
import 'package:flutter/material.dart';
class EditProfile extends StatefulWidget {
#override
_EditProfileState createState() => _EditProfileState();
}
class _EditProfileState extends State<EditProfile> {
var _controller = TextEditingController();
TextSelectionControls _mainContentSelectionController;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: MediaQuery.of(context).size.height * 0.1,
color: Colors.white,
child: TextField(
controller: _controller,
toolbarOptions: ToolbarOptions(
paste: true,
cut: true,
copy: true,
selectAll: true,
),
readOnly: false,
focusNode: FocusNode(),
cursorColor: Colors.blue,
style: TextStyle(color: Colors.black),
keyboardType: TextInputType.text,
autocorrect: false,
autofocus: false,
maxLines: 10,
minLines: 1,
enableInteractiveSelection: true,
),
)
],
)),
),
);
}
}
As per this answer, EditableText is rarely used.