I am an android developer and new to flutter.
I would like to create a GridView with wrap content item height (I draw it with pen in the screenshot). But I have tried with the following code and it gave me only square grid item. I would like how to get height wrap content grid item and I have no idea and can't find how to get it. Please help. Thank you.
class CategoryItem extends StatelessWidget {
final Category category;
CategoryItem({Key key, #required this.category})
: assert(category != null),
super(key: key);
#override
Widget build(BuildContext context) {
return Card(
child: Text(
category.name,
style: TextStyle(fontSize: 34.0, fontWeight: FontWeight.bold),
),
color: Colors.amberAccent,
);
}
}
class CategoryGrid extends StatefulWidget {
final List<Category> items;
const CategoryGrid({Key key, this.items}) : super(key: key);
#override
_CategoryGridState createState() => _CategoryGridState();
}
class _CategoryGridState extends State<CategoryGrid> {
#override
Widget build(BuildContext context) {
final Orientation orientation = MediaQuery.of(context).orientation;
return Column(
children: <Widget>[
Expanded(
child: SafeArea(
top: false,
bottom: false,
child: GridView.builder(
itemCount: widget.items.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: (orientation == Orientation.portrait) ? 2 : 3,),
itemBuilder: (BuildContext context, int index) {
return CategoryItem(category: widget.items[index],);
},
),
),
),
],
);
}
}
For height you can use "childAspectRatio"
For example-
GridView.count(
childAspectRatio: 4.0,
crossAxisCount: 2,
padding: EdgeInsets.all(5.0),
children: <Widget>[
Container(
margin: EdgeInsets.only(left: 10.0, right: 10.0, top: 5.0),
child: Text(
'10:00 AM - 12:00 PM',
style: new TextStyle( color: Colors.black87, fontSize: 14.0,
fontWeight: FontWeight.normal,
),
),
);
],
shrinkWrap: true,
// todo comment this out and check the result
physics: ClampingScrollPhysics(),
)
To wrap content grid item you can use the childAspectRatio property of gridview
Ex.
GridView.builder(
itemCount: widget.items.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: (orientation == Orientation.portrait) ? 2 : 3, childAspectRatio:(MediaQuery.of(context).size.height * 0.006)),
itemBuilder: (BuildContext context, int index) {
return CategoryItem(category: widget.items[index],);
},
)
you can set childAspectRatio 0.006 instead of according to your content size
You need to set childAspectRatio attribute of SliverGridDelegateWithFixedCrossAxisCount delegate to control the height with respect to width of the grid item.
If you just want to "shrink" the height (and something like match_parent for widhth) of the text widget wrap it around a Column, Row and Expanded like this
class CategoryItem extends StatelessWidget {
final Category category;
CategoryItem({Key key, #required this.category})
: assert(category != null),
super(key: key);
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: Card(
child: Text(
category.name,
style: TextStyle(fontSize: 34.0, fontWeight: FontWeight.bold),
),
color: Colors.amberAccent,
),
),
],
),
],
);
}
}
In GridView use this line
the childAspectRatio as per yor need
childAspectRatio: double,
Related
I have data that comes from API and I want to arrange it in the form of a table as in the image attached below, but when I do that the data is repeated and I could not solve the problem, I need help on modifying the code below so that I do not want to repeat the code until the modification begins.
Clarification
The code
class StarringComponent extends StatelessWidget {
StarringComponent({Key? key}) : super(key: key);
final List<_StarringModel> _listOfStarring = [
_StarringModel('Michael J. Fox', 'https://www.biography.com/.image/ar_8:10%2Cc_fill%2Ccs_srgb%2Cfl_progressive%2Cg_faces:center%2Cq_auto:good%2Cw_620/MTkwNTAwODA4ODM0NDI2Nzc4/gettyimages-1144626740.jpg'),
_StarringModel('Christopher Lloyd', 'https://s3.r29static.com/bin/entry/9e6/0,0,2000,2000/x,80/2172764/image.jpg'),
_StarringModel('Robert Zimek', 'https://media1.popsugar-assets.com/files/thumbor/XS_IeHJo2d3MzS-0CXmqOZ0Zl34/fit-in/2048xorig/filters:format_auto-!!-:strip_icc-!!-/2019/01/08/515/n/1922398/7f748b5fb07a03fd_GettyImages-1019711520/i/Charlie-Hunnam.jpg'),
_StarringModel('Lea Thompson', 'https://www.independent.ie/migration_catalog/8bb89/25195674.ece/AUTOCROP/w620/N0217211295348219998A_1'),
];
#override
Widget build(BuildContext context) {
return Table(children: List.generate(_listOfStarring.length, (index) => _buildTableRow(context, _listOfStarring[index].name, _listOfStarring[index].image)));
}
TableRow _buildTableRow(BuildContext context, String name, String image) {
return TableRow(
children: List.generate(2, (index) => Padding(
padding: EdgeInsets.symmetric(vertical: 10),
child: Row(
children: [
CircleAvatar(
radius: 14,
backgroundColor: Colors.red.withOpacity(0.4),
backgroundImage: NetworkImage(image),
),
SizedBox(width: 10),
Flexible(
child: Text(name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
))
);
}
}
Starring model
class _StarringModel {
String name, image;
_StarringModel(this.name, this.image);
}
Use GridView for simpler implementation.
GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
...
),
itemCount: _listOfStarring.length,
itemBuilder: (BuildContext ctx, index) {
return _buildItem(
_listOfStarring[index].name,
_listOfStarring[index].image,
);
},
);
Widget _buildItem(String name, String image) {
...
}
I am trying to make UI of calculator based on my drawing,
here I have divided screen in two expanded container, top one for output and bottom one for buttons...
in bottom container I have taken grid view to show all buttons,
I want to fit all button's in bottom area without having scrolling effect of grid view.
in my code...grid view not showing last row...I have to scroll down to view it...
here is my code
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
Expanded(child: Container(color: Colors.orange,)),
Expanded(
child: GridView.builder(
itemCount: buttons.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4),
itemBuilder: (context, index) {
return ButtonWidget(color: Colors.grey,
ontap: () {},
caption: buttons[index].toString(),
textcolor: Colors.black);
})),
],
),
),
);
}
and here is button's class
class ButtonWidget extends StatelessWidget {
final color;
final textcolor;
final String caption;
final VoidCallback ontap;
const ButtonWidget(
{Key? key,
required this.color,
required this.ontap,
required this.caption,
required this.textcolor})
: super(key: key);
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
decoration: BoxDecoration(
color: Colors.grey[400],
borderRadius: BorderRadius.circular(100),
),
child: Center(
child: Text(
caption.toString(),
style: GoogleFonts.actor(fontSize: 30,fontWeight: FontWeight.bold),
)),
),
);
}
}
1.Remove your Expanded for GridView.
Disable scrolling for GridView.
Like this.
GridView.builder(
/// Add these two lines
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
/// Your code,
),
Tip:
If you don't wanna have small container on top, you can resize buttons' aspect ratio like this. But it will make buttons look a bit ugly.
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
///Add this, tweak the value as you wish
childAspectRatio: 1.3,
),
Have Fun!
I am trying find a way for the user to Scroll Parent Widget when Child Widgets have been scrolled to the top or bottom, whilst maintaining the scroll velocity / scroll momentum / scroll physics / user experience.
A good demonstration of what I'm trying to achieve (albeit without BouncingScrollPhysics): https://imgur.com/WJRCbk3
Taken from: Flutter: Continue scrolling in top Listview when reaching bottom of nested Listview
I appreciate lots of similar questions to this have been asked already, with answers relating to NotificationListener, which I have tried. However this method does not maintain the scroll velocity / scroll momentum / scroll physics / user experience, so leads to a poor quality user experience. Flutter: Continue scrolling in top Listview when reaching bottom of nested Listview looks to use a different method that might achieve the desired results, but I'm unable to get it to work correctly / with allowed operation conditions).
It seems odd there is not yet an answer to fully satisfies the desired functionality as it is very common on websites. It's clear by the number of questions on this topic, a full solution would be really appreciated.
Best Q&As so far:
Flutter : ListView : Scroll parent ListView when child ListView reach bottom - ClampingScrollPhysics not working in sized container
Is there any way to scroll parent listview when the child listview reached end in flutter?
Flutter: Continue scrolling in top Listview when reaching bottom of nested Listview
Other similar Q&As:
How to automatically start scrolling a parent scrollable widget when the child scrollable reaches the top in flutter
Flutter Nested list scroll parent when reach to end/start of inner list
I have created some basic code that can be used to test / demonstrate solutions that 'everyone' should be able to understand easily:
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: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final ScrollController _textAController = ScrollController();
final ScrollController _textBController = ScrollController();
final ScrollController _pageController = ScrollController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView(
controller: _pageController,
children: [
Container(
height: 200,
color: Colors.green,
),
Container(
height: 200,
color: Colors.red,
),
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 200),
child: SingleChildScrollView(
padding: const EdgeInsets.only(right: 15),
controller: _textAController,
physics: const ClampingScrollPhysics(),
child: Column(
children: [
const Text(
'Scrollable Child 1',
softWrap: true,
textAlign: TextAlign.center,
),
Container(
color: Colors.amber,
height: 600,
)
],
),
),
),
),
Container(
height: 10,
color: Colors.purple,
),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 200),
child: SingleChildScrollView(
controller: _textBController,
physics: const ClampingScrollPhysics(),
padding: const EdgeInsets.only(right: 15),
child: Column(
children: [
const Text(
'Scrollable Child 2',
softWrap: true,
textAlign: TextAlign.center,
),
ListView.separated(
physics: const NeverScrollableScrollPhysics(),
separatorBuilder: (BuildContext context, index) {
return Container(
height: 12,
width: 50,
color: Colors.grey,
);
},
shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 60,
color: Colors.black,
);
},
),
],
),
),
),
),
Container(
height: 100,
color: Colors.blue,
),
],
),
);
}
}
I was having the same problem until I came across your post and your links to some of the other posts trying to solve the same problem. So thanks for pointing me in the right direction. The key to it is using the velocity data provided by some of the scroll events you can listen to with the NotificationListener:
My solution is a little hackier than i'd ideally like, but the behavior is what you're after I believe.
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: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final ScrollController _textAController = ScrollController();
final ScrollController _textBController = ScrollController();
final ScrollController _pageController = ScrollController();
bool _scrolling = false;
getMinMaxPosition(double tryScrollTo){
return
tryScrollTo < _pageController.position.minScrollExtent
? _pageController.position.minScrollExtent
: tryScrollTo > _pageController.position.maxScrollExtent
? _pageController.position.maxScrollExtent
: tryScrollTo;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body:
NotificationListener(
onNotification: (ScrollNotification notification) {
//users finger is still on the screen
if(notification is OverscrollNotification && notification.velocity == 0){
var scrollTo = getMinMaxPosition(_pageController.position.pixels + (notification.overscroll));
_pageController.jumpTo(scrollTo);
}
//users finger left screen before limit of the listview was reached, but momentum takes it to the limit and beoyond
else if(notification is OverscrollNotification){
var yVelocity = notification.velocity;
_scrolling = true;//stops other notifiations overriding this scroll animation
var scrollTo = getMinMaxPosition(_pageController.position.pixels + (yVelocity/5));
_pageController.animateTo(scrollTo, duration: const Duration(milliseconds: 1000),curve: Curves.linearToEaseOut).then((value) => _scrolling = false);
}
//users finger left screen after the limit of teh list view was reached
else if(notification is ScrollEndNotification && notification.depth > 0 && !_scrolling){
var yVelocity = notification.dragDetails?.velocity.pixelsPerSecond.dy ?? 0;
var scrollTo = getMinMaxPosition(_pageController.position.pixels - (yVelocity/5));
var scrollToPractical = scrollTo < _pageController.position.minScrollExtent ? _pageController.position.minScrollExtent : scrollTo > _pageController.position.maxScrollExtent ? _pageController.position.maxScrollExtent : scrollTo;
_pageController.animateTo(scrollToPractical, duration: const Duration(milliseconds: 1000),curve: Curves.linearToEaseOut);
}
return true;
},
child: ListView(
controller: _pageController,
children: [
Container(
height: 200,
color: Colors.green,
),
Container(
height: 200,
color: Colors.red,
),
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 200),
child: SingleChildScrollView(
padding: const EdgeInsets.only(right: 15),
controller: _textAController,
physics: const ClampingScrollPhysics(),
child: Column(
children: [
const Text(
'Scrollable Child 1',
softWrap: true,
textAlign: TextAlign.center,
),
Container(
color: Colors.amber,
height: 600,
)
],
),
),
),
),
Container(
height: 10,
color: Colors.purple,
),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 200),
child: SingleChildScrollView(
controller: _textBController,
physics: const ClampingScrollPhysics(),
padding: const EdgeInsets.only(right: 15),
child: Column(
children: [
const Text(
'Scrollable Child 2',
softWrap: true,
textAlign: TextAlign.center,
),
ListView.separated(
physics: const NeverScrollableScrollPhysics(),
separatorBuilder: (BuildContext context, index) {
return Container(
height: 12,
width: 50,
color: Colors.grey,
);
},
shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 60,
color: Colors.black,
);
},
),
],
),
),
),
),
Container(
height: 100,
color: Colors.blue,
),
],
)),
);
}
}
I have a CategoryList that I want to return a list of elevated buttons that are built from some JSON data.
The buttons are built however the text does not display inside the buttons. They are drawn in the app when loaded but the text does not appear.
I have tried to modify the style for it but to no avail and I know that the name in print(categories[index].name); is available but it's not being displayed.
The image below illustrates the problem. The top container is a horizontal list of ElevatedButtons but without the text inside displayed. The container beneath that is static to show what is expected.
Has anyone else run into this issue, if so what was your workaround/solution?
class CategoryList extends StatelessWidget {
const CategoryList({Key? key, required this.categories}) : super(key: key);
final List<Category> categories;
#override
Widget build(BuildContext context) {
return GridView.builder(
padding: const EdgeInsets.all(5.0),
scrollDirection: Axis.horizontal,
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200,
childAspectRatio: 3 / 2,
crossAxisSpacing: 20,
mainAxisSpacing: 20),
itemCount: categories.length,
itemBuilder: (context, index) {
return Center(
child: ElevatedButton(
onPressed: () {
print(categories[index].id);
print(categories[index].name);
},
child: Text(categories[index].name,
style: TextStyle(
fontStyle: FontStyle
.normal)),
style: ButtonStyle(
shadowColor: MaterialStateProperty.all<Color>(Colors.black),
backgroundColor:
MaterialStateProperty.all<Color>(Colors.deepPurple),
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0),
),
),
),
),
);
},
);
}
}
childAspectRatio == 1.5, try to use a bigger value like 10 to see if it changes.
The solution that solved the problem was to use the switch the GridView.Builder for the ListView.builder and then to change the scroll direction to horizontal.
class CategoryList extends StatelessWidget {
const CategoryList({Key? key, required this.categories}) : super(key: key);
final List<Category> categories;
#override
Widget build(BuildContext context) {
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: categories.length,
itemBuilder: (context, index) {
return Padding(
padding: EdgeInsets.only(left: 7, right: 7),
child: ElevatedButton(
onPressed: () {},
child: Text(categories[index]
.name),
style: ButtonStyle(
shadowColor: MaterialStateProperty.all<Color>(Colors.black),
backgroundColor:
MaterialStateProperty.all<Color>(Colors.purple),
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0),
),
),
),
));
},
);
}
}
I am using GridView to show a different students on screen. I am using my custom made cards to show a student. Now, if the name of a student is very large, it is taking more space and rest of the cards are remaining of same size.
At first, when the name was too large, I was getting an error for less space. Then to fix that, I changed aspect ratio. But now, my screen seems too ditorted. Can you please help me out on how to fix this?
Here are the code snippets -
First, my card -
class CardItem extends StatelessWidget {
final Widget imageUrl;
final String title;
final VoidCallback function;
final BoxDecoration? bor;
final String? board;
final String? standard;
const CardItem({
Key? key,
required this.imageUrl,
required this.title,
required this.function,
this.bor,
this.board,
this.standard
})
: super(key: key);
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: function,
child: Column(
children: [
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
color: cardColor,
child: Container(
padding: EdgeInsets.all(getProportionateScreenHeight(22)),
decoration: bor,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircleAvatar(
radius: 50.0,
child: imageUrl,
),
SizedBox(
height: getProportionateScreenHeight(11),
),
Text(
title,
style: Theme.of(context)
.textTheme
.bodyText2!
.apply(color: Colors.white),
),
Padding(
padding: const EdgeInsets.only(top: 7.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
board??"",
style: TextStyle(
color: brandPurple,
fontSize: 13,
),
),
Text(
standard??"",
style: TextStyle(
color: brandPurple,
fontSize: 13,
),
),
],
),
),
],
),
),
),
],
),
);
}
}
How I used them in GridView -
child: GridView.count(
physics: ScrollPhysics(),
crossAxisSpacing:
getProportionateScreenWidth(25.0),
mainAxisSpacing:
getProportionateScreenHeight(0.0),
childAspectRatio: 2 / 3,
shrinkWrap: false,
crossAxisCount: 2,
children: [
for (int i = 0; i < dataList.length; i++)
CardItem(
imageUrl: dataList[i].avtar == null
? Image.asset(
'assets/images/profile_pic.png')
: CachedNetworkImage(
imageUrl: dataList[i].avtar!,
imageBuilder:
(context, imageProvider) =>
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover),
),
),
placeholder: (context, url) =>
CircularProgressIndicator(),
errorWidget:
(context, url, error) =>
Icon(Icons.error),
// httpHeaders: {
// "Authorization":
// 'JWT ' + token,
// },
),
title: dataList[i].name!,
board: getBoard(
dataList[i].student_current_board),
standard: getGrade(
dataList[i].student_current_board,
dataList[i].grade),
function: () {
setState(() {
selected_id = dataList[i].id!;
print(dataList[i].name);
Provider.of<APIData>(context,
listen: false)
.initializeCurrentStudent(
dataList[i]);
});
},
bor: selected_id == dataList[i].id!
? border_light()
: BoxDecoration(),
),
Add(
imageUrl:
'assets/images/add_profile_icon.svg',
title: 'Add Profile',
function: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AddProf()),
).then((value) => refresh());
},
),
],
),
Here is the UI that I am getting -
You can use
childAspectRatio: 0.75,
It will fix the height / width for all items.
Check the below code for example
class ItemCardGridView extends StatelessWidget {
const ItemCardGridView(
{Key? key,
required this.crossAxisCount,
required this.padding,
required this.items})
// we plan to use this with 1 or 2 columns only
: assert(crossAxisCount == 1 || crossAxisCount == 2),
super(key: key);
final int crossAxisCount;
final EdgeInsets padding;
// list representing the data for all items
final List<ItemCardData> items;
#override
Widget build(BuildContext context) {
return GridView.builder(
padding: padding,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
mainAxisSpacing: 40,
crossAxisSpacing: 24,
// width / height: fixed for *all* items
childAspectRatio: 0.75,
),
// return a custom ItemCard
itemBuilder: (context, i) => ItemCard(data: items[i]),
itemCount: items.length,
);
}
}
I think you can use the following package for equal and beautiful gridviews.
Here's the link: https://pub.dev/packages/flutter_staggered_grid_view
In this package you can use Masonry Gridview which will surely help you!