I created a row with a BackButton and a TextWidget.
I want to center the text to the middle of the screen. Actually flutter centeres the text to the containers width, but the containers width isnt the same as the screen width, because there is the backbutton. How do i fix that?
Expanded getTitle() {
return new Expanded(
child: new Text("Einloggen", style: new TextStyle(fontSize: 18.0), textAlign: TextAlign.center)
);
}
BackButton getBackButton() {
return new BackButton(
);
}
Row getHeader() {
return new Row(
children: <Widget>[
getBackButton(),
getTitle()
],
);
}
#override
Widget build(BuildContext context) {
final double statusBarHeight = MediaQuery.of(context).padding.top;
return new Material(
child: new Container(
padding: new EdgeInsets.fromLTRB(0.0, statusBarHeight, 0.0, 0.0),
child: new Column(
children: <Widget>[
getHeader()
],
),
),
);
}
You can use the Row's mainAxisAlignment parameter to center align children of a row.
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
//children Widgets
]
);
Similarly, mainAxisAligment paramter can also be used to align Column's children. For more information check this out!
You can achieve the same UI using A Scaffold with AppBar
class mytab extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
centerTitle: true,
leading: new Icon(Icons.arrow_back),
title: new Text("Einloggen",
style: new TextStyle(fontSize: 18.0)),
),
);
}
}
To make the Title in the Center :
centerTitle: true
Based on your code
Widget getTitle() {
return const Text('Einloggen',
style: TextStyle(fontSize: 18.0), textAlign: TextAlign.center);
}
BackButton getBackButton() {
return const BackButton();
}
Row getHeader() {
return Row(
children: <Widget>[
Expanded(
child: getBackButton(),
),
const Spacer(),
getTitle(),
const Spacer(flex: 2)
],
);
}
#override
Widget build(BuildContext context) {
final double statusBarHeight = MediaQuery.of(context).padding.top;
return Material(
child: Container(
padding: EdgeInsets.fromLTRB(0.0, statusBarHeight, 0.0, 0.0),
child: Column(
children: <Widget>[getHeader()],
),
),
);
}
I don't know if it is still useful, but I found a solution thanks to widegtes: Container, Stack and Align.
Widget getTitle() {
return new Text("Einloggen", style: new TextStyle(fontSize: 18.0));
}
Widget getBackButton() {
return Container(
height: MediaQuery.of(context).size.height,
child: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: moveToLogin,
));
}
Widget getHeader() {
return Container(
height: 50.0,
// you must set a size to the Conteiener to make sure that the internal Align
// widens as much as possible.
child: new Stack(
// Stack places the objects in the upper left corner
children: <Widget>[
getBackButton(),
Align(alignment: Alignment.center, child: getTitle()),
],
),
);
}
final double statusBarHeight = MediaQuery.of(context).padding.top;
return new Container(
padding: new EdgeInsets.fromLTRB(0.0, statusBarHeight, 0.0, 0.0),
child: new Column(
children: <Widget>[getHeader()],
),
);
This is the result
Image
Related
I am looking to create a grid with 4 custom widgets that can either add or subtract from a given starting number. See image for reference.
For example, if you press player one, the number would increase or decrease to 100 or 99. But the other 3 players would remain the same.
I had originally used one stateful widget with a separate function for each player, but I am sure there's a way to do it in a more modular way.
class CommanderDamage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return CommanderDamageState();
}
}
class CommanderDamageState extends State<CommanderDamage> {
int damage = 0;
void update() {
setState(() {
damage++;
});
}
#override
Widget build(context) {
return MaterialApp(
home: Scaffold(
body: GridView.builder(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemCount: 4,
itemBuilder: (BuildContext context, index) {
return Container(
child: Column(
children: <Widget>[
Text("Player " + index.toString()),
InkWell(
onTap: update,
child: Container(
width: 100.0,
height: 100.0,
child: Text(damage),
)
],
),
);
},
),
),
);
}
}
EDIT: I have edited my code to reflect my current. Currently, when the damage area is pressed, the damage increases for all 4 players instead of the one I am pressing.
Wrap your text widget inside InkWell(). Basically what InkWell does is creates a rectangular touch responsive area.
InkWell(
child: Text(
'Player One',
style: TextStyle(
fontSize: 20, color: Colors.white),
onTap: () {
// Your function
}
)
But this make the interactive tap area according to size of the text which is very small, so it's better to wrap it inside a container and provide height-width or some space with padding
InkWell(
child: Container(
width: 100,
height: 100,
child: Text(
'Player One',
style: TextStyle(
fontSize: 20, color: Colors.white), ),
onTap: () {
// Your function
}
)
An inside onTap you can your function and perform changes.
Read more about InkWell:
https://docs.flutter.io/flutter/material/InkWell-class.html
After lots of trial and error I managed to find an answer.
I had to set the state within the onTap instead of making a separate function and calling it in the onTap.
class CommanderDamage extends StatefulWidget {
int damage = 0;
CommanderDamage({this.damage, Key key});
#override
State<StatefulWidget> createState() {
return CommanderDamageState();
}
}
class CommanderDamageState extends State<CommanderDamage> {
var damage = [0, 0, 0, 0, 0, 0];
#override
Widget build(context) {
return MaterialApp(
home: Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft, end: Alignment.bottomRight,
colors: [Color(0xfff6921e), Color(0xffee4036)],
),
),
child: GridView.builder(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemCount: damage.length,
itemBuilder: (BuildContext context, index) {
return Container(
child: Column(
children: <Widget>[
InkWell(
onTap: () {
setState(() {
damage[index]++;
});
},
onLongPress: () {
setState(() {
damage[index] = 0;
});
},
child: Container(
width: 100.0,
height: 100.0,
child: Text(damage[index].toString()),
),
),
],
),
);
},
),
),
],
),
),
);
}
}
I am trying to use the BLoC pattern with RxDart, and i would like to get an event from a list through StreamBuilder.
My problem is that when I am opening my Details Screen, for like 1sec I've got the following error message : The method 'firstWhere' was called on null.
Is there a way to wait getting data without adding a loading progress because I would like to keep my HeroAnimation on my image ?
class _EventsDetailsScreenState extends State<EventsDetailsScreen> {
#override
Widget build(BuildContext context) {
final eventBloc = BlocProvider.of<EventsBloc>(context);
//Event event = eventBloc.events.firstWhere((elmt) => elmt.id == widget.id, orElse:() => null);
return StreamBuilder(
stream : eventBloc.allEvents,
builder: (context, snapshot){
final Event event = snapshot.data.firstWhere((elmt) => elmt.id == widget.id, orElse:() => null);
return Scaffold(
body: Stack(
children: <Widget>[
Column(children: <Widget>[
Expanded(
child: ListView(
shrinkWrap: true,
children: <Widget>[
_buildEventImage(context, event),
(event.description != null && event.description.trim().isNotEmpty) ? _buildDescriptionSection(context, event) : Container(),
],
))
]),
new Positioned(
//Place it at the top, and not use the entire screen
top: 0.0,
left: 0.0,
right: 0.0,
child: AppBar(
actions: <Widget>[
IconButton(icon: Icon(Icons.create), onPressed: () async => print('Edit')),
],
backgroundColor: Colors.transparent, //No more green
elevation: 0.0, //Shadow gone
),
),
],
),
);
},
);
}
Widget _buildEventImage(BuildContext context, Event event) {
return Hero(
tag: 'eventImg${event.id}',
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("images/croatia.jpg"),
alignment: Alignment.topCenter,
fit: BoxFit.fill),
),
padding: EdgeInsets.only(left: 40.0, bottom: 250.0),
alignment: Alignment.bottomLeft,
height: MediaQuery.of(context).size.height - 30.0,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
child: Material(
color: Colors.transparent,
child: Text(
event.name,
style: eventNameTextStyle,
)),
),
Container(
child: Material(
color: Colors.transparent,
child: Text(
"In ${event.date.difference(DateTime.now()).inDays} days",
style: eventDateTextStyle)))
],
)),
);
}
I hope you have solved this already ,
but I faced the same problem while working with BLoC Pattern in Flutter.
The problem comes from the fact that streams are asynchronous and the build method is synchronous .
This is from the official docs :
The initial snapshot data can be controlled by specifying initialData.
This should be used to ensure that the first frame has the expected
value, as the builder will always be called before the stream listener
has a chance to be processed.
StreamBuilder class
So in order to avoid this behavior you have to use initialData property in the StreamBuilder.
To use this with a BLoC you could expose a simple getter at your BLoC that get synchronous value of your current state to give as an initial data for any new subscriber.
You can add an if statement to check if the snapshot has data, this way firstWhere function will only be called if there's data in the snapshot :
class _EventsDetailsScreenState extends State<EventsDetailsScreen> {
#override
Widget build(BuildContext context) {
final eventBloc = BlocProvider.of<EventsBloc>(context);
//Event event = eventBloc.events.firstWhere((elmt) => elmt.id == widget.id, orElse:() => null);
return StreamBuilder(
stream : eventBloc.allEvents,
builder: (context, snapshot){
if(snapshot.hasData){
return Scaffold(
body: Stack(
children: <Widget>[
Column(children: <Widget>[
Expanded(
child: ListView(
shrinkWrap: true,
children: <Widget>[
_buildEventImage(context, event),
(event.description != null && event.description.trim().isNotEmpty) ? _buildDescriptionSection(context, event) : Container(),
],
))
]),
new Positioned(
//Place it at the top, and not use the entire screen
top: 0.0,
left: 0.0,
right: 0.0,
child: AppBar(
actions: <Widget>[
IconButton(icon: Icon(Icons.create), onPressed: () async => print('Edit')),
],
backgroundColor: Colors.transparent, //No more green
elevation: 0.0, //Shadow gone
),
),
],
),
);
}else{
return Scaffold();
}
});
}
I'm making a new stateful widget that would show a listview according to the option selected, which are ONE and TWO here. The value of index changes once the GestureDetector is tapped, fontsize and color of the text changes. but, the Container with pages[index] does not rebuild
I don't know what is wrong since, one of the container in the column rebuilds and the other doesn't.
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return MatchStatsState();
}
}
class MatchStatsState extends State<MatchStats>{
List<Widget> pages = [
ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
BattingStatsView(CskBatting),
BowlingStatsView(cskBowling),
],
),
ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
BattingStatsView(kxipBatting),
BowlingStatsView(kxipBowling)
],
),
];
Color activeColor = Colors.yellow;
Color inactiveColor = Colors.white;
num activeFontSize = 20.0;
num inactiveFontSize = 15.0;
int index = 0;
#override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
height: MediaQuery.of(context).size.height*0.4,
width: MediaQuery.of(context).size.width*0.95,
child: Column(
children: <Widget>[
Container(
height: MediaQuery.of(context).size.height*0.05,
width: MediaQuery.of(context).size.width*0.95,
child: Row(
children: <Widget>[
GestureDetector(
onTap: (){
setState(() {
index = 0;
});
},
child: Container(
width: MediaQuery.of(context).size.width*0.45,
child: Text("ONE",style: TextStyle(color: index == 0?activeColor:inactiveColor,fontSize: index == 0? activeFontSize: inactiveFontSize)),
),
),
GestureDetector(
onTap: (){
setState(() {
index = 1;
});
},
child: Container(
width: MediaQuery.of(context).size.width*0.45,
child: Text("TWO",style: TextStyle(color: index == 1?activeColor:inactiveColor, fontSize: index == 1? activeFontSize: inactiveFontSize)),
),
),
],
),
),
Container(
height: MediaQuery.of(context).size.height*0.35,
width: MediaQuery.of(context).size.width*0.95,
child: pages[index]
),
]
)
);
}
}
I want the second container in the column to rebuild when the value of index changes, how could I achieve that?
Try with this:
create a method that return a List Widget like this:
List<Widget> buildPages() {
return [
ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
BattingStatsView(CskBatting),
BowlingStatsView(cskBowling),
],
),
ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
BattingStatsView(kxipBatting),
BowlingStatsView(kxipBowling)
],
),
];
}
Widget getProperWidget(int index) {
return buildPages()[index];
}
Than your column container:
Container(
height: MediaQuery.of(context).size.height*0.35,
width: MediaQuery.of(context).size.width*0.95,
child: getproperWidget(index)
),
Remember to override the initState.
I think the cause of this issue is the element tree doesn't recognize the change that has been done in the widget tree , so you can add Key to the container which holds pages[index] or
you can do something like this :
Widget getWidget(int index){
return Container(
child:pages[index],
);
}
instead of using Container in the widget tree, use a function that will be called every time the ui re renders .
I hope that can help
I have 4 images in 2 columns, when I clicked on one image its style should change like color, shadow should change or that image should be replaced by other image. Once click on that image, other images should remain same. It should work like radio buttons. How to do that? Please help me, thanks in advance.
final img_rowi= Center(child:
new Container(
color: Colors.transparent,
padding: const EdgeInsets.all(5.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(padding: const EdgeInsets.all(3.0),child: Stack(
alignment: Alignment.center,
children: <Widget>[
svgIcon,new GestureDetector(
onTap: (){
setState(() {
pressed = !pressed;
});
},
child:
Container(
child: new Column(
children: <Widget>[
new Container(
child: new Image.asset(
'images/sheep_female.png',
height: 50.0,
fit: BoxFit.cover,
),
),
new Container(
child: new Text('Sheep',style: pressed
? TextStyle(color: const Color(0xFFCDCDCD),fontFamily: 'Montserrat',
)
: TextStyle(color:Colors.black,fontFamily: 'Montserrat',
),),
),
],
),
),
),
],
),),
Padding(padding: const EdgeInsets.all(3.0),child:
Stack(
alignment: Alignment.center,
children: <Widget>[
svgIcon,new GestureDetector(
onTap: (){
setState(() {
pressed1 = !pressed1;
});
},
child:
Container(
child: new Column(
children: <Widget>[
new Container(
child: new Image.asset(
'images/biily_doe.png',
height: 50.0,
fit: BoxFit.cover,
),
),
new Container(
child: new Text('Billy Doe',style: pressed1
? TextStyle(color: const Color(0xFFCDCDCD),fontFamily: 'Montserrat',
)
: TextStyle(color:Colors.black,fontFamily: 'Montserrat',
),),
),
],
),
),
),
],
),),
],
),
),
);
Store initial properties of Image in variables. For example if I want to set initial color of FlutterLogo widget to Colors.blue then declare a state in the class. Then wrap your Image with GestureDetector widget and set onTap property. Now call setState method and change all the variables (properties of Image) inside it.
Below is an example where there is one FlutterLogo widget where I've set initial color of that widget to be Colors.blue and when I tap on it, color of FlutterLogo widget is changed to Colors.green. If I again tap on it and if color is Colors.green then it changes color to Colors.yellow and so on. You can do similar thing with your Image and change it's size, visibility and other properties.
There is also imagePath variable which stores path of initial asset and when user taps on second widget (Image.asset) in Column, value of variable imagePath is changed and build method get called again and image is replaced.
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: MyApp()));
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool visibility;
Color colorOfFlutterLogo = Colors.blue;
String imagePath1 = "assets/initial-path-of-image-1";
String imagePath2 = "assets/initial-path-of-image-2";
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.black,
),
body: Column(
children: <Widget>[
GestureDetector(
onTap: () => setState(() {
if (colorOfFlutterLogo == Colors.blue)
colorOfFlutterLogo = Colors.green;
else if (colorOfFlutterLogo == Colors.green)
colorOfFlutterLogo = Colors.yellow;
else if (colorOfFlutterLogo == Colors.yellow)
colorOfFlutterLogo = Colors.blue;
}),
child: FlutterLogo(
size: double.infinity,
colors: colorOfFlutterLogo,
),
),
// Image 1
GestureDetector(
onTap: () => setState(() {
imagePath2 = "assets/new-path-for-image-2";
}),
child: Image.asset(imagePath1),
),
// Image 2
GestureDetector(
onTap: () => setState(() {
imagePath1 = "assets/new-path-for-image-1";
}),
child: Image.asset(imagePath2),
)
],
));
}
}
I am trying to expand widget inside of Column widget but not able to make it expended.
When giving constant height to parent widget, the layout will be rendered as expected. But as I remove the constant height layout is not as expected as I want to make Listview with it and I should not give a constant height to the widget which will be used as listview item.
Below is my layout code.
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'layout test',
home: Layout_test_class(),
));
}
class Layout_test_class extends StatelessWidget {
Widget cell() {
return Container(
color: Colors.yellow,
// height: 200, after un commenting this will work. but i want to make it without this
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: Container(
color: Colors.green,
child: Text('apple z'),
),
),
Container(
color: Colors.red,
child:Text('apple 2'),
)
],
),
),
Column(
children: <Widget>[
Container(
color: Colors.black,
width: 200,
height: 200,
),
],
),
],
),
);
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('title'),
),
body: Center(
child: ListView(
children: <Widget>[
cell(),
],
)
),
);
}
}
Below is my expected output screenshot.
Try to wrap your Container with IntrinsicHeight
return IntrinsicHeight(
Container(
color: Colors.yellow
child: ...
)
)
Your ListView needs to be inside Flexible. Flexible inside Column will set maximum height available to ListView. ListView needs a finite height from parent, Flexible will provide that based on max. space available.
Column(
children: <Widget>[
Flexible(
child: ListView.builder(...)
),
Container(
color: Colors.red,
child:Text('apple 2'),
),
],
)
A nice way of doing this, it's to play with the MediaQuery, heigth and width.
Let me explain, if you want the widget to have the maximum heigth of a decide screen, you can set it like this:
Container(
height: MediaQuery.of(context).size.height // Full screen size
)
You can manipulate it by dividing by 2, 3, 400, the value you want.
The same things works for the width
Container(
width: MediaQuery.of(context).size.width // Can divide by any value you want here
)
Actually quite the opposite, if you're planning to use this as an item in a listViewyou can't let infinite size on the same axis your listView is scrolling.
Let me explain:
Currently you're not defining any height on your cell() widget, which is fine if you're using it alone. like this :
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'layout test',
home: Layout_test_class(),
));
}
class Layout_test_class extends StatelessWidget {
Widget cell() {
return Container(
color: Colors.yellow,
//height: 250, after un commenting this will work. but i want to make it without this
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: Container(
color: Colors.green,
child: Text('apple z'),
),
),
Container(
color: Colors.red,
child: Text('apple 2'),
)
],
),
),
Column(
children: <Widget>[
Container(
color: Colors.black,
width: 200,
height: 200,
),
],
),
],
),
);
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('title'),
),
body: cell(),
);
}
}
But using it with a listView you have to define a height. A listView scrolls as long as it have some content to scroll. Right now it just like you're giving it infinite content so it would scroll indefinitely. Instead Flutter is not constructing it.
It's actually quite ok to define a global size for your container (as an item). You can even define a specific size for each using a parameter like this :
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'layout test',
home: Layout_test_class(),
));
}
class Layout_test_class extends StatelessWidget {
Widget cell(double height) {
return Container(
color: Colors.yellow,
height: height, //after un commenting this will work. but i want to make it without this
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: Container(
color: Colors.green,
child: Text('apple z'),
),
),
Container(
color: Colors.red,
child: Text('apple 2'),
)
],
),
),
Column(
children: <Widget>[
Container(
color: Colors.black,
width: 200,
height: 200,
),
],
),
],
),
);
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text('title'),
),
body: ListView(
children: <Widget>[
cell(250.0),
cell(230.0),
cell(300.0),
],
)
);
}
}