How to constraints width and height of `Scaffold` (useful for Web and Desktop) - flutter

The screenshot below is a Scaffold on Desktop (should be similar on browser):
Is it possible to constraints Scaffold to a min size (width and height)?
If screen width is smaller than the min width, a horizontal scrollbar appears to move left and right.
If screen height is smaller than the min height, a vertical scrollbar appears to move up and down.
Ex. designers in our project want a min width of 960px on browser.

Here's an example main.dart that uses LayoutBuilder to constrain the scaffold (both the body and the appBar) to a min of 480dp, and if the width constraints are less than that, wraps the scaffold inside a horizontal ScrollView with a ScrollBar. And if the constraint height is less than 480dp, it wraps scaffold (which may already be wrapped or not) in a vertical scroll.
If both width and height are less than 480dp, 2 scrollbars are visible. In this case, the widget tree must be ScrollBar > ScrollBar > ScrollView > ScrollView. If widget tree is ScrollBar > ScrollView > ScrollBar > ScrollView, the nested Scrollbar is only visible when the parent ScrollBar is scrolled to the edge side.
import 'package:flutter/material.dart';
main() {
runApp(MaterialApp(
// set default isAlwaysShown, so don't need to set for individual Scrollbar.
theme: ThemeData(scrollbarTheme: ScrollbarThemeData(isAlwaysShown: true)),
home: App(),
));
}
class App extends StatefulWidget {
#override
AppState createState() => AppState();
}
class AppState extends State<App> {
final minWidth = 480.0;
final minHeight = 480.0;
ScrollController _horizontalController = ScrollController();
ScrollController _verticalController = ScrollController();
#override
void dispose() {
_horizontalController.dispose();
_verticalController.dispose();
super.dispose();
}
Widget _buildScaffold() {
return Scaffold(
appBar: AppBar(title: Text('2D Scrollbars')),
body: Container(color: Colors.amber),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.school), label: 'School'),
],
),
);
}
#override
Widget build(BuildContext context) {
final scaffold = _buildScaffold();
return LayoutBuilder(
builder: (context, constraints) {
final horizontalScrollbarEnabled = constraints.minWidth < minWidth;
final verticalScrollbarEnabled = constraints.minHeight < minHeight;
if (horizontalScrollbarEnabled && verticalScrollbarEnabled) {
return Scrollbar(
controller: _horizontalController,
child: Scrollbar(
// IMPORTANT: this scrollbar only handle notification of the vertical ScrollView.
// The first ScrollView (depth = 0), is the horizontal one.
// The second ScrollView (depth = 1), is the vertical one.
// If notification.depth != 1, the notification is bubble up to horizontal Scrollbar.
notificationPredicate: (notification) => notification.depth == 1,
controller: _verticalController,
child: SingleChildScrollView(
controller: _horizontalController,
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
controller: _verticalController,
child: Container(
width: minWidth,
height: minHeight,
child: scaffold,
),
),
),
),
);
} else if (horizontalScrollbarEnabled) {
return Scrollbar(
controller: _horizontalController,
child: SingleChildScrollView(
controller: _horizontalController,
scrollDirection: Axis.horizontal,
child: Container(
width: minWidth,
child: scaffold,
),
),
);
} else if (verticalScrollbarEnabled) {
return Scrollbar(
controller: _verticalController,
child: SingleChildScrollView(
controller: _verticalController,
child: Container(
height: minHeight,
child: scaffold,
),
),
);
}
return scaffold;
},
);
}
}

Related

Flutter How to change container height based on the ListView's item height?

Hello I have a Scaffold wrapped with SingleChildScrollView and child is Column.
Inside Column; Container, TabBar and TabBarView.
First Container is just there for black space.
Container(
color: Colors.black,
height: 300,
),
The second widget of Column which mean TabBar:
(I know we can use it in AppBar but now it is what it is.)
const TabBar(
labelColor: Colors.red,
tabs: [
Tab(text: "Tab1"),
Tab(text: "Tab2"),
Tab(text: "Tab3"),
],
),
Last Column widget is TabBarView. It wrapped by Container that has 300 height.
Container(
height: 300, // here is problem
color: Colors.amber,
child: TabBarView(
children: [
buildContainer(200, Colors.red, 2),
buildContainer(100, Colors.red, 2),
buildContainer(150, Colors.red, 3),
],
),
),
and also this is buildContainer method;
buildContainer(double height, Color color, int count) => ListView.builder(
shrinkWrap: true,
itemCount: count,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: height,
color: color,
child: Center(
child: Text("my height ${height.toString()}"),
),
),
);
});
Here is my question. I have 3 tabs and I have three ListViewBuilder.Each one has own child count. But all of them height limited to 300 because of their parent that is Container. I want to set Tab's height dynamicly with each ListViewBuilder's item count.
How can I do that ? I accept dynamic height without child scrolling. I mean, I can scroll whole page for reach the last child. In example, Instagram profile tab. If I have 30 photo, height is phone height. But I have 300 photo, it scrolling all the way down. But also, I don't want to understand pagenation for this one. I am not going to do Instagram. I just want to that, If I create 5 container, okey show me your max height. If I create 1 container, show me just that without scrolling.
I added a dynamic height calculation depending on the number of objects in the ListView.builder. The formula is not perfect, you can add to it, but the point is to subtract the AppBar height and padding to get a clean screen area that the widget completely occupies.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: mainWidget(),
);
}
Widget mainWidget() {
AppBar appBar = AppBar(
toolbarHeight: 56, //You can manually set the AppBar height
title: const Text("App bar"),
);
print(appBar.preferredSize); // Or you can save this value and use it later, it will not be fixed, but will depend on the screen size
return Scaffold(
appBar: appBar,
body: HelpSO(color: Colors.red, count: 5),
);
}
}
class HelpSO extends StatelessWidget {
late double height;
Color color;
int count;
HelpSO({Key? key, required this.color, required this.count})
: super(key: key);
#override
Widget build(BuildContext context) {
// height = (deviceHeight / itemCount) - padding (top + bottom) - appbar.prefferedSize.height / 2;
height = (MediaQuery.of(context).size.height / count) - 16.0 - 56 / 2;
return ListView.builder(
shrinkWrap: true,
itemCount: count,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(8.0), // Subtract this value
child: Container(
height: height,
color: color,
child: Center(
child: Text("My height ${height.toString()}"),
),
),
);
});
}
}
I'm new at flutter(2 m).I just come accros with this problem.
My solution was juste wrap first or second(base on your logic) container with a SingleChildScrollView
Hope it will be helpful

How to add Grids into TabbarView in Flutter?

So basically I have this widget:
class MyWidget extends StatelessWidget {
const MyWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
// ...
SliverToBoxAdapter(
child: TabBar(
controller: this._controller,
indicator: UnderlineTabIndicator(
borderSide: BorderSide(width: 1.0, color: Colors.red),
),
tabs: [
Tab(icon: Icon(CupertinoIcons.camera)),
Tab(icon: Icon(CupertinoIcons.photo)),
Tab(icon: Icon(CupertinoIcons.video_camera)),
],
),
),
SliverFillRemaining(
child: TabBarView(
controller: this._controller,
children: [
// Want Scrollable Grid here
// Want Scrollable Grid here
Center(
child: Text("Hello Reader🙂"),
),
],
),
),
// ...
],
),
);
}
}
I want to add a 2 scrollable grids as children in the TabBarView however when I use GridView.builder(...), there is an annoying gap at the top of the grid and scrolling isn't all too great neither even with shrinkWrap: true and physics: NeverScrollableScrollPhysics().
However when I use a SliverGrid(...), there is this error
RenderObjects expect specific types of children because they coordinate with their children during layout and paint. For example, a RenderSliver cannot be the child of a RenderBox because a RenderSliver does not understand the RenderBox layout protocol.
This obviously makes sense because TabBarView isn't a sliver widget. I have already taken a look at this post but it wasn't really of any help.
How could I implement this? Is there perhaps a way I could create my own widget builder that builds a custom layout?
Thank You!
You need to use SliverOverlapAbsorber/SliverOverlapInjector, the following code works for me (working full code on dart pad):
Here i used SliverFixedExtentList but you can it replace with SliverGrid.
#override
State<StatefulWidget> createState() => _NewsScreenState();
}
class _NewsScreenState extends State<NewsScreen> {
final List<String> listItems = [];
final List<String> _tabs = <String>[
"Featured",
"Popular",
"Latest",
];
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
body: DefaultTabController(
length: _tabs.length, // This is the number of tabs.
child: NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
// These are the slivers that show up in the "outer" scroll view.
return <Widget>[
SliverOverlapAbsorber(
// This widget takes the overlapping behavior of the SliverAppBar,
// and redirects it to the SliverOverlapInjector below. If it is
// missing, then it is possible for the nested "inner" scroll view
// below to end up under the SliverAppBar even when the inner
// scroll view thinks it has not been scrolled.
// This is not necessary if the "headerSliverBuilder" only builds
// widgets that do not overlap the next sliver.
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverSafeArea(
top: false,
sliver: SliverAppBar(
title: const Text('Books'),
floating: true,
pinned: true,
snap: false,
primary: true,
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
// These are the widgets to put in each tab in the tab bar.
tabs: _tabs
.map((String name) => Tab(text: name))
.toList(),
),
),
),
),
];
},
body: TabBarView(
// These are the contents of the tab views, below the tabs.
children: _tabs.map((String name) {
return SafeArea(
top: false,
bottom: false,
child: Builder(
// This Builder is needed to provide a BuildContext that is "inside"
// the NestedScrollView, so that sliverOverlapAbsorberHandleFor() can
// find the NestedScrollView.
builder: (BuildContext context) {
return CustomScrollView(
// The "controller" and "primary" members should be left
// unset, so that the NestedScrollView can control this
// inner scroll view.
// If the "controller" property is set, then this scroll
// view will not be associated with the NestedScrollView.
// The PageStorageKey should be unique to this ScrollView;
// it allows the list to remember its scroll position when
// the tab view is not on the screen.
key: PageStorageKey<String>(name),
slivers: <Widget>[
SliverOverlapInjector(
// This is the flip side of the SliverOverlapAbsorber above.
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
),
SliverPadding(
padding: const EdgeInsets.all(8.0),
// In this example, the inner scroll view has
// fixed-height list items, hence the use of
// SliverFixedExtentList. However, one could use any
// sliver widget here, e.g. SliverList or SliverGrid.
sliver: SliverFixedExtentList(
// The items in this example are fixed to 48 pixels
// high. This matches the Material Design spec for
// ListTile widgets.
itemExtent: 60.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
// This builder is called for each child.
// In this example, we just number each list item.
return Container(
color: Color((math.Random().nextDouble() *
0xFFFFFF)
.toInt() <<
0)
.withOpacity(1.0));
},
// The childCount of the SliverChildBuilderDelegate
// specifies how many children this inner list
// has. In this example, each tab has a list of
// exactly 30 items, but this is arbitrary.
childCount: 30,
),
),
),
],
);
},
),
);
}).toList(),
),
),
),
),
);
}
}

Adding item to widget dynamically doesn't update widget's height

When I add a widget to a sliding_up_panel widget dynamically after the initial load, the panel's height does not increase, meaning that the bottom of the panel gets cut off and I get the A RenderFlex overflowed by 27 pixels on the bottom. error.
What can I do to ensure the panel updates its height in this case please?
To Reproduce
Add a widget to the panel dynamically via the condition ? widget : widget logic. For example:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("SlidingUpPanelExample"),
),
body: Stack(
children: <Widget>[
Center(child: Text("This is the Widget behind the sliding panel"),),
SlidingUpPanel(
panel: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
myFirstVariable ? Text("This is a sliding Widget") : Container(),
mySecondVariable ? Text("This is another sliding Widget") : Container(),
],
),
)
)
],
)
);
}
Thanks so much!
the SlidingUpPanel widget doesn't adjust its height according to its children.
A solution to your problem would be add a SingleChildScrollView to your panel content and use the panelBuilder instead of the panel property.
The panelBuilder takes the burden of managing scrolling and sliding conflicts by providing you a controller that you can feed to your SingleChildScrollView, your code will change to be like the following :
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("SlidingUpPanelExample"),
),
body: Stack(
children: <Widget>[
Center(child: Text("This is the Widget behind the sliding panel"),),
SlidingUpPanel(
panelBuilder:(sc) => SingleChildScrollView(
controller: sc,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
myFirstVariable ? Text("This is a sliding Widget") : Container(),
mySecondVariable ? Text("This is another sliding Widget") : Container(),
],
),
)
)
],
)
);
}
Like this, you won't have any overflow issues, and when the content of the panel overflows the SingleChildScrollView will become scrollable.
hope this helps:
use a controller,
You can control the height of the panel depending on the items on the list (0.0 to 1.0). Ensure to update the UI once you add a new item.
use a List
This will solve the renderOverflow you mentioned
/// just for the example
List<Widget> items = <Widget>[];
//adding items
addItem() {
items.add(Container(
constraints: BoxConstraints(maxHeight: 200, minHeight: 200),
color: Color((Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0),
));
/// if you know in advance the height of the widgets been rendered: 200
/// if you know in advance the height of the panel: 500, or context.size.height / 2 (which would be 50% of screen) asume 600
///
/// increase panel height panel according to the number of items
///
_pc.panelPosition =
(items.length * 200) / 600 > 1.0 ? 1 : (items.length * 200) / 600;
/// Hey
/// SET State after adding the item
/// to refresh UI
}
Widget _scrollingList(ScrollController sc) {
return ListView.builder(
controller: sc,
itemCount: items.length,
itemBuilder: (context, index) {
return items[index];
},
);
}
PanelController _pc = new PanelController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("SlidingUpPanelExample"),
),
floatingActionButton: FloatingActionButton(
onPressed: addItem,
),
body: Stack(
children: <Widget>[
Center(
child: Text("This is the Widget behind the sliding panel"),
),
SlidingUpPanel(
controller: _pc,
panelBuilder: (ScrollController sc) => _scrollingList(sc),
),
],
));
Plugin has the methods: minHeight and maxHeight to do the work. I think is not posible change it dynamically without set that attributes.

how to get a full size height of customscrollview widget in flutter?

I want to get the whole height of CustomScrollView widget. So I made a below code but it's not working.
#override
void initState(){
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => getSizeAndPosition());
}
getSizeAndPosition() {
RenderBox _customScrollBox =
_customScrollKey.currentContext.findRenderObject();
_customScrollSize = _customScrollBox.size;
_customScrollPosition = _customScrollBox.localToGlobal(Offset.zero);
print(_customScrollSize.height);
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _customScrollKey,
appBar: _appbar(),
body: CustomScrollView(
controller: _controller,
slivers: [
SliverList(
delegate: SliverChildListDelegate([
_titleSection(),
_thumnail(),
SizedBox(
height: 40,
),
])),
],
),
);
}
The height obtained by this code does not take into account the height of the list in the customScrollview. I mean, _customScrollSize.height and MediaQuery.of(context).size.width are the same.
I want this function
_controller.addListener(() {
setState(() {
if (_controller.offset < 0) {
scrollHeight = 0;
} else {
scrollHeight = _controller.offset;
}
});
});
Container(
width: size.width * (scrollHeight / _customScrollSize.height),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 1)),
With the above code, '_customScrollSize.height' does not reflect the overall height and therefore the function is not implemented properly. Is there any good way to use it in this situation?
CustomScrollView
A ScrollView that creates custom scroll effects using slivers.
A CustomScrollView lets you supply slivers directly to create various scrolling effects, such as lists, grids, and expanding headers. For example, to create a scroll view that contains an expanding app bar followed by a list and a grid, use a list of three slivers: SliverAppBar, SliverList, and SliverGrid.
In your case do remove the appBar and use a sliverAppBar withing the custome scrollview , and you can use sliverfillRemaining widget for your other children
example
CustomScrollView(
slivers: <Widget>[
const SliverAppBar(
pinned: true,
expandedHeight: 250.0,
flexibleSpace: FlexibleSpaceBar(
title: Text('Demo'),
),
),
SliverList(
delegate: SliverChildListDelegate([
_titleSection(),
_thumnail(),
SizedBox(
height: 40,
),
])),
...

How to specify ListTile height in Flutter

In this code, I am trying to make a list of buttons or tiles "as buttons do not work well for me " at the very top of the page. Thus, when one is clicked it returns a value in the rest of the page.
The issue is The tile here toke around more than half of the page which makes it looks inconsistent. I want to limit the height of the tile, I have tried putting them in a row and a container and it doesn't work. Any HELP will be appreciated.
the result after running the code is:
this is the error after runing the code :
class HomePage extends StatefulWidget {
// const HomePage({Key key}) : super(key: key);
#override
HomePageState createState() {
return new HomePageState();
}
}
class HomePageState extends State<HomePage> {
List<String> temp=new List();
List<String> temp1=['Nile University', 'Smart Village', 'Zewail'];
Map<String,String> map1={};
#override
void initState() {
super.initState();
getplaces(temp);
getuser(map1,'1jKpg81YCO5PoFOa2wWR');
}
Future<List> getuser(temp,String place) async{
List<String> userids=[];
QuerySnapshot usersubs= await Firestore.instance.collection('tempSubs').getDocuments();
QuerySnapshot userid= await Firestore.instance.collection('users').where('place',isEqualTo: place).getDocuments();
userid.documents.forEach((DocumentSnapshot doc,){
usersubs.documents.forEach((DocumentSnapshot doc1){
if(doc.documentID==doc1.documentID){
doc1.data['products'].forEach((k,v){
if( DateTime.fromMillisecondsSinceEpoch(v).day==DateTime.now().day){
int x= DateTime.fromMillisecondsSinceEpoch(v).day;
print('keey equal $k and v is $x');
print('dy is $x');
userids.add(
doc.documentID);
}
});
}
} ); }
);
print('doc.documentID');
print (userids);
setState(() {});
return userids;
}
Future<List> getplaces(temp) async{
QuerySnapshot place= await Firestore.instance.collection('places').getDocuments();
place.documents.forEach((DocumentSnapshot doc){
temp.add(
doc.data['name']
);
// print(doc.data['name']);
});
// print(temp);
setState(() {});
return temp;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Home Page"),
),
body: !temp.isNotEmpty?
CircularProgressIndicator():
Row(mainAxisSize:MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children:<Widget>[
Container(
height: 100.0,
child:
ListView.builder(
scrollDirection: Axis.horizontal,
itemExtent: 100.0,
itemCount:temp.length,
itemBuilder:(BuildContext context, int index) {
return ListTile(title: Text(temp[index]),onTap:
(){
print(temp[index]);
}
);}
),),
Container(child:Text('data'),)
],),
);
}
}
Applying VisualDensity allows you to expand or contract the height of list tile. VisualDensity is compactness of UI elements. Here is an example:
// negative value to contract
ListTile(
title: Text('Tile title'),
dense: true,
visualDensity: VisualDensity(vertical: -3), // to compact
onTap: () {
// tap actions
},
)
// positive value to expand
ListTile(
title: Text('Tile title'),
dense: true,
visualDensity: VisualDensity(vertical: 3), // to expand
onTap: () {
// tap actions
},
)
The values ranges from -4 to 4 and default is 0 as of writing this answer.
However, you cannot use this method for specific width or height size.
Just remove the Expanded Widget to avoid fill the space available and use a parent Container with a fixed height, the same as the itemExtent value:
Column(
children: <Widget>[
Container(
height: 100.0,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemExtent: 100.0,
itemCount: temp.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(temp[index]),
onTap: () {
print(temp[index]);
});
}),
),
Container(
child: Text('data'),
)
],
),
You should use a Container or Padding instead of ListTile if you need more customization.
You cannot set the height, but you can make it smaller by setting the dense property to true:
ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: list.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(list[index].name,style: TextStyle(fontSize: 20.0),),
contentPadding: EdgeInsets.symmetric(vertical: 0.0, horizontal: 16.0),
dense:true,
);
},
);
ListTile:
A single fixed-height row that typically contains some text as well as
a leading or trailing icon.
To be accessible, tappable leading and trailing widgets have to be at
least 48x48 in size. However, to adhere to the Material spec, trailing
and leading widgets in one-line ListTiles should visually be at most
32 (dense: true) or 40 (dense: false) in height, which may conflict
with the accessibility requirement.
For this reason, a one-line ListTile allows the height of leading and
trailing widgets to be constrained by the height of the ListTile. This
allows for the creation of tappable leading and trailing widgets that
are large enough, but it is up to the developer to ensure that their
widgets follow the Material spec.
https://api.flutter.dev/flutter/material/ListTile-class.html
Since there's no height property in ListTile you can limit the size of a tile by placing it inside a SizedBox:
SizedBox(
height: 32,
child: ListTile(..))