Flutter GridView sometimes shows padding despite setting it to EdgeInsets.zero - flutter

I'm making a very simple Grid, basically 9 squares, and I set the padding to EdgeInsets.zero (or EdgeInsets.all(0)) and sometimes, depending on the window's size, it shows a padding, or not.
I'm testing it on chrome and linux (fedora plasma, and fedora i3wm) and the error is consistent.
The images bellow share the same code, I just resized the window slightly
Image with paddings/gaps
Image without paddings/gaps
Here's the code
import 'package:flutter/material.dart';
void main(){
runApp(MaterialApp(home: myApp));
}
// display 9 squares
Widget myApp = GridView.count(
crossAxisCount: 3,
padding: EdgeInsets.zero,
children: [
Square(),
Square(),
Square(),
Square(),
Square(),
Square(),
Square(),
Square(),
Square(),
],
);
// simple red colored square
class Square extends StatelessWidget {
Color color = Colors.red;
Square({super.key});
#override
Widget build(BuildContext context){
return Container(
color: color,
padding: EdgeInsets.zero,
);
}
}

flutter is cross-platform framework, which help to build apps with single codebase. But, the challenge is how to make the app adaptive for each platform.
You might read this documentation for full explanation: https://docs.flutter.dev/development/ui/layout/building-adaptive-apps
there many considerations, and you said that its show padding depending window size, which is one of the documentaion mentioned here: https://docs.flutter.dev/development/ui/layout/building-adaptive-apps#building-adaptive-layouts
im not explore yet, but thats my hypothesis. hope it guide you to solve the issue

Related

Flutter - Partition column into a variable size limited to half, and fill what's left with expanded

What I want to do:
I am trying to partition a column into two parts: upper (for settings) and lower (for data).
The settings part is relatively small, but might grow bigger depending on what the user adds (filters, etc...). As such, its size is unknown when building.
When the settings part grow bigger than half the column, they should be limited to half the column. The user should be able to scroll through the rest.
If it is smaller than half the column, the settings should only take the necessary space.
The data part should always fill the rest of the column: meaning, at least half of it, and if the settings container is smaller than half, then the data takes up this leftover space too.
My current "solution" is as follows:
import 'dart:io';
import 'package:flutter/material.dart';
import 'dart:math';
class TestWidget extends StatefulWidget {
final File file;
final FileInterpreter currentInterpreter;
final void Function({File? newFile, FileInterpreter<dynamic>? newInterpreter}) changeViewCallback;
const TestWidget({super.key, required this.file, required this.currentInterpreter, required this.changeViewCallback});
#override
State<TestWidget> createState() => TestWidgetState();
}
class TestWidgetState extends State<TestWidget> {
var rng = Random();
Widget? extensionPanel;
#override
Widget build(BuildContext context) => Container(
color: Colors.amber,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// just a button to test different settings sizes
Container(
color: Colors.lightBlue.shade100,
child: SizedBox(height: 25, child: Row(children: [const Text("quick settings go here"), IconButton(onPressed: () => setState(() {extensionPanel = Text("SETTINGS START${"\n - :D"*rng.nextInt(40)}\nSETTINGS END");}), icon: const Icon(Icons.keyboard_arrow_down_sharp))],)),
),
// extension of the quick settings
if (extensionPanel!=null) Flexible(
fit: FlexFit.loose,
child: Container(
color: Colors.blue,
child: SingleChildScrollView(controller: ScrollController(), child: extensionPanel,)
)
),
if (extensionPanel!=null) Container(
color: Colors.red,
child: Row(children: [const Expanded(child: Divider()), IconButton(onPressed: (){setState(() {extensionPanel=null;});}, icon: const Icon(Icons.keyboard_arrow_up)), const Expanded(child: Divider())],)
),
// Important info
Expanded(
child: Container(
color: Colors.green,
child: const Center(child: Text("I am centered, very big, and fill ALL the empty vertical space\nEven the space left behind by the settings container\n:D"),)
),
)
],
),
);
}
The widget contains sugar to collapse the settings panel and change the settings size randomly.
It gives me the following result:
When settings are big enough
When settings are not big enough
This solution contains multiple containers: the full column is orange, and contains:
Light blue: fixed size, allows to refresh the settings size.
Blue: the flexible, scroll-able settings. Should only take the necessary space, and AT MOST half the column.
Red: fixed size, to collapse the settings panel.
Green: The expanded data. Should fill ALL the remaining vertical orange space.
Analysis of the result, and issues
It is almost perfect, except:
✅ The good:
when there are a lot of settings:
-> they are limited to half the screen (blue container).
-> the green container uses the rest of the space (half the column).
-> I can scroll through the settings with no issues.
when there's not many settings:
-> the settings use up only what they need.
when I collapse the settings:
-> the green container takes all the space.
⛔ The bad:
when there's not many settings:
-> the green section uses only half the column (there's leftover orange space)
💡 What should happen:
any leftover orange space, should be given to the green container.
Other attempts
I tested multiple variations:
giving the green container more flex: ⛔ fills more space, but not all. Also, makes the settings section too small when there are a lot of settings.
giving the settings flexible container flex: 0: ⛔ if there are many settings, takes up more than half the space. Worst case, causes overflow. Also removes the scrollable property of the settings.
Using a SizedBox instead of Flexible: ⛔ I don't know how much space the settings bit will take... Different settings might need different sizes, that's why I want it flexible. Surely it cannot be this difficult?
Using two Expanded and forgetting about it: ⛔ Sure, but the green container displays a ton of very important information. Wasting almost half the column in empty space is very sad...
I also had a look at the solutions given in :
this stackoverflow forum
this github thread
Sadly, to no avail.
If you are using Flexible and Expanded in single Column or Row, then space will be allocated equally with flex value defaulted as 1.
You may have to remove Flexible widget for blue area, but surely you will face the other issue if there are many items.

Flutter GridView's children are changing their size, but fixed size is preferred

I am looking for responsive design solutions in Flutter for Web.
I am looking for solutions to make the web app responsive. However, I want the Flutter's GridView elements (specifically the Card widgets) not to change their size, but to be the same size all the time. (Changing the size of the Card widget causes errors in the display of the elements contained in that card).
I have used GridView together with the "extent" and "builder" methods in both cases, unfortunately the cards change their size, so that not all the elements contained in them are displayed correctly.
Another issue, I would like the widgets to form an rectangle, so if there are, for example, 12 cards, I want them to form a 6x2 or 4x3 or 3x4 (columns x rows) matrix, not, for example, 7+5 - which is undesirable.
The extra columns should only form when the browser window allows it, i.e. when it accommodates the extra columns (as I wrote at the beginning, this must be done without changing the size of the cards). Cards in view should be always centered.
I hope I have described the problem in detail and clearly, if not I can add graphics to better illustrate the problem.
Here is code Example (minimum working code) utilising GridView (with builder option):
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(context) =>
MaterialApp(
home: MyHomePage()
);
}
class MyHomePage extends StatelessWidget {
final List<String> elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"];
#override
Widget build(context) =>
Scaffold(
body: GridView.builder(
itemCount: elements.length,
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 130.0,
crossAxisSpacing: 20.0,
mainAxisSpacing: 20.0,
),
itemBuilder: (context, i) => Card(
child: Center(
child: Padding(
padding: EdgeInsets.all(8.0), child: Text(elements[i])
)
)
)
)
);
}
Code above don't use custom cards, I did not want to put them here, as they use images from local hard drive, which makes the code inoperable without making some extra changes. But if You need it I can put here the whole code.
Important thing: I will always have 6, 8, 10 or 12 images (didn't decided yet) on this page! Always same images count on first page.
Below image show desired cards placement, for 6 and 8 cards respectively:
Below I added undesired placement of 10 Cards widgets in Grid View. I don't want such form (4+4+2). It must be 5x2 or 2x5, to form rectangle.

Flutter pull to refresh on lower part of screen

I have a app that looks like the picture above.
When i swipe right or left, page switches along with the pageview content. but NOT the image part, it stays still.
When i implement pull to refresh on this case, the gap opens above image part showing progress indicator and refreshes.
I want the refresher crack open between image and content part, how do i achieve this?
thank you so much for reply in advance, you are the hero.
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
const Expanded(
child: SizedBox(height: 200), //The 'Image Part'
),
SmartRefresher(
child: PageView.builder(itemBuilder: (context, state) => Column(
children:[SizedBox(height:200),PageContent()], itemCount: 3),//The 'Content Part'
)
],
));
}
}
Example code added above, this represents what i would like to implement, I want the refresher only accessible inside PageContent()
but i could not because it contains Column inside
seems like an issue with your stack.
if you want the image part to also follow the swipe it has to be within the pageview.builder.
For the refresh, under pageview.builder wrap the content part in the pull-to-refresh widget but not the image.
This is possibly caused by the Stack widget
Change your Stack to a Column
Wrap your SmartRefresher with and Expanded widget for your ListviewBuilder to take the remaining space in vertical axis. You will have an unbounded high error otherwise
Set the shrinkwrap of your ListviewBuilder to true for it to build your list elements on demand
That's it! ☑️

Best way to allow a bit of overscroll in a CustomScrollView

The UI I'm making usually starts with the bottom sliver scrolled all the way in, so that its top is at the top of the view. But also:
Needs an extra empty space at the top, in case the user wants to pull the content down so that they can reach it without moving their hand from the bottom of the phone (this is a really basic ergonomic feature and I think we should expect to see it become pretty common soon, first led by apps moving more of their key functionality to the bottom of the screen, EG, Firefox's url bar.) (Currently, I'm using the appBar sliver for this, but I can imagine a full solution not using that)
Might need extra empty space at the bottom, whenever the content in that bottom sliver wont be long enough to allow it to be scrolled in all the way. It will seem buggy and irregular otherwise. Ideally I'd impose a minHeight so that the bottom sliver will always at least as tall as the screen, but it's a sliver, so I'm pretty sure that's not possible/ugly-difficult.
The avenue I'm considering right now is, ScrollPhysics wrapper that modifies its ScrollMetrics so that maxExtent and minExtent are larger. As far as I can tell, this will allow the CustomScrollView (given this ScrollPhysics) to overscroll. It feels kinda messy though. It would be nice to know what determines maxExtent and minExtent in the first place and alter that.
Lacking better options, I went ahead with the plan, and made my own custom ScrollPhysics class that allows overscroll by the given amount, extra.
return CustomScrollView(
physics: _ExtraScrollPhysics(extra: 100 * MediaQuery.of(context).devicePixelRatio),
...
And _ExtraScrollPhysics is basically just an extended AlwaysScrollable with all of the methods that take ScrollMetrics overloaded to copy its contents into a ScrollMetric with a minScrollExtent that has been decreased by -extra, then passing it along to the superclass's version of the method. It turns out that adjusting the maxScrollExtent field wasn't necessary for the usecase I described!
This has one drawback, the overscroll glow indicator, on top, appears at the top of the content, rather than the top of the scroll view, which looks pretty bad. It looks like this might be fixable, but I'd far prefer a method where this wasn't an issue.
mako's solution is a good starting point but it does not work for mouse wheel scrolling, only includes overscroll at the top, and did not implement the solution to the glow indicator problem.
A more general solution
For web, use a Listener to detect PointerSignalEvents, and manually scroll the list with a ScrollController.
For mobile, listening for events is not needed.
Extend a ScrollPhysics class as mako suggested but use NeverScrollableScrollPhysics for web to prevent the physics from interfering with the manual scrolling. To fix the glow indicator problem for mobile, wrap your CustomScrollView in a ScrollConfiguration as provided by nioncode.
Add overscroll_physics.dart from the gist.
Add custom_glowing_overscroll_indicator.dart from the other gist.
GestureBinding.instance.pointerSignalResolver.register is used to prevent the scroll event from propogating up the widget tree.
Example
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:my_project/custom_glowing_overscroll_indicator.dart';
import 'package:my_project/overscroll_physics.dart';
class OverscrollList extends StatelessWidget {
final ScrollController _scrollCtrl = ScrollController();
final double _topOverscroll = 200;
final double _bottomOverscroll = 200;
void _scrollList(Offset offset) {
_scrollCtrl.jumpTo(
_scrollCtrl.offset + offset.dy,
);
}
#override
Widget build(BuildContext context) {
return Container(
height: 300,
decoration: BoxDecoration(border: Border.all(width: 1)),
child: Listener(
onPointerSignal: (PointerSignalEvent event) {
if (kIsWeb) {
GestureBinding.instance.pointerSignalResolver.register(event, (event) {
_scrollList((event as PointerScrollEvent).scrollDelta);
});
}
},
child: ScrollConfiguration(
behavior: OffsetOverscrollBehavior(
leadingPaintOffset: -_topOverscroll,
trailingPaintOffset: -_bottomOverscroll,
),
child: CustomScrollView(
controller: _scrollCtrl,
physics: kIsWeb
? NeverScrollableOverscrollPhysics(
overscrollStart: _topOverscroll,
overscrollEnd: _bottomOverscroll,
)
: AlwaysScrollableOverscrollPhysics(
overscrollStart: _topOverscroll,
overscrollEnd: _bottomOverscroll,
),
slivers: [
SliverToBoxAdapter(
child: Container(width: 400, height: 100, color: Colors.blue),
),
SliverToBoxAdapter(
child: Container(width: 400, height: 100, color: Colors.yellow),
),
SliverToBoxAdapter(
child: Container(width: 400, height: 100, color: Colors.red),
),
SliverToBoxAdapter(
child: Container(width: 400, height: 100, color: Colors.orange),
),
],
),
),
),
);
}
}
dartpad demo
Mobile result:

Starting with plain (non material ui) canvas in flutter

All tutorials and documentation I've seen so far start of with importing flutter material. I am wondering is this an absolute requirement? What if I want to start with a plain canvas and build my own theme / widgets. Can I do this, if so what package should be used here so I get access to default widgets?
Although the answers here are correct, I want to add a few more points. To use raw widgets use import 'package:flutter/widgets.dart';
Here is a working example:
import 'package:flutter/widgets.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(25.0),
child: Directionality(
textDirection: TextDirection.ltr,
child:Text("hello world",
style: TextStyle(
color: Color.fromRGBO(255, 255, 255, 1)
),
)));
}
}
The directionality is needed, the padding was added so that we see the message, otherwise it is masked by the menubar in phone.
Widgets in flutter makes the developers day easy. All the widgets are built on top dart:ui lib. It is up to you, to decide to use existing set of widgets or develop your ui from scratch. Flutter does not stop you from writing your own widgets.
You can find a few raw example of here, that does not use any widgets at all.
If you simple don't want only material widgets, then you can just build your own themed widgets with all other basic widgets and layouts available in flutter.
If you wanted to build few of your own widgets with a canvas and use it along with other widgets in flutter, you look into CustomPaint and CustomPainter widgets.
Hope this helped!
Just as Chandan Purohit answered, use import 'package:flutter/widgets.dart';. Flutter official gives a minimal app in Introduction to widgets as follows:
import 'package:flutter/material.dart';
void main() {
runApp(
const Center(
child: Text(
'Hello, world!',
textDirection: TextDirection.ltr,
),
),
);
}
Just change flutter/material.dart to flutter/widgets.dart, and you get a plain canvas to start.
Note: The color of Text is white by default.