I am working with a NestedScrollView and a SliverAppBar and need to make adjusments to the expandedHeight: value of the SliverAppBar based on the height of a widget hosting some text. Currently the expandedHeight: is 400.0. However, because of the text widget the 400.0 value may not always render an appropriate design. So I want to make the expandedHeight: relative by adding the height of the text widget to the static 400.0 value.
Like this:
expandedHeight: 400.0 + textWidgetHeight
I need some help getting only the height value of the widget as a double.
This is the code for the widget:
Container _textWidget(String text) {
return Container(
width: 180.0,
margin: EdgeInsets.only(
top: 15.0,
),
child: Text(
text,
textAlign: TextAlign.center,
style: TextStyle(
color: AppTheme.define().hintColor,
fontFamily: 'RobotoMedium',
fontSize: 10.0,
),
),
);
}
you can do this, using a combination of context.size and SchedulerBinding.instance.addPostFrameCallback.
SchedulerBinding.instance.addPostFrameCallback is used inside the build method of a Widget or State to schedule a callback function once the rendering of the current frame is completed.
So, first make your _textWidget into a new StatelessWidget,
class TextWidget extends StatelessWidget {
String text;
var onTextBuild;
TextWiidget(this.text, this.onTextBuild);
Widget build(BuildContext context) {
SchedulerBinding.instance?.addPostFrameCallback((timeStamp) {
// This will be called once this frame is built.
onTextBuild(context.size); // context.size gives the Size that this widget ha occupied.
});
return Container(
width: 180.0,
margin: EdgeInsets.only(top: 15.0),
child: Text(
text,
textAlign: TextAlign.center,
style: TextStyle(
color: AppTheme.define().hintColor,
fontFamily: 'RobotoMedium',
fontSize: 10.0,
),
),
);
}
}
Now, when you are using your TextWidget, ensure it's parent is a StatefulWidget and create a new state variable,
Size textWidgetSize = Size(0, 0);
And while using your TextWidget, use it like this to get the size and update state.
....
TextWidget(text, (size) {
setState(() {
textWidgetSize = size;
});
}),
....
Then, in your SliverAppBar, you can use your new state variable like this,
expandedHeight: 400 + (textWidgetSize != null ? textWidgetSize.height : 0),
Related
I have an issue with Scaffold. I have an AppBar with text and a button. The body hosts a ListView.builder. Inside the ListView is a container and a number of TextButton Widgets.
The Text part of the TextButton can be wide, and causes the warning bar to appear. So, I wrapped in an Expanded widget to wrap it.
This results in an error...
The following assertion was thrown during performLayout(): RenderFlex
children have non-zero flex but incoming width constraints are
unbounded.
When a row is in a parent that does not provide a finite width
constraint, for example if it is in a horizontal scrollable, it will
try to shrink-wrap its children along the horizontal axis. Setting a
flex on a child (e.g. using Expanded) indicates that the child is to
expand to fill the remaining space in the horizontal direction. These
two directives are mutually exclusive. If a parent is to shrink-wrap
its child, the child cannot simultaneously expand to fit its parent.
Consider setting mainAxisSize to MainAxisSize.min and using
FlexFit.loose fits for the flexible children (using Flexible rather
than Expanded). This will allow the flexible children to size
themselves to less than the infinite remaining space they would
otherwise be forced to take, and then will cause the RenderFlex to
shrink-wrap the children rather than expanding to fit the maximum
constraints provided by the parent.
and also the contents of my AppBar disappear, and I can no longer click the back arrow.
Here is the scaffold to build the page...
late StateSetter _setState;
#override
Widget build(BuildContext context) {
return buildSubFormPage();
}
Widget buildSubPage() {
return StatefulBuilder(
builder: (bContext, StateSetter setBState) {
_setState = setBState;
return Scaffold(
appBar: new AppBar(
title: new Text("My list of items"),
actions: [
Padding(
padding: EdgeInsets.only(
right: 20.0),
child: openSubFormItem(
widget.field, 0,
"+ Add new", null),
)
],
),
body: ListView.builder(
padding: EdgeInsets.all(8.0),
//shrinkWrap: true,
itemCount: subFormItems.length,
itemBuilder: (context, index) {
return Container(
//width: screenWidth,
child: openSubFormItem(
widget.field, index, "Press Here",
subFormItems[index]),
);
},
)
);
}
);
}
Here is mostly the content of my openSubFormItem widget.
The building of the button text has been removed, but you can get the idea...
Widget openSubFormItem(PendigoFields field, int index, String buttonText, dynamic subFormItem) {
String outputText = "A long string of text, that will surely cause the warning bars if I can't make it wrap properly";
if (buttonText == "+ Add new") {
outputText = buttonText;
}
return Container(
//padding: EdgeInsets.all(4),
//width: screenWidth - 30,
child:
TextButton(
child: Row(
mainAxisSize: MainAxisSize.max,
//mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Flexible(
// fit : FlexFit.loose,
// child: Text(outputText, style: TextStyle(color: Colors.black,
// fontSize: 15.0,
// fontWeight: FontWeight.bold,),),
// ),
Expanded(child: Text(outputText, style: TextStyle(color: Colors.black,
fontSize: 15.0,
fontWeight: FontWeight.bold,),),
),
// Text(outputText, style: TextStyle(color: Colors.black,
// fontSize: 15.0,
// fontWeight: FontWeight.bold,),),
// I also have an IconButton here, removed for clarity
]
),
)
decoration: buttonText == "Press Here" ? BoxDecoration(
border: Border(
bottom: BorderSide(color: Colors.purple[400]!)),
)
: null
);
}
How do I get my AppBar to show the AppBar content (and button) and allow my ListView to be able to wrap the text if the text is too long?
As the error says: unbounded width, this is because the Row parent which is the TextButton does not have a specified width, and when using Expanded, the Row or the Column parent of it must know its constrains.
Give a width to the TextButton.
Also a note, don't create your widgets using methods as this is not recommend from flutter team, use separated widgets instead.
In the screen, I have a Column, it has a cusotm made widget of specific height. Then, I have Expanded, in which I have a TabBar which has three tabs.
In one of those tabs, I want to show a list. First, I have a padding, which contains column. The column has some text, which should remain at top and the list should be shown in the space which is remaining. I am using Expanded for that, but it is not working.
I can't use ListView directly, and also can't use expanded. It is only working when I am giving it a container of fix size. Now, in different screens, it will look different. So, I want to take all of the remaining space and build the list there. Code for reference -
Here is the doubts screen, which is one of the tabs of main screen -
import 'package:flutter/material.dart';
import 'package:my_board_plus/size_config.dart';
import 'package:my_board_plus/styles.dart';
import '../../api_handling/api_fetch/fetch_doubt_questions.dart';
import '../../data_models/doubt_question_model.dart';
class NewDoubtsScreen extends StatefulWidget {
const NewDoubtsScreen({Key? key}) : super(key: key);
#override
State<NewDoubtsScreen> createState() => _NewDoubtsScreenState();
}
class _NewDoubtsScreenState extends State<NewDoubtsScreen> {
late Future<List<DoubtQuestionModel>> doubtQuestionsList;
#override
void initState() {
doubtQuestionsList = fetchDoubtQuestion();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: backgroundColor2,
floatingActionButton: Container(
width: getProportionateScreenWidth(130),
height: getProportionateScreenHeight(50),
decoration: BoxDecoration(
color: brandPurple,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.all(Radius.circular(20)),
),
child: Center(
child: Text(
'? My Doubts',
style: TextStyle(
color: Colors.white,
fontSize: 15,
),
),
),
),
body: Padding(
padding: EdgeInsets.only(top: 8.0, left: 5),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Trending Doubts',
style: TextStyle(
color: Colors.white,
),
),
Text(
'View all',
style: TextStyle(
color: brandYellow,
decoration: TextDecoration.underline
),
),
],
),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Container(
height: getProportionateScreenHeight(530),
width: double.infinity,
color: Colors.red,
),
),
],
),
),
);
}
}
The red area that you are seeing is the one. I want it to occupy whole area available in the phone screen, so I can show list in it which should be scrollable. In this case, it is occupying all, but in different screens, it might not. So, please give me some suggestions.
You can try to give the height of the container like
height: double.infinity
Or you can give the height of it with substracting the other height from the screen size like
height: MediaQuery.of(context).size.height - getProportionateScreenHeight(50) //the heigth size that you give the other widget that top of it
try to wrap your Padding widget with the Expanded widget,
Expanded widget in column will take the rest of the space of the screen.
also no need to give height to Container widget, so you can remove getProportionateScreenHeight(530) this one
I have this issue of rebuilding widget when The keyboards shows up. I tried to use the sizer package but never could figure out how to get it to work
when I go back from this screen everything in the previous screen will rebuild, Please note: If I don't click on the typeaheadwidget such that the keyboard doesn't show up the state is preserved in the previous screen but as soon as the keyboard pops up the widgets get rebuilt
Could you please check ?
class SearchScreen extends StatefulWidget {
#override
_SearchScreenState createState() => _SearchScreenState();
}
class _SearchScreenState extends State<SearchScreen> {
TextEditingController pickUpTextEditingController = TextEditingController();
TextEditingController dropOffTextEditingController = TextEditingController();
#override
void initState() {
super.initState();
}
#override
#mustCallSuper
Widget build(BuildContext context) {
String placeAddress =
Provider.of<AppData>(context).pickUpLocation.placeName ?? "";
pickUpTextEditingController.text = placeAddress;
return Scaffold(
resizeToAvoidBottomInset: false,
body: Stack(
children: [
Container(
height: 250.0,
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black,
blurRadius: 6.0,
spreadRadius: 0.5,
offset: Offset(0.7, 0.7),
)
],
),
child: Padding(
padding: EdgeInsets.only(
left: 25.0, top: 30.0, right: 25.0, bottom: 20.0),
child: Column(
children: [
SizedBox(height: 5.0),
Stack(
children: [
GestureDetector(
onTap: () {
Navigator.pop(
//send back data
context,
dropOffTextEditingController.text);
},
child: Icon(Icons.arrow_back)),
Center(
child: Text(
"Set Drop Off",
style: TextStyle(
fontSize: 18.0, fontFamily: "Brand-Bold"),
),
)
],
),
SizedBox(height: 16.0),
Row(
children: [
Image.asset("images/images/pickicon.png",
height: 16.0, width: 16.0),
SizedBox(width: 18.0),
Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.grey[400],
borderRadius: BorderRadius.circular(5.0),
),
child: Padding(
padding: EdgeInsets.all(3.0),
child: TextField(
controller: pickUpTextEditingController,
decoration: InputDecoration(
hintText: "PickUp Location",
fillColor: Colors.grey[400],
filled: true,
border: InputBorder.none,
isDense: true,
contentPadding: EdgeInsets.only(
left: 11.0, top: 8.0, bottom: 8.0),
),
),
),
))
],
),
SizedBox(height: 10.0),
Row(
children: [
Image.asset("images/images/desticon.png",
height: 16.0, width: 16.0),
SizedBox(width: 18.0),
Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.grey[400],
borderRadius: BorderRadius.circular(5.0),
),
child: Padding(
padding: EdgeInsets.all(3.0),
child: TypeAheadField(
itemBuilder: null,
onSuggestionSelected: null,
suggestionsCallback: null,
),
),
),
),
],
),
],
),
),
),
],
),
);
}
}
You should not try to control when the build method is called. Flutter will call build when it decides it needs to (e.g. keyboard appears, device rotated, parent rebuilds, etc).
Instead, you should make sure that your build method is a "pure" function. In Flutter specifically, this means that you should not perform any action with "side-effects" (basically anything which modifies the state of the app).
For example:
Widget build(BuildContext context) {
final x = 2 + 3; // fine, nothing else is modified
final state = context.watch<MyModel>(); // also fine, only reading data
controller.text = "hello"; // BAD, modifies the state of the app
return ...;
}
Instead, you should move your logic with side effects into other lifecycle methods (e.g. initState(), didChangeDepencencies(), etc).
For example, if you want to set your text field to a particular string when it first appears, you can use initState:
class _SearchScreenState extends State<SearchScreen> {
#override
void initState() {
super.initState();
final data = context.read<AppData>();
controller.text = data.pickUpLocation.placeName ?? "";
}
Widget build(BuildContext context) {
// ...
}
}
Now build() can be called whenever it has to be, without resetting the state of your text field.
Note that, even if there was some way to prevent your widget from being rebuilt, this is also likely not what you want, since the UI would not update to accommodate the keyboard.
the only reason why your widgets got rebuilds after keyboard pop up.
is that one or more of your widgets size depends on MediaQuery.
you can try to ge your screen size from LayoutBuilder as an alternative for MediaQuery.
Give the textfield an initial value like this:
initvalue:Provider.of<AppData>(context).pickUpLocation.placeName ?? ""
and use onchange method in text field instead of text editing controller like this:
onchange(value){
Provider.of<AppData>(context).pickUpLocation.placeName=value;}
I am also facing the same issue, My blocBuidler is getting rebuilt every time when click on textfield or keyboard is appear.
In my case, I was calling the event in parent BlocBuilder so whenever I pressed on textfields the parent BlocBuilder is called the event, so it builds state of child BlocBuilder
Make sure you are also doing the same thing. If you are doing the same thing please check the state whether it is already built or not.
(BlocProvider.of<YourBlocName>(context).state is YouBlocState) ? Print('do nothing'): BlocProvider.of<YourBlocName>(context).add(youBlocEvent);
When you tap the TextField widget, it makes the keyboard show up. And when the keyboard shows up, your screen size changes. This causes the rebuild
Container height is set to fixed 40 but once I'm using that Widget in AppBar() it takes all the possible height. Here is the code for my custom widget which has Fixed height of Container,
class LPBorderButtonWithIcon extends StatelessWidget {
final GestureTapCallback onPressed;
final String text;
final String iconAsset;
final Color textColor;
LPBorderButtonWithIcon(
{#required this.onPressed,
#required this.text,
#required this.textColor,
#required this.iconAsset});
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onPressed,
child: Container(
height: 40,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25),
border: Border.all(color: Color(0XFFd8dce1))),
child: Row(
children: [
WidthSizedBox(15),
Image.asset(
iconAsset,
height: 14,
width: 14,
),
WidthSizedBox(5),
Text(text,
style: TextStyle(
color: textColor,
fontSize: 12,
fontFamily: "GilroyMedium")),
WidthSizedBox(15),
],
),
));
}
}
and here I'm using LPBorderButtonWithIcon() in this screen,
class CreateRulesScreen extends StatefulWidget {
#override
_CreateRulesScreenState createState() => _CreateRulesScreenState();
}
class _CreateRulesScreenState extends State<CreateRulesScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
brightness: Brightness.light,
backgroundColor: Colors.white,
elevation: 1,
centerTitle: false,
titleSpacing: 0.0,
leading: BackButton(
color: LPColor.primary,
onPressed: () {
Navigator.of(context).pop();
},
),
title: Text(
"Create Rule",
style: LPStyle.titleStyle,
),
actions: [
Container(
margin: EdgeInsets.only(top: 12, bottom: 12, right: 16),
child: LPBorderButtonWithIcon(
onPressed: null,
text: "Create",
textColor: Color(0XFF508ff4),
iconAsset: "images/ic_publish.png",
),
)
],
),
);
}
}
and below is the result where that custom container takes all the possible height. Please let me know how can I set fixed height to my custom widget.
Place your Container inside an Align, Aling will force the container to occupy only the space it needs.
Align(
child: Container(
height: 20,
width: 30,
color: Colors.white,
),
)
The parent widget takes the entire space available to draw the widget, Here Container is the parent widget, and it's taking whatever space is available, so to give height to the Container, that needed to be placed inside any widget which assigns x,y position of widgets to get it to draw.
Container(
height: 40, // Its not going to apply height as it's parent widget
)
So to work out the above code you have to align Container to any other widget like Center, Align, etc.
For Eg:
Scaffold(
body: Container(
height: 600,
color: Colors.red,
child: Container(
height: 200,
color: Colors.yellow,
),
),
);
The above example child container will not draw yellow color in 200 height, it will take the entire 600 height space.
Output:
To Solve this we have assigned some widgets to the child Container so that it will get the x, y position to start drawing the child widget. Here Center widget is used.
Eg:
Scaffold(
body: Container(
height: 600,
color: Colors.red,
child: Center(
child: Container(
height: 200,
color: Colors.yellow,
),
),
),
);
Output:
Some Limitation:
A widget can decide its own size only within the constraints given to
it by its parent. This means a widget usually can’t have any size it
wants.
A widget can’t know and doesn’t decide its own position in the
screen, since it’s the widget’s parent who decides the position of
the widget.
Since the parent’s size and position, in its turn, also depends on
its own parent, it’s impossible to precisely define the size and
position of any widget without taking into consideration the tree as
a whole.
If a child wants a different size from its parent and the parent
doesn’t have enough information to align it, then the child’s size
might be ignored. Be specific when defining alignment.
Reference link: https://flutter.dev/docs/development/ui/layout/constraints
I am trying to make the button below resize depending on different screen sizes but retain the same ratios (i.e. text and button size should change proportionately so they look the same on all screens). I am using the AutoSizeText package to resize the text based on screensize but the text doesn't seem to getting smaller on the smaller screen which might be causing the button to resize oddly.
[![button 1][1]][1] - lager screen
[![button 2][2]][2] - smaller screen
I have tried using a mediaquery to size the button with height and width but that does not seem to be working.
Is there a recommended way of doing this?
class PurchaseButton extends StatelessWidget {
final Product product;
PurchaseButton({Key key, #required this.product}) : super(key: key);
#override
Widget build(BuildContext context) {
double deviceWidth = MediaQuery.of(context).size.width;
Container(
margin: EdgeInsets.symmetric(horizontal: 0.05 * deviceWidth),
child: Row(
children: <Widget>[
Expanded(
child: MaterialButton(
// height: SizeConfig.blockSizeVertical * 6,
onPressed: () async {
await Provider.of<SubscriptionModel>(context).makePurchase(product);
},
child: Text('Join now! Only ${product.priceString}',
style: Theme.of(context).textTheme.body1.copyWith(
fontWeight: FontWeight.w600, fontSize: 0.03 * deviceWidth)),
color: Theme.of(context).primaryColor,
),
),
],
),
);
return Container();
}
}
Many ways to do so, here's a suggestion:
Wrap your widget with this tree of widgets:
Container: to manipulate the width according to the screen size,
-- Row: we need it to force Expanded to work,
---- Expanded: will Expand its content to the whole space it has,
------[the widget you want to expand]
#override
Widget build(BuildContext context) {
double deviceWidth = MediaQuery.of(context).size.width;
double deviceHight = MediaQuery.of(context).size.height;
Container(
// If the button size(Row) is 90% then we give margin 5% + 5% like this
margin: EdgeInsets.symmetric(horizontal: 0.05 * deviceWidth),
// We need a Row in order to "Expanded" to work
child: Row(
children: <Widget>[
// Use "Expanded" if you want the button to fill the Row's size
// Use "Flexible" if you want the button to fit the text inside size.
Expanded(
child: MaterialButton(
onPressed: () {
print('Hi');
},
child: Text(
'Join now! Only...',
style: Theme.of(context)
.textTheme
.body1
.copyWith(fontWeight: FontWeight.w600),
),
color: Theme.of(context).primaryColor,
),
),
],
),
);
Regarding AutoSizeText it takes the size of text's container into consideration, not the screen size, my suggestion is to use regular Text(..) widget with font size taken from MediaQuery.of(context).size.width
e.g
child: Text(
'Join now! Only...',
style: Theme.of(context)
.textTheme
.body1
.copyWith(
fontWeight: FontWeight.w600,
fontSize: 0.03 * deviceWidth,
),
),