Related
I am trying to create one Grid View page with Cards as list elements and the last of the cards are getting cut from the bottom. The following are relevant code snippets:
createListBody.dart
List<String> services = [
'Demo1',
'Demo2',
'Demo3',
'Demo4',
'Demo5',
'Demo6',
'Demo7',
'Demo8',
'Demo9',
'Demo10',
];
#override
Widget build(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height,
child: GridView.count(
crossAxisCount: 2,
crossAxisSpacing: 2.0,
mainAxisSpacing: 2.0,
children: services.map((String serviceName){
return Container(
child:Card(
color: Colors.grey[500],
elevation: 15,
semanticContainer: true,
shadowColor: palletFuchsia,
shape: CircleBorder(),
child: InkWell(
onTap: (){
print("Tapped "+serviceName);
},
child: Center(
child: Text(
serviceName,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 25
),
),
),
),
)
);
}).toList(),
),
);
}
listScreen.dart
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: palletWhiteDrawer,
drawer: Theme(
data: Theme.of(context).copyWith(
canvasColor: palletYellowDrawer,
),
child: CreatDrawerWidget(),
),
appBar: CreateAppBar(),
body:GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
FocusScope.of(context).requestFocus(new FocusNode());
},
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ServiceListBody()
],
)
),
),
);
}
Screenshot
I am completely new to flutter so, sorry if there is some silly mistake. But any help would be useful.
Thank you for your time!
Just for exploring, you can do height: MediaQuery.of(context).size.height *2, It shouldn't get cut anymore right?
Solution 1
Your telling your container a given height which is fix for the moment, no matter how many items you got inside your ListView.
You can for example use ConstrainedBox with shrinkWrap:true and manage your max height
ConstrainedBox(
constraints: BoxConstraints(maxHeight: 200, minHeight: 56.0),
child: ListView.builder(
shrinkWrap: true,
...
more info: https://api.flutter.dev/flutter/widgets/ConstrainedBox-class.html
Solution 2
use Slivers, they are dynamic.
For this you need to customize your structure a little bit. You wrap everything with a CustomScrollView(), SliverToBoxAdapter() for fixed elements and SliverList() for dynamic ones, makes it work like a charm.
Example using CustomScrollView and SliverToBoxAdapter:
return SafeArea(
child: CustomScrollView(
//scrollDirection: Axis.vertical,
physics: BouncingScrollPhysics(),
slivers: <Widget>[
// Place sliver widgets here
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.fromLTRB(25, 30, 25, 20),
child: SizedBox(
height: 299,
child: Column(children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
...
You can define the height with the SizedBox height. Also you can apply nice effects to your whole View on the CustomScrollView
Example Sliverlist:
SliverList(
delegate: SliverChildBuilderDelegate(
(ctx, index) => ChangeNotifierProvider.value(
value: list[index],
child: GestureDetector(
onTap: () {}),
childCount: list.length,
)),
Infos about slivers: https://medium.com/flutterdevs/explore-slivers-in-flutter-d44073bffdf6
The GridView Widget itself is Scrollable , So you don't have to wrap it with a Column and SingleChildScrollView Widget...
You can now simply scroll down if the Circles goes out of the screen
If you wish all the circles to fit in the screen.. You'll have to use
var mobileHeight = MediaQuery.of(context).size.height
And then the height of each circles will be mobileHeight divided by the no. of circles vertically.
I have a text element which should be aligned to the left but for some reason it appears centered and I can't figure out why.
What code should be changed / added to make the result string aligned to the right?
return Positioned(
bottom: 0,
right: 0,
left: 0,
child: Align(
alignment: Alignment.bottomCenter,
child: Container(
margin: EdgeInsets.fromLTRB(10, 0, 10, 30),
padding: EdgeInsets.all(10),
child: Column(
children: <Widget>[
//First row here. The one below is the second row:
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Container(
padding: EdgeInsets.only(left: 5.0),
child: Icon(Icons.comment),
),
Expanded(
child: Column(children: [
Padding(
padding: EdgeInsets.only(
top: 10.0, left: 5.0, right: 0),
child: Text(
result, //This text is centered. Why?
maxLines: 7,
overflow: TextOverflow.ellipsis,
)),
]),
)
],
),
],
)
)
)
);
It's because row or column alignment.
By default column is centering children, to change that update:
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
...
If this does not solve your problem check:
Under which circumstances textAlign property works in Flutter?
More info on column/row alignment:
https://medium.com/jlouage/flutter-row-column-cheat-sheet-78c38d242041
You can use direction for this purpose.
For example:
MaterialApp(
builder: (context, child) {
return Directionality(
textDirection: TextDirection.rtl,
child: child,
);
},
);
In accordance to https://api.flutter.dev/flutter/widgets/Column-class.html on Column-Class, the Column widget does not scroll (and in general it is considered an error to have more children in a Column than will fit in the available room). If you have a line of widgets and want them to be able to scroll if there is insufficient room, consider using a ListView. For a horizontal variant, see Row.
Also, if you only have one child, then consider using Align or Center to position the child.
For your case, you could add the property crossAxisAlignment to your column as following
... Column(crossAxisAlignment: CrossAxisAlignment.end, ...
as mentioned by #nuts so the text will be align right. Here is a full example also from the above reference under https://api.flutter.dev/flutter/widgets/Expanded-class.html, that i just modified to illustrate how to do it:
/// Flutter code sample for Expanded
// This example shows how to use an [Expanded] widget in a [Column] so that
// its middle child, a [Container] here, expands to fill the space.
//
// ![This results in two thin blue boxes with a larger amber box in between.](https://flutter.github.io/assets-for-api-docs/assets/widgets/expanded_column.png)
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
/// This is the main application widget.
class MyApp extends StatelessWidget {
const MyApp({
Key ? key
}): super(key: key);
static
const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatelessWidget(),
);
}
}
/// This is the stateless widget that the main application instantiates.
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({
Key ? key
}): super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Expanded Column Sample'),
),
body: Center(
child: Column(
children: < Widget > [
Container(
color: Colors.blue,
height: 100,
width: 100,
),
Expanded(
child: Container(
color: Colors.amber,
width: 100,
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: < Widget > [
Text('We moved this text to the right')
],
),
Container(
color: Colors.blue,
height: 100,
width: 100,
),
],
),
),
);
}
}
Hope, this will help!
I've very much a beginner in Dart and Flutter, and my project is to learn while building an app. I've learned the basics with an online class and I have basis in Java.
Now, in my homepage, I'm trying to have 4 buttons take the whole screen. My current layout is to have them all take an equal amount of space and be stacked vertically.
Problem is, I can't find a way to make the RaisedButton fill automatically. I've tried retrieving the height of the screen and dividing that between the buttons but it's not working. Here's how I do it :
First, the homescreen layout:
class Homescreen extends StatelessWidget {
Widget build(context) {
double height = MediaQuery.of(context).size.height - 60;
return Scaffold(
body: SafeArea(
child: ListView(
children: [
PlanningButton(height),
Container(margin: EdgeInsets.only(top: 20.0)),
BookingButton(height),
Container(margin: EdgeInsets.only(top: 20.0)),
ModifyButton(height),
Container(margin: EdgeInsets.only(top: 20.0)),
CancelButton(height),
],
),
),
);
}
As you can see, I pass the height argument to my buttons all built around this model:
Widget PlanningButton(double pixel_y) {
return RaisedButton(
padding: EdgeInsets.all(pixel_y / 8.0),
onPressed: () {}, //goes to the planning screen
color: Colors.blue[200],
child: Text("Planning"),
);
}
Yet, it's never quite right. I don't think it's taking into account the notification bar or the navigation bar.
I suspect there is a workaround that involves wrapping the buttons into a container or using a different kind of widget that have button-like properties but I can't find what suits my needs.
Thanks for any help !
edit: I'm basically trying to do this
Try removing the variable height logic, and simply wrap your buttons in Expanded widgets. This is pretty much what it was built for.
Check it out here: https://api.flutter.dev/flutter/widgets/Expanded-class.html
You could use a Column and Expanded to do the job. If you want to make your Column scrollable, you can wrap it with SingleChildScrollView. I used SizedBox instead of Container to make the spacing because it is way more efficient and is designed for it.
class Homescreen extends StatelessWidget {
Widget build(context) {
double height = MediaQuery.of(context).size.height - 60;
return Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
Expanded(
child: PlanningButton(height)
),
SizedBox(height:20),
Expanded(
child: BookingButton(height),
),
SizedBox(height:20),
Expanded(
child: ModifyButton(height),
),
SizedBox(height:20),
Expanded(
child: CancelButton(height),
),
],
),
),
);
}
If 4 buttons are the only widgets, you dont need to use ListView just use a Column with mainAxisAlignment: MainAxisAlignment.spaceEvenly
class Sample extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
child: Container(
color: Colors.red,
child: Center(
child: RaisedButton(
onPressed: () {},
child: Text("button"),
),
),
),
),
Expanded(
child: Container(
color: Colors.blue,
child: Center(
child: RaisedButton(
onPressed: () {},
child: Text("button"),
),
),
),
),
Expanded(
child: Container(
color: Colors.green,
child: Center(
child: RaisedButton(
onPressed: () {},
child: Text("button"),
),
),
),
),
Expanded(
child: Container(
color: Colors.yellow,
child: Center(
child: RaisedButton(
onPressed: () {},
child: Text("button"),
),
),
),
),
],
),
),
);
}
}
Before you read this, it's not the best way to do things but it's one I'm using at least for prototyping. You may find using a container with a list and the argument MainAxisAlignment to work better for you.
A lot of comments here might give a solution that works for you but here's mine. I got it to work like I wanted by using SizedBox.
First, I recovered the width of the screen, then I created the buttons and put them into SizedBox fitting the screen's width, I put them in a Column and I added a few Container for the style points.
Result:
Code :
class Homescreen extends StatelessWidget {
Widget build(context) {
double width = MediaQuery.of(context).size.width; //getting screen width
return Scaffold(
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Container(margin: EdgeInsets.only(bottom: 20.0)),
PlanningButton(width),
Container(margin: EdgeInsets.only(bottom: 20.0)),
BookingButton(width),
Container(margin: EdgeInsets.only(bottom: 20.0)),
ModifyButton(width),
Container(margin: EdgeInsets.only(bottom: 20.0)),
CancelButton(width),
Container(margin: EdgeInsets.only(bottom: 20.0)),
],
),
),
);
}
Widget PlanningButton(double width) {
return Expanded(
child: SizedBox(
width: width - 20,
child: RaisedButton(
onPressed: () {},
color: Colors.blue,
child: Text(
"planning",
textScaleFactor: 4.0,
),
),
//fit: BoxFit.cover,
),
);
}
The user can tap anywhere on the colored buttons and it will register. Now I'll add images and transparency to make it look good!
I would like to create a widget that behaves same way as the horizontal scrolling lists (for example for recommended apps) in google play app.
An example of what I mean:
I have tried to use the Page Viewer widget but it does not quite do same thing. Some noticiable facts:
1) 3 perfectly squared images are fully seen and another one is partially shown. The second one (the greenish one) is right in the center.
2) The left most image (the blue one) is perfectly aligned with the Caption "Recommended for you"
3) All 4 squared images have rounded corners.
4) It supports item snapping
This is the best I have been able to mimic this behaviour with the PageView widget:
Red is the background color of the container containing the pageview.
Green is the color for even items and Blue for odd items
Inside every item an image of 100x100 is added.
This is the code:
return Container(
width: double.infinity,
color: Colors.red,
height: 100,
child:
PageView(
pageSnapping: true,
controller:
PageController(initialPage: 1, viewportFraction: 0.315),
children: List<Widget>.generate(10, (index) {
return Container(
color: index%2 ==0 ? Colors.green : Colors.blue,
child:Image.network("http://via.placeholder.com/100x100",
fit: BoxFit.fitHeight)
);
})));
Width my code there are 2 things that won't work:
1) I can't make appear a partially seen item from the right side keeping the 3 fully visible items and the second perfectly centered as in the original image form google play, just by using viewportFraction.
2) I can't apply rounded corners to the item images because as you see the container is not squared but the image is. So when I apply ClipRRect it is not applied correctly. I have tried to add another container forcing its size to 100x100 and apply ClipRRect on the image inside this container but when it is inside a PageView it seems the width/height given have no effect. They seem controlled internally by PageView.
So, Could anybody help on this problems to get something with the same behaviour as the google play horizontal scrollable list?
Cheers!
class SO extends StatelessWidget {
#override
Widget build(BuildContext context) {
double edge = 120.0;
double padding = edge / 10.0;
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.red,
padding: const EdgeInsets.symmetric(vertical: 8),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
for (int i = 0; i < 10; i++)
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.all(padding),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(edge * .2)),
child: Container(
width: edge,
height: edge,
color: Colors.blue,
child: Image.network("http://via.placeholder.com/${edge.round()}x${edge.round()}", fit: BoxFit.fitHeight),
),
),
),
Container(
width: edge + padding,
padding: EdgeInsets.only(left: padding),
child: Text(
'foo app bar baz app apk',
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
Padding(
padding: EdgeInsets.only(left: padding),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('4.2'),
Icon(
Icons.star,
size: 16,
)
],
),
),
],
)
],
),
),
),
);
}
}
which gives
You can modify PageScrollPhysics to create the snapping effect.
This what I did,
import 'package:flutter/material.dart';
Future<void> main() async {
runApp(
MaterialApp(
home: new Main(),
),
);
}
class Main extends StatefulWidget {
#override
_MainState createState() => _MainState();
}
class _MainState extends State<Main> {
static const _scrollPhysics =
const ExtentScrollPhysics(itemExtent: 80, separatorSpacing: 10);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Demo"),
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
"Recommended for you",
style: const TextStyle(fontWeight: FontWeight.bold),
),
IconButton(
icon: Icon(Icons.arrow_forward),
onPressed: () {},
)
],
),
SizedBox.fromSize(
size: Size.fromHeight(130),
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: 30,
physics: _scrollPhysics,
separatorBuilder: (context, _) =>
SizedBox(width: _scrollPhysics.dividerSpacing),
itemBuilder: (context, index) {
return SizedBox(
width: _scrollPhysics.itemExtent, // set height for vertical
child: CardItem(),
);
},
),
),
],
),
),
);
}
}
class CardItem extends StatefulWidget {
#override
_CardItemState createState() => _CardItemState();
}
class _CardItemState extends State<CardItem> {
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
AspectRatio(
aspectRatio: 1,
child: Container(
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(20.0),
),
),
),
const SizedBox(height: 8.0),
Text(
"Tile Name",
overflow: TextOverflow.ellipsis,
maxLines: 2,
style: const TextStyle(fontSize: 12.0),
),
Text(
"1 MB",
style: const TextStyle(
fontSize: 12.0,
color: Colors.black54,
),
),
],
);
}
}
class ExtentScrollPhysics extends ScrollPhysics {
final double itemExtent;
final double dividerSpacing;
const ExtentScrollPhysics(
{ScrollPhysics parent, this.itemExtent, double separatorSpacing})
: assert(itemExtent != null && itemExtent > 0),
dividerSpacing = separatorSpacing ?? 0,
super(parent: parent);
#override
ExtentScrollPhysics applyTo(ScrollPhysics ancestor) {
return ExtentScrollPhysics(
parent: buildParent(ancestor),
itemExtent: itemExtent,
separatorSpacing: dividerSpacing,
);
}
double _getItem(ScrollPosition position) {
return position.pixels / (itemExtent + dividerSpacing);
}
double _getPixels(ScrollPosition position, double item) {
return item * (itemExtent + dividerSpacing);
}
double _getTargetPixels(
ScrollPosition position, Tolerance tolerance, double velocity) {
double page = _getItem(position);
if (velocity < -tolerance.velocity)
page -= 0.5;
else if (velocity > tolerance.velocity) page += 0.5;
return _getPixels(position, page.roundToDouble());
}
#override
Simulation createBallisticSimulation(
ScrollMetrics position, double velocity) {
// If we're out of range and not headed back in range, defer to the parent
// ballistics, which should put us back in range at a page boundary.
if ((velocity <= 0.0 && position.pixels <= position.minScrollExtent) ||
(velocity >= 0.0 && position.pixels >= position.maxScrollExtent))
return super.createBallisticSimulation(position, velocity);
final Tolerance tolerance = this.tolerance;
final double target = _getTargetPixels(position, tolerance, velocity);
if (target != position.pixels)
return ScrollSpringSimulation(spring, position.pixels, target, velocity,
tolerance: tolerance);
return null;
}
#override
bool get allowImplicitScrolling => false;
}
So here's my simple use case: I want to have two buttons next to each other horizontally. In native android (which is where I come from) I would have placed them in a LinearLayout and given them weight 1 each and setting their height to wrap_content.
Now, I have placed two RaisedButton in a ButtonBar widget but where I run the app I can see the second one is getting clipped. I want them to be equally spaced and have dynamic height as per their text. How can I achieve the same in flutter? Below is what I have tried so far:
import 'package:flutter/material.dart';
class NewScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: true,
title: Text("Building layouts"),
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context, false),
)),
body: myLayoutWidget(),
),
);
}
}
// replace this method with code in the examples below
Widget myLayoutWidget() {
return Container(
child: Column(
children: <Widget>[
ButtonBar(
children: <Widget>[
RaisedButton(
onPressed: () {},
child: Text("Very long text button",),
),
RaisedButton(
child: Text("Very very very very long text button"),
color: Colors.red,
onPressed: () {},
)
],
),
],
),
);
}
This is how it looks right now:
Try using Row instead of Button Bar and add buttons to Expanded parent
Widget myLayoutWidget() {
return Container(
child: Row(
children: <Widget>[
Expanded(
child: RaisedButton(
onPressed: () {},
child: Text("Very long text button",),
),
),
Expanded(
child: RaisedButton(
child: Text("Very very very very long text button"),
color: Colors.red,
onPressed: () {},
),
)
],
),
);
}