I have my custom Button widget in a container which width is defined by the width of its content (it can't be static). When I put that widget in a wrap widget it expands my custom Button to screen width, but if I put it in a row it wraps correctly and I can't figure out how to maintain wanted width of children inside the wrap widget. Here is the code below:
My Custom Button Widget:
class Button extends StatefulWidget {
final String imageAsset;
final String btnText;
final Color colorBegin;
final Color colorEnd;
const Button({Key? key, required this.imageAsset, required this.btnText, required this.colorBegin, required this.colorEnd}) : super(key: key);
#override
State<Button> createState() => _ButtonState();
}
class _ButtonState extends State<Button> {
Variables variables = Variables();
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [widget.colorBegin, widget.colorEnd]
),
borderRadius: BorderRadius.all(Radius.circular(variables.radius(context, 16)))
),
child: Row(
children: [
Image.asset(
widget.imageAsset,
height: variables.height(context, 5),
width: variables.height(context, 5),
),
SizedBox(width: variables.width(context, 3)),
Text(
widget.btnText,
style: TextStyle(
fontFamily: 'LeagueSpartanRegular',
fontSize: variables.height(context, 2.5),
color: Color(0xFF0B182A),
),
),
SizedBox(width: variables.width(context, 4)),
],
),
);
}
}
My wrap widget:
class _ButtonListState extends State<ButtonList> {
#override
Widget build(BuildContext context) {
return Wrap(
alignment: WrapAlignment.center,
runSpacing: 20,
children: const [
Button(
btnText: 'Little Friend',
imageAsset: 'assets/images/icon_little_friend.png',
colorBegin: Color(0xFFb3d7a4),
colorEnd: Color(0xFFb0caaa),
),
Button(
btnText: 'Little Friend',
imageAsset: 'assets/images/icon_little_friend.png',
colorBegin: Color(0xFFb3d7a4),
colorEnd: Color(0xFFb0caaa),
),
Button(
btnText: 'Little Friend',
imageAsset: 'assets/images/icon_little_friend.png',
colorBegin: Color(0xFFb3d7a4),
colorEnd: Color(0xFFb0caaa),
),
Button(
btnText: 'Little Friend',
imageAsset: 'assets/images/icon_little_friend.png',
colorBegin: Color(0xFFb3d7a4),
colorEnd: Color(0xFFb0caaa),
),
],
);
}
}
And my home screen:
class _HomeWidgetState extends State<HomeWidget> {
Variables variables = Variables();
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(top: variables.height(context, 7)),
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/images/bg_main.png"),
fit: BoxFit.cover,
),
),
child: Column(
children: [
TextCatalogue(),
SizedBox(height: variables.height(context, 4)),
Expanded(child: Stack(
children: [
WhiteBackgroundHome(),
ButtonList(),
]
)
),
],
),
);
}
}
Problem
This is the result when I replace wrap with row, the children aren't expanded
I tried placing it out of the expanded widget on the home screen, tried deleting everything on the home screen and returning only the wrap widget still get the same result.
It fixes the problem if I define the width of my custom button widget, but that is not the solution because the buttons will have different width depending on the text inside.
Try adding this line for your Button widget.
class _ButtonState extends State<Button> {
Variables variables = Variables();
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [widget.colorBegin, widget.colorEnd]
),
borderRadius: BorderRadius.all(Radius.circular(variables.radius(context, 16)))
),
child: Row(
mainAxisSize: MainAxisSize.min, // ++++++++++++++++
children: [
Image.asset(
widget.imageAsset,
height: variables.height(context, 5),
width: variables.height(context, 5),
),
SizedBox(width: variables.width(context, 3)),
Text(
widget.btnText,
style: TextStyle(
fontFamily: 'LeagueSpartanRegular',
fontSize: variables.height(context, 2.5),
color: Color(0xFF0B182A),
),
),
SizedBox(width: variables.width(context, 4)),
],
),
);
}
}
You want the width of each button to be the sum of widths of its children. In your Button widget's build function. Add mainAxisSize: MainAxisSize.min to the Row widget.
From the docs:
The width of the Row is determined by the mainAxisSize property. If the mainAxisSize property is MainAxisSize.max, then the width of the Row is the max width of the incoming constraints. If the mainAxisSize property is MainAxisSize.min, then the width of the Row is the sum of widths of the children (subject to the incoming constraints).
//...
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
widget.imageAsset,
height: variables.height(context, 5),
width: variables.height(context, 5),
),
//...
Related
I have a list of strings for tabs, and want to give same width to all tabs instead of based on text length...
Like I have 4 tabs , it should be occupy same width ,
and if text length is bigger than text size should b adjusted..
DONT want to user listview, all tabs should be adjusted according to available width
like width is 300 and tabs are 2, each tab should be in width of 150(including padding etc..)
but I am getting following which I dont want to set widget based on text length
,
class HomeScreen extends StatelessWidget {
HomeScreen({Key? key}) : super(key: key);
List<String> titles=['All','Income','Expense','Debt','Others','Liabilities'];
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Container(
height: 100,
color: Colors.grey,
child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: titles.map((e) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 2.0),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8,vertical: 4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Colors.white,
),
child: Text(e)),
)).toList(),),
),
),
);
}
}
Wrap each child of Row inside Expanded and Replace Text With AutoSizeText https://pub.dev/packages/auto_size_text
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
final List<String> titles = const [
'All',
'Income',
'Expense',
'Debt',
'Others',
'Liabilities'
];
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Container(
height: 100,
color: Colors.grey,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: titles
.map((e) => Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 2.0),
child: Container(
height: 24,
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Colors.white,
),
child: AutoSizeText(
e,
maxLines: 1,
textAlign: TextAlign.center,
)),
),
))
.toList(),
),
),
),
);
}
}
Output:
You can achieve this by wrapping each tab in a Container widget with a fixed width and set the mainAxisAlignment property of the parent Row widget to MainAxisAlignment.spaceBetween. This will distribute the tabs evenly across the available width like this.
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Container(
width: 150.0,
child: Text("Tab 1"),
),
Container(
width: 150.0,
child: Text("Tab 2"),
),
Container(
width: 150.0,
child: Text("Tab 3"),
),
Container(
width: 150.0,
child: Text("Tab 4"),
),
],
)
You can adjust the width property of the Container widgets as per your requirements. If the text is larger than the container width, you can adjust the text size using the style property of the Text widget and set the overflow property of the Container widget to TextOverflow.ellipsis to display an ellipsis (...) when the text overflows the container.
Take a look at Boxy, which even has an example of doing what you want to do (make a series of items the width of the largest item).
You can use a LayoutBuilder.
This widget allows you to get the parent's widget width, and according to that you can then use N SizedBox to limit the width of your chips.
I need to position a child widget (w1) at a relative position to its parent (in the example see the white box at 30%), and to position two other child widgets (w2, w3) to the right and left.
The positioned child (w1) is the anchor and should remain in 30%, regardless of w2 and w3 width, while the other child widgets should positioned accordingly.
Here's what I'm trying to achieve:
I tried to use Stack widget as the parent
The problem is I can't place the position child at 30%, as it only excepts const values
I also tried to placed them in Container with FractionalOffset - it works for single children, but I can't add another children to this Container.
Container(
height: 50,
color: Colors.blue[200],
alignment: const FractionalOffset(0.3, 0),
child: Text("50",style: const TextStyle(fontSize: 16, color: Colors.black))),
You can use CompositedTransformTarget widget.
class TestF133 extends StatelessWidget {
const TestF133({super.key});
#override
Widget build(BuildContext context) {
final LayerLink link = LayerLink();
return Scaffold(
backgroundColor: Colors.deepPurple,
body: Stack(
children: [
Align(
alignment: Alignment(.3, 0),
child: CompositedTransformTarget(
link: link,
child: Container(
height: 50,
width: 50, // you can handle theses
color: Colors.white,
),
),
),
CompositedTransformFollower(
link: link,
followerAnchor: Alignment.centerLeft,
targetAnchor: Alignment.centerRight,
child: Text("dynamic right Text"),
),
CompositedTransformFollower(
link: link,
followerAnchor: Alignment.centerRight,
targetAnchor: Alignment.centerLeft,
child: Text("dynamic left Text"),
)
],
),
);
}
}
I hope I got your question right. So, a quick solution would be:
class MyBar extends StatelessWidget {
const MyBar({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
return Container(
color: Colors.blue,
child: Row(
children: [
SizedBox(
width: width * 3 / 10,
child: const Align(
alignment: Alignment.centerRight,
child: Text('left'),
),
),
Container(
height: 50,
color: Colors.lime.shade100,
margin: const EdgeInsets.symmetric(horizontal: 10),
child: const Text("50",
style: TextStyle(fontSize: 16, color: Colors.black)),
),
const Expanded(child: Text('right')),
],
),
);
}
}
For more accurate result, I think you can find the width of the handler and subtract the half of it from the width of the SizedBox (or something like that).
You can also use Stack with position with this approach.
I have created a button class that extends a stateless widget for creating a customized button widget. I have used this button to create buttons in a class and it worked perfectly fine. But, when i tried to create a button in an another class, using the same button widget, i got an error,'The method '*' was called on null and RenderFlex overflowed' which i could not get why is it appearing. Can somebody help me, what and where did i do the blunder.
This one is the button widget
import 'package:flutter/material.dart';
import 'package:group_project/ui/size_config.dart';
import 'package:group_project/widgets/responsive_widget.dart';
// Button Widget
class Button extends StatelessWidget {
final IconData icon;
final String btnName;
final double height;
final double width;
final Color buttonColor;
final Color iconColor;
final double iconSize;
final Color textColor;
final double btnTextSize;
Border border;
BorderRadius btnBorderRadius;
MainAxisAlignment mainAxisAlignment;
CrossAxisAlignment crossAxisAlignment;
Button({
#required this.icon,
#required this.btnName,
this.height,
this.width,
this.buttonColor,
this.iconColor,
this.textColor,
this.border,
this.mainAxisAlignment,
this.crossAxisAlignment,
this.btnTextSize,
this.iconSize,
this.btnBorderRadius,
});
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
height: height,
width: width,
decoration: BoxDecoration(
color: buttonColor,
border: border,
borderRadius: btnBorderRadius,
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: mainAxisAlignment,
children: [
Text(
btnName,
style: TextStyle(
fontSize: btnTextSize * SizeConfig.textMultiplier,
color: textColor,
),
),
Icon(
icon,
color: iconColor,
size: iconSize * SizeConfig.imageSizeMultiplier,
),
],
),
),
);
}
}
This is the class where I got the error.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:eva_icons_flutter/eva_icons_flutter.dart';
import 'package:group_project/ui/size_config.dart';
import 'package:group_project/widgets/widgets.dart';
// import 'package:group_project/data/data.dart';
// import 'package:group_project/widgets/product_carousel_widget.dart';
class ProductsPage extends StatefulWidget {
#override
_ProductsPageState createState() => _ProductsPageState();
}
class _ProductsPageState extends State<ProductsPage> {
Size size;
bool visible = true;
void isVisible() {
if (visible == true) {
visible = false;
} else {
visible = true;
}
}
#override
Widget build(BuildContext context) {
size = MediaQuery.of(context).size;
return Scaffold(
backgroundColor: Color(0xfff0f0f0),
body: ListView(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
children: [
Container(
height: 233.33 * SizeConfig.heightMultiplier,
width: double.infinity,
color: Colors.blue,
child: Image(
image: AssetImage('images/jacket.jpg'),
fit: BoxFit.cover,
),
),
Stack(
children: [
// Products description
Visibility(
visible: visible,
child: Padding(
padding: const EdgeInsets.only(top: 40.0),
child: Container(
padding: EdgeInsets.fromLTRB(10.0, 40.0, 10.0, 10.0),
height: 400,
width: double.infinity,
decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(20.0),
// border: Border.all(color: Colors.blue),
color: Colors.blue,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Iphone Pro Max'
),
Text(
'Rs. 125000'
),
],
),
SizedBox(
height: 20.0,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'(Used)'
),
Text(
'Condition: Good'
),
],
),
SizedBox(
height: 20.0,
),
Text(
'This is iphone 11 pro max, 64 GB variant. The size of the mobile phone is 6.5 inches. Released 2019, September ',
),
SizedBox(
height: 20.0,
),
Row(
children: [
// This is the button where exactly I am getting the error
Button(
height: 25 * SizeConfig.heightMultiplier,
width: 80,
icon: Icons.shopping_cart,
btnName: 'Add',
),
],
),
],
),
),
),
),
FlatButton(
padding: EdgeInsets.all(0),
onPressed: () {
setState(() {
isVisible();
});
},
child: Button(
icon: EvaIcons.chevronDown,
btnName: 'Show Description',
height: 25.0 * SizeConfig.heightMultiplier,
width: double.infinity,
buttonColor: Colors.blue,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
btnTextSize: 8.0,
iconSize: 20,
textColor: Colors.white,
iconColor: Colors.white,
// btnBorderRadius: BorderRadius.circular(0),
),
),
],
),
],
),
],
),
],
),
);
}
}
Wrap it with Expanded Widget when using inside a Row or Column
Expanded(
child: Button(
height: 25 * SizeConfig.heightMultiplier,
width: 80,
icon: Icons.shopping_cart,
btnName: 'Add',
),
),
Your Row has no defined height or width. Try wrapping your row in an Expanded widget.
This also why you're getting the null reference exception. You're sizeConfig (Which I assume is based off of MediaQuery.of(context).size) Gets it's context from it's parent widget and since a Row widget doesn't define (It is a flexible widget) height or width, it will return null for size. Expanded will tell the Row to set its size to all available space.
#override
Widget build(BuildContext context) {
return SingleChildScrollView(child: Container(...));}
surround your code with SingleChildScrollView that's why render flex error is showing
for the '*' is called on null error click on the run button at the bottom of android studio and look at the log and click the link with ProductPage.dart and it will take you to the line where the error happens
Try giving it a width or wrapping with a Expanded
It is given infinite width because MediaQuery is always given the context of it's parent, and in this scenario it gets it from Row widget which doesn't have a defined width. Neither does a Column widget have a defined height. That's why it's giving the error.
Wrapping it with Expanded widget will make the Row take all of free space.
I am very new to flutter so please be fair.
I would like to create the following.
Layout
Background which is always the entire screen-size (includes multiple stacked images)
Content (adapts to the the normal app ui behaviours -> ui element like keyboard)
I can't seem to figure out how to create this background element which should not resize when the keyboard is pulled out.
I have this and I hope someone could give me a hand.
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
var insetHeight = MediaQuery.of(context).viewInsets.bottom;
return Scaffold(
resizeToAvoidBottomPadding: false,
body: Stack(
children: <Widget>[
Stack(
children: <Widget>[
Positioned(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height + insetHeight,
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/images/background.png"),
fit: BoxFit.cover,
alignment: Alignment.bottomCenter,
),
)
),
),
],
),
SizedBox(
width: 370,
child: SingleChildScrollView(
physics: PageScrollPhysics(), // dont really need this
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.all(20.0),
child: Text("Login to",
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: "Opensans",
fontSize: 30,
letterSpacing: .6,
color: Colors.black45,
fontWeight: FontWeight.normal
)
),
),
Card(
child: Padding(padding: EdgeInsets.fromLTRB(20.0, 20.0, 20.0, 0), child: LoginForm()),
)
],
),
)
)
],
),
);
}
}
I have tried to use the MediaQuery.of(context).viewInsets.bottom; but it's always returning 0.
I am not sure how to solve this.
The Scaffold rebuilds its body when keyboard is visible.
So move your background widget outside the Scaffold.
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
SizedBox.expand(
child: Image.asset(
"assets/images/background.png",
fit: BoxFit.cover,
),
),
Scaffold(
backgroundColor: Colors.transparent, //Should be transparent
body: Center(
child: TextField(),
),
),
],
);
}
}
For background, Try following:
Create a variable Size _screensize in _MyHomePageState.
Override initState in _MyHomePageState and do following:
#override
void initState() {
super.initState();
_screenSize ??= MediaQuery.of(context).size;`
}
Use this _screenSize as width and height for Positioned as below:
Positioned(
width: _screenSize.width,
height: _screenSize.height,
),
Add other normal widgets as usual.
Summary for background:
step 1: creating a private reference for size to use.
step 2: updating the private size variable only when it is null, which is only when the widget state is initialized the first time.
step 3: using private size as height and width for the background.
TL;DR
Is it possible to align ListView's children like Column's spaceBetween alignment?
Like in this screenshot:
Problem that I tried to solve:
In the beginning I had a Column widget with 2 children. The Column's alignment was set to spaceBetween. It was what I wanted, one widget at the top, one at the bottom. But then when I clicked to input to add credentials, the keyboard caused an overflow issue described in this GitHub issue. So to fix it, I replaced the Column with ListView. But now my children widgets are stuck at the top of the screen.
My full LoginScreen code:
import 'package:flutter/material.dart';
import '../widgets/button.dart';
import '../widgets/input.dart';
import '../widgets/logo.dart';
class LoginScreen2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
Background(),
Wrapper(),
],
),
);
}
}
class Background extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Color(0xfff6f6f6),
Colors.white,
],
),
),
);
}
}
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
Logo(LogoColor.dummy_black),
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.all(20),
child: BottomPart(),
),
),
],
);
}
}
class BottomPart extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Input('User name'),
Input('Password'),
Button(
'Log in',
onPressed,
),
],
);
}
void onPressed() {}
}
You could use ListView's property reverse
reverse: true,
And then change the order of the ListView children.
Find a solution. You should wrap your Column in IntrinsicHeight and ConstrainedBox with minHeight.
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: IntrinsicHeight(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: MediaQuery.of(context).size.height,
),
child: Column(
children: [
Container(
height: 100,
width: double.infinity,
color: Colors.yellow,
child: Text('1', style: TextStyle(color: Colors.black)),
),
Spacer(),
Container(
height: 100,
width: double.infinity,
color: Colors.red,
child: Text('2', style: TextStyle(color: Colors.black)),
),
],
),
),
),
);
}
}
Try using SizedBox(height:50.0) // or any other value in b/w Logo widget and Align(bottom part).
return ListView(
children: <Widget>[
Logo(LogoColor.dummy_black),
SizedBox(height: MediaQuery.of(context).size.height/ 3),// you will get value which is 1/3rd part of height of your device screen
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.all(20),
child: BottomPart(),
),
),
],
);
Finally I found out soln to this problem. Use Code :`
Align(
alignment: Alignment.bottomCenter,
child: ListView(
shrinkWrap: true,
children: <Widget>[
// Your Widget Here
],
),
),
you can insert this between logo and Align
Expanded(child: SizedBox(height: 8)),
You could take out the bottom part from ListView, and put it in the last of Stack's children. Your Wrapper could look like:
Widget build(BuildContext context){
var bottomHeight = 120.0;//replaced with your BottomPart's height
return Stack(
children: [
ListView(),//your ListView
Positioned(
top: MediaQuery.of(context).size.height - bottomHeight,
child: SizedBox(
height: bottomHeight,
child: BottomPart(),
)
)
],
);
}