Flutter vertical scroll inside a horizontal PageView [duplicate] - flutter

This question already has an answer here:
Combine SingleChildScrollView and PageView in Flutter
(1 answer)
Closed 3 years ago.
I added 2 Form widgets to a PageView. Each form has 6 TextFormField. When I tap on the last 2 TextFormField from either Form, the keyboard shows up over the fields and hides them to the user.
I tried using a SingleChildScrollView inside PageView, with no luck.
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomPadding: false,
body: PageView(
children: <Widget>[
_sampleForm(),
_sampleForm(),
],
),
)
}
_sampleForm(){
return Container(
margin: const EdgeInsets.fromLTRB(0, 0, 0, 10),
width: MediaQuery.of(context).size.width,
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Form(
child: Column(
children: <Widget>[
TextFormField(...),
TextFormField(...),
TextFormField(...),
TextFormField(...),
TextFormField(...),
TextFormField(...),
],
),
),
],
),
),
);
}
What I need is to automatically scroll up when tapping the TextFormFields, and also be able to swipe a PageView horizontally,

try this approach. I have used a ListView instead of [SingleChildScrollView, Column].
Here the code.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SampleScreen(),
);
}
}
class SampleScreen extends StatefulWidget {
#override
_SampleScreenState createState() => _SampleScreenState();
}
class _SampleScreenState extends State<SampleScreen> {
List<FocusNode> _focusNodes = List.generate(7, (_) => FocusNode());
#override
void dispose() {
_focusNodes.forEach((_) => _.dispose());
super.dispose();
}
setFocus(BuildContext context, {FocusNode focusNode}) {
FocusScope.of(context).requestFocus(focusNode ?? FocusNode());
}
_sampleForm1() {
return Form(
child: ListView(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 10),
children: <Widget>[
TextFormField(
focusNode: _focusNodes[0],
decoration: InputDecoration(labelText: "Flutter 1"),
onFieldSubmitted: (_) => setFocus(context, focusNode: _focusNodes[1]),
textInputAction: TextInputAction.next,
),
TextFormField(
focusNode: _focusNodes[1],
decoration: InputDecoration(labelText: "Flutter 2"),
onFieldSubmitted: (_) => setFocus(context, focusNode: _focusNodes[2]),
textInputAction: TextInputAction.next,
),
TextFormField(
focusNode: _focusNodes[2],
decoration: InputDecoration(labelText: "Flutter 3"),
onFieldSubmitted: (_) => setFocus(context, focusNode: _focusNodes[3]),
textInputAction: TextInputAction.next,
),
TextFormField(
focusNode: _focusNodes[3],
decoration: InputDecoration(labelText: "Flutter 4"),
onFieldSubmitted: (_) => setFocus(context, focusNode: _focusNodes[4]),
textInputAction: TextInputAction.next,
),
TextFormField(
focusNode: _focusNodes[4],
decoration: InputDecoration(labelText: "Flutter 5"),
onFieldSubmitted: (_) => setFocus(context, focusNode: _focusNodes[5]),
textInputAction: TextInputAction.next,
),
TextFormField(
focusNode: _focusNodes[5],
decoration: InputDecoration(labelText: "Flutter 6"),
onFieldSubmitted: (_) => setFocus(context, focusNode: _focusNodes[6]),
textInputAction: TextInputAction.next,
),
TextFormField(
focusNode: _focusNodes[6],
decoration: InputDecoration(labelText: "Flutter 7"),
onFieldSubmitted: (_) => setFocus(context),
textInputAction: TextInputAction.done,
),
],
),
);
}
_sampleForm2() {
return Form(
child: ListView(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 10),
children: <Widget>[
TextFormField(decoration: InputDecoration(labelText: "Flutter 1")),
TextFormField(decoration: InputDecoration(labelText: "Flutter 2")),
TextFormField(decoration: InputDecoration(labelText: "Flutter 3")),
TextFormField(decoration: InputDecoration(labelText: "Flutter 4")),
TextFormField(decoration: InputDecoration(labelText: "Flutter 5")),
TextFormField(decoration: InputDecoration(labelText: "Flutter 6")),
TextFormField(decoration: InputDecoration(labelText: "Flutter 7")),
],
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: PageView(
children: <Widget>[
_sampleForm1(),
_sampleForm2(),
],
),
);
}
}
Hope it helps :)

Related

Many "RenderBox was not laid out" for a Row in a Column

The many solutions that are already present for the same error do not seem to help in my case, thus the question.
I get a bunch of "RenderBox was not laid out" errors while my Scaffold body remains empty. The error occurs because of the Row widget in my Column widget. When removing the Row widget the error does not occur anymore.
I tried wrapping the Row widget in an Expanded as well as SizedBox or Flexible but none helped. I also tried the same with the parent Column widget even both at the same time with different combinations.
When looking at the Widget Details Tree in the Flutter Inspector the renderObject of the Padding at the top has size: MISSING but constraints are finite constraints: BoxConstraints(0.0<=w<=392.7, 0.0<=h<=413.8).
Same goes for the child Column widget with the following constraints: constraints: BoxConstraints(0.0.<=w<=340.7, 0.0<=h<=361.8).
The child Row widget then also has size: MISSING but constraints: BoxConstraints(0.0<=w<=340.7, 0.0<=h<=Infinity).
I thought that the child widgets inherit the constraints of the parent? And as I said wrapping the Row with Expanded, SizedBox or Flexbox did not help except I did it wrong.
I would be glad to get some answers and a solution. Thank you in advance!
The exact error messages can be found here: https://pastebin.com/n3RFbasT
My code:
class AddBalance extends StatefulWidget {
const AddBalance({Key? key}) : super(key: key);
#override
State<AddBalance> createState() => _AddBalanceState();
}
class _AddBalanceState extends State<AddBalance> {
final _formKey = GlobalKey<FormState>();
bool subtractSwitch = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0,
title: const Text("Add Entry"),
),
body: Padding(
padding: const EdgeInsets.all(26.0),
child: Form(
autovalidateMode: AutovalidateMode.always,
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
autofocus: true,
maxLines: 1,
textInputAction: TextInputAction.next,
decoration: const InputDecoration(
labelText: "Name",
),
),
Row(
children: [
TextFormField(
textInputAction: TextInputAction.next,
keyboardType: TextInputType.number,
maxLength: 10,
maxLines: 1,
decoration: const InputDecoration(
labelText: "Amount",
),
),
const Text("Subtract"),
Switch(
value: subtractSwitch,
onChanged: (value) {
setState(() {
subtractSwitch = value;
});
}),
],
)
],
),
),
),
);
}
}
Just need to wrap your Row with any Widget that give you boundaries like Center, Align , SizedBox, etc.
In this case I used Align and ... don't forget to use Expanded over your TextFormField inside the Row.
Scaffold(
appBar: AppBar(
elevation: 0,
title: const Text("Add Entry"),
),
body: Padding(
padding: const EdgeInsets.all(26.0),
child: Form(
autovalidateMode: AutovalidateMode.always,
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
autofocus: true,
maxLines: 1,
textInputAction: TextInputAction.next,
decoration: const InputDecoration(
labelText: "Name",
),
),
Align(
child: Row(
children: [
Expanded(
child: TextFormField(
textInputAction: TextInputAction.next,
keyboardType: TextInputType.number,
maxLength: 10,
maxLines: 1,
decoration: const InputDecoration(
labelText: "Amount",
),
),
),
const Text("Subtract"),
Switch(
value: subtractSwitch,
onChanged: (value) {
setState(() {
subtractSwitch = value;
});
}),
],
),
)
],
),
),
),
)

Error text increases TextFormField height which leads to overflow

So i have a few TextFormFields inside scrollable column and it works fine, until error text appears. Because it causes bottom overflow. The desired behavior is button that just jumps up and don’t cause overflow. I have tried to put textfield inside a container with fixed height but after error text appear it just shrinked textfield
Form _buildForm(BuildContext context, BoxConstraints constraints) {
print(constraints);
return Form(
key: _form,
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minWidth: constraints.maxWidth, minHeight: constraints.maxHeight),
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(top: 48, bottom: 32),
child: Text(
'Sign Up',
style: AppTheme.theme.textTheme.headline3
.copyWith(color: AppColors.black),
),
),
LabeledTextField(
margin: EdgeInsets.only(bottom: 16),
fieldName: 'Email',
decoration: InputDecoration(hintText: 'example#mail.com'),
textInputAction: TextInputAction.next,
onFieldSubmitted: (_) {
FocusScope.of(context).requestFocus(_userNameFocusNode);
},
onSaved: (val) => _authData["login"] = val,
validator: FormValidators.emailValidator,
autovalidateMode: AutovalidateMode.onUserInteraction,
),
LabeledTextField(
margin: EdgeInsets.only(bottom: 16),
fieldName: 'User Name',
decoration: InputDecoration(hintText: 'alexexample...'),
textInputAction: TextInputAction.next,
onFieldSubmitted: (_) {
FocusScope.of(context).requestFocus(_passwordFocusNode);
},
onSaved: (val) => _authData["username"] = val,
validator: FormValidators.isRequiredValidator,
autovalidateMode: AutovalidateMode.onUserInteraction,
),
LabeledTextField(
fieldName: 'Password',
focusNode: _passwordFocusNode,
obscureText: true,
decoration: InputDecoration(hintText: 'Type in...'),
onSaved: (val) => _authData["password"] = val,
validator: FormValidators.passwordValidator,
autovalidateMode: AutovalidateMode.onUserInteraction,
),
Spacer(),
AuthButtons(
mode: AuthMode.signup,
saveForm: _saveForm,
),
],
),
),
),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
//resizeToAvoidBottomInset: false,
appBar: CustomAppBar(
preferredSize: Size.fromHeight(64),
title: 'title',
),
body: Padding(
padding: const EdgeInsets.only(left: 16, right: 16),
child: LayoutBuilder(
builder: (context, constraints) => _buildForm(context, constraints),
),
),
);
}
LabeledTextField is basically just a textFormField with Text widget before it
class _LabeledTextFieldState extends State<LabeledTextField> {
#override
Widget build(BuildContext context) {
return Container(
margin: widget.margin,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.fieldName,
style: AppTheme.theme.textTheme.caption
.copyWith(color: AppColors.darkGrey),
),
TextFormField(
initialValue: widget.initialValue,
textInputAction: widget.textInputAction,
focusNode: widget.focusNode,
onSaved: widget.onSaved,
onChanged: widget.onChanged,
validator: widget.validator,
onFieldSubmitted: widget.onFieldSubmitted,
autovalidateMode: widget.autovalidateMode,
obscureText: widget.obscureText,
decoration: widget.decoration,
),
],
),
);
}
}
You can try expanded instead of a container as a parent widget or use wrap as a parent widget on your text field.
Wrap your Column with SingleChildScrollView
class _LabeledTextFieldState extends State<LabeledTextField> {
#override
Widget build(BuildContext context) {
return Container(
margin: widget.margin,
child: SingleChildScrollView(
physics: BouncingScrollPhysics(), // u can ignore this if u want
child : Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.fieldName,
style: AppTheme.theme.textTheme.caption
.copyWith(color: AppColors.darkGrey),
),
TextFormField(
initialValue: widget.initialValue,
textInputAction: widget.textInputAction,
focusNode: widget.focusNode,
onSaved: widget.onSaved,
onChanged: widget.onChanged,
validator: widget.validator,
onFieldSubmitted: widget.onFieldSubmitted,
autovalidateMode: widget.autovalidateMode,
obscureText: widget.obscureText,
decoration: widget.decoration,
),
],
),
),
);
}
}

Flutter Image.Network not rebuiling/triggering api call when url value is changed

The title essentially sums up the issue I'm having. In the code below you can see that I have a text field controller assigned to the URL input. I'm wanting the effect to be that when the user finishes entering a URL the image is automatically updated. In my case, the image never updates after the field value is changed. However, the image does change once a hot reload is performed. Only then is the desired effect achieved. What's going on here? I would assume that on every change to the Text controllers value that the entire form would be rebuilt(which I do see when I enable rainbow flag in dev tools) except for the image widget. Why is this happening?
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class EditProductPage extends StatefulWidget {
static const routeName = '/edit-product-page';
#override
_EditProductPageState createState() => _EditProductPageState();
}
class _EditProductPageState extends State<EditProductPage> {
final _urlTextController = TextEditingController();
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Edit Product'),
),
body: Card(
elevation: 5,
child: Padding(
padding: const EdgeInsets.all(15),
child: Form(
child: ListView(
children: [
TextFormField(
decoration: InputDecoration(labelText: 'Title'),
textInputAction: TextInputAction.next,
),
TextFormField(
decoration: InputDecoration(labelText: 'Price'),
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
),
TextFormField(
decoration: InputDecoration(labelText: 'Description'),
maxLines: 3,
keyboardType: TextInputType.multiline,
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Container(
width: 100,
height: 100,
margin: EdgeInsets.only(
top: 8,
right: 10,
),
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: Colors.grey,
),
),
child: _urlTextController.text.isEmpty
? Text('Enter a URL')
: FittedBox(
child: Image.network(
_urlTextController.text,
fit: BoxFit.cover,
key: ValueKey(_urlTextController.text),
),
),
),
Expanded(
child: TextFormField(
decoration: InputDecoration(labelText: 'Image URL'),
keyboardType: TextInputType.url,
textInputAction: TextInputAction.done,
controller: _urlTextController,
),
),
],
),
],
),
),
),
),
);
}
}
You need to change state every time user inputs something to rebuild form with new values
#override
void initState() {
_urlTextController.addListener(onUrlChanged);
super.initState();
}
void onUrlChanged() {
setState(() {});
}
#override
void dispose() {
_urlTextController.removeListener(onUrlChanged);
super.dispose();
}
Add onChanged callback which will trigger after adding anything inside TextFormField
Expanded(
child: TextFormField(
decoration: InputDecoration(labelText: 'Image URL'),
keyboardType: TextInputType.url,
textInputAction: TextInputAction.done,
controller: _urlTextController,
onChanged: (value) {
if (value.contains('.png')) { // Add ||(or) condition like this for other extension
setState(() {});
}
},
),
),

Remove scrollbar at right from multiline TextField - Flutter

I have TextField purchaseCommentField() within the structure as below:
#override
Widget build(BuildContext context)
{
return GestureDetector(
onTap: CommonUtils.endEditing(context),
child: Container(
width: _width,
color: Colors.white,
child: SingleChildScrollView(
child: Column(
children: <Widget>[
....
...
Visibility(
visible: _additionalInfo != null,
child: purchaseCommentField()
),
rowSpacer(16.0),
actionButton(context)
],
)
)
),
);
}
Widget purchaseCommentField()
{
return TextField(
controller: _purchaseCommentController,
minLines: 1,
maxLines: null,
keyboardType: TextInputType.multiline,
style: new TextStyle(fontSize: 14.0),
decoration: new InputDecoration(
labelText: 'Additional Info',
border: OutlineInputBorder(),
),
);
}
When I focus textfield, I got unwanted scrollbar at right inside the field:
This issue occured when I set fontSize:14.0. When I remove that or set fontSize to 16.0 then there is no scrollbar at right:
Is there a way to remove that scrollbar in textfield ?
try using TextFormField,
TextFormField(
controller: textController,
validator: (value) {
if (value.trim().isEmpty) {
return _reportTypeModel.language.msgEnterDesc;
}
return null;
},
style: new TextStyle(fontSize: 14.0),
maxLength: 500,
decoration: InputDecoration(
labelText: _reportTypeModel.language.description,
),
minLines: 4,
maxLines: 6,
keyboardType: TextInputType.multiline,
textInputAction: TextInputAction.next,
),
Output:
There's an open issue on github regarding this.
For now you can fix this by creating a custom ScrollBehavior and overriding buildScrollbar method:
class CustomScrollBehavior extends ScrollBehavior {
const CustomScrollBehavior();
#override
Widget buildScrollbar(context, child, details) {
return child;
}
}
And then wrap your TextField with ScrollConfiguration widget:
return ScrollConfiguration(
behavior: const CustomScrollConfiguration(),
child: TextField(
// Your text-field params
),
);
This works for TextFormField too.

Flutter: Stepper doesn't scrolling

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.