Flutter - make ListTile fill remaining screen space - flutter

My Flutter app displays a list of events. When there are no events to display, a message is displayed saying "no events to display":
Center(child: Text("No Events to Display"))
This resulted in the following which is what is required:
However, this means the users cannot pull down to refresh (the Center widget is not in a ListView). So I added a ListView to hold the Center widget so the list of events could be refreshed:
Widget buildNoEvents() {
final noEventMessage = ListView.builder(
padding: const EdgeInsets.all(10),
physics: const AlwaysScrollableScrollPhysics(),
itemCount: 1,
itemBuilder: (context, index) =>
Center(child: Text("No Events to Display")));
return noEventMessage;
}
This results in the following, where the Center widget is positioned at the top of the screen:
The message needs to be in the centre of the screen.
Additionally, to complicate matters, there is also a requirement to display urgent messages at the top of the screen, above the list of events:
Widget buildNoEvents() {
Bulletin bulletin = getBulletin();
final noEventMessage = ListView.builder(
padding: const EdgeInsets.all(10),
physics: const AlwaysScrollableScrollPhysics(),
itemCount: 1,
itemBuilder: (context, index) =>
Center(child: Text("No Events to Display")));
if (bulletin.showBulletin) {
return Column(
children: [
BulletinView(bulletin: bulletin),
Expanded(child: noEventMessage)
],
);
} else {
return noEventMessage;
}
}
class BulletinView extends StatelessWidget {
final Bulletin bulletin;
const BulletinView({super.key, required this.bulletin});
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 10, left: 10, right: 10),
child: ListTile(
tileColor: const Color.fromARGB(255, 244, 232, 232),
leading: const CircleAvatar(
backgroundColor: Colors.red,
child: Text(
"!",
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 244, 232, 232),
),
)),
title: Text(bulletin.title),
subtitle: Text("Corner of Eden Avenue")));
}
}
Note the Expanded widget - if I don't use Expanded to wrap the ListView I get the following exception:
════════ Exception caught by rendering library ═════════════════════════════════
The following assertion was thrown during performResize():
Vertical viewport was given unbounded height.
This results in the following UI:
The "bulletin" is correctly positioned at the top of the screen, above the ListView, but the "no events..." message is not correctly positioned in the centre of the screen. The ListView is correctly taking up the whole of the screen below the bulletin and responds to pull to refresh.
How can I force the ListView element for "no events to display" to fill the screen and therefore centre the "no events..." text?
STRIPPED DOWN CODE
class EventListScreen extends StatefulWidget {
#override
_EventListScreenState createState() => _EventListScreenState();
const EventListScreen({Key? key}) : super(key: key);
}
class _EventListScreenState extends State<EventListScreen> {
List<Event> events = [];
Future<List<Event>> getData() async {
events = await Network.getUsers(context);
return events;
}
Future<void> refreshData() async {
await Network.getUsers(context);
setState(() {});
}
#override
build(context) {
return PlatformScaffold(
body: RefreshIndicator(
onRefresh: refreshData,
child: FutureBuilder<List<Event>>(
future: getData(),
builder: (context, snapshot) {
return buildNoEvents();
},
),
));
}
Widget buildNoEvents() {
final noEventMessage = ListView.builder(
padding: const EdgeInsets.all(10),
physics: const AlwaysScrollableScrollPhysics(),
itemCount: 1,
itemBuilder: (context, index) =>
const Center(child: Text("No Events to Display")));
if (getBulletin().showBulletin) {
return Column(
children: [
BulletinView(bulletin: getBulletin()),
Expanded(child: noEventMessage)
],
);
} else {
return noEventMessage;
}
}
Bulletin getBulletin() {
return const Bulletin(title: "WARNING!", message: "News...", showBulletin: true); // dummy for demo purposes
}
}
class Bulletin {
final bool showBulletin;
final String title;
final String message;
const Bulletin({required this.title, required this.message, required this.showBulletin});
}
class BulletinView extends StatelessWidget {
final Bulletin bulletin;
const BulletinView({super.key, required this.bulletin});
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 10, left: 10, right: 10),
child: ListTile(
tileColor: const Color.fromARGB(255, 244, 232, 232),
leading: const CircleAvatar(
backgroundColor: Colors.red,
child: Text(
"!",
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 244, 232, 232),
),
)),
title: Text(bulletin.title),
subtitle: Text(bulletin.message)));
}
}

You can wrap the noEventMessage in Center widget and add shrink-wrap:true
Widget buildNoEvents() {
Bulletin bulletin = getBulletin();
final noEventMessage = ListView.builder(
shrinkWrap: true, //<---add this
padding: const EdgeInsets.all(10),
physics: const AlwaysScrollableScrollPhysics(),
itemCount: 1,
itemBuilder: (context, index) =>
Center(child: Text("No Events to Display")));
if (bulletin.showBulletin) {
return Column(
children: [
BulletinView(bulletin: bulletin),
Expanded(child: noEventMessage)
],
);
} else {
return Center(child:noEventMessage); //<--add center widget here
}
}
However pull to refresh doesn't require you to add these elements in Listview. You can still wrap the main Column widget with a Refresh indicator and it will still work
Edit
Widget noEventMessage = SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: Container(
child: Center(
child: Text('Hello World'),
),
height: MediaQuery.of(context).size.height,
)
);
// And just return the Widget without the center
if (bulletin.showBulletin) {
return Column(
children: [
BulletinView(bulletin: bulletin),
Expanded(child: noEventMessage)
],
);
} else {
return noEventMessage;
}

Related

Flutter ListView set 1 complete container at a time

I have a very simple ListView in which I have a container of full height
class TestApp extends StatelessWidget {
final List<MessageItem> items;
TestApp({Key? key, required this.items}) : super(key: key);
#override
Widget build(BuildContext context) {
final title = 'Mixed List';
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Column(
children: [
Expanded(
child: ListView.builder(
physics: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics()),
itemCount: 3,
itemBuilder: (context, index) {
return ColorsBox(context);
},
),
),
],
),
),
);
}
Widget ColorsBox(context) {
return Container(
height: MediaQuery.of(context).size.height * 1,
color: Color((math.Random().nextDouble() * 0xFFFFFF).toInt())
.withOpacity(1.0));
}
}
What I need to do is I need to set 1 container only at a time. Mean no half container will be shown only 1 colour container show on 1 time. If scroll then the second container then 3rd so on. Basically trying some TikTok stuff on ListView
You should Use PageView.Builder
PageController controller = PageController();
var currentPageValue = 0.0;
PageView.builder(
scrollDirection: Axis.vertical,
controller: controller,
itemCount: 10,
itemBuilder: (context, position) {
return Container(
color: position % 2 == 0 ? Colors.blue : Colors.pink,
child: Center(
child: Text(
"Page",
style: TextStyle(color: Colors.white, fontSize: 22.0),
),
),
);
},
)
It's already working Fine.
return Container(
margin: EdgeInsets.only(bottom: 15),
height: MediaQuery.of(context).size.height,
color: Colors.blue
.withOpacity(1.0));
}

Flutter Futurebuilder inside TabBarView not triggering the future after initial application load while switching between tabs?

I am new to flutter and trying to implement a tabview in homePage in the flutter app.
The first tab is populated from data from firebase remote config and second tab is populated by using Futurebuilder. When I switch the tabs the future function is not triggering. It is only triggered during initial application load. Whenever I switch tabs and come back to 2nd tab. The futurebuilder's future function is not triggered again.
Can someone give any solutions for this.?
Container(
width: MediaQuery.of(context).size.width,
color: Colors.white,
child: GridView.count(
shrinkWrap: true,
physics: BouncingScrollPhysics(),
padding: const EdgeInsets.all(4.0),
childAspectRatio: 1.0,
crossAxisCount: isTablet ? 2 : 1,
crossAxisSpacing: 4.0,
mainAxisSpacing: 4.0,
children: [
FutureBuilder(
future: _getBookmarks,
builder:
(BuildContext context, AsyncSnapshot snapshot) {
var listWidget;
if (snapshot.connectionState ==
ConnectionState.done) {
if (snapshot.data.length == 0) {
listWidget = Container(
child: Center(
child: Text("No Favorites to Display!"),
));
} else {
listWidget = ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
final bookmarks = snapshot.data[index];
return BuildFavoriteCard(
bookmarks, context);
},
);
}
} else {
listWidget = Center(
child: CircularProgressIndicator(),
);
}
return listWidget;
})
],
))
Here's an example combining the TabBar and FutureBuilder examples of the Flutter documentation.
If you run this, you will see that a new future is created each time you navigate to the first tab (since the TabBarView's content is rebuilt).
I would assume that this is currently not working for you since your future _getBookmarks is defined somewhere higher up in the widget tree (in the part that is not rebuilt by switching tabs).
The solution would be to move the future inside your TabBarView widget.
import 'package:flutter/material.dart';
void main() {
runApp(const TabBarDemo());
}
class TabBarDemo extends StatelessWidget {
const TabBarDemo({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
bottom: const TabBar(
tabs: [
Tab(icon: Icon(Icons.directions_car)),
Tab(icon: Icon(Icons.directions_transit)),
],
),
title: const Text('Tabs Demo'),
),
body: TabBarView(
children: [
Center(
child: MyStatefulWidget(),
),
Icon(Icons.directions_transit),
],
),
),
),
);
}
}
/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({
Key? key,
}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
/// This is the private State class that goes with MyStatefulWidget.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
Future<String>? _calculation;
#override
void initState() {
_calculation = Future<String>.delayed(
const Duration(seconds: 2),
() => 'Data Loaded',
);
super.initState();
}
#override
Widget build(BuildContext context) {
return DefaultTextStyle(
style: Theme.of(context).textTheme.headline2!,
textAlign: TextAlign.center,
child: FutureBuilder<String>(
future:
_calculation, // calculation, // a previously-obtained Future<String> or null
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
List<Widget> children;
if (snapshot.hasData) {
children = <Widget>[
const Icon(
Icons.check_circle_outline,
color: Colors.green,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Result: ${snapshot.data}'),
),
];
} else if (snapshot.hasError) {
children = <Widget>[
const Icon(
Icons.error_outline,
color: Colors.red,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Error: ${snapshot.error}'),
)
];
} else {
children = const <Widget>[
SizedBox(
child: CircularProgressIndicator(),
width: 60,
height: 60,
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Awaiting result...'),
)
];
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: children,
),
);
},
),
);
}
}

Flutter include content into a page

I actually have a searchBar(autocomplete) that is working.
When i select a result, the displaySnack() is working, it displays a snackBar, but i would like to display the content of testList().
My goal is to understand how I can launch another widget, to be able to add new widget on the page again and again.
My final goal is once i have the selected value, to make an http request, get a list as return and display a listview.
The function is executed ( i can see it in debugger ) but doesn't display anything..
(i'm new to flutter, so please explain your response if possible :) )
onSuggestionSelected : yes i know that it is void..
import 'package:drawer/src/share/snack_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import '../models/post_model.dart';
import '../services/http_service.dart';
// import 'package:http/http.dart' as http;
class PostsPage extends StatelessWidget {
final String title;
const PostsPage({Key? key, required this.title}) : super(key: key);
static Future<List<Post>> filterList(String value) async {
List<Post> list = await HttpService.fetchPosts();
return list.where(
(x) => x.title.toLowerCase().contains(value.toLowerCase())).toList();
}
#override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: Text(title),
),
body: SafeArea(
child: Container(
padding: EdgeInsets.all(16),
child: TypeAheadField<Post?>(
debounceDuration: Duration(milliseconds: 500),
hideSuggestionsOnKeyboardHide: false,
textFieldConfiguration: TextFieldConfiguration(
decoration: InputDecoration(
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(),
hintText: 'Select the namespace...',
),
),
suggestionsCallback: filterList,
itemBuilder: (context, Post? suggestion) {
final user = suggestion!;
return ListTile(
title: Text(user.title),
);
},
noItemsFoundBuilder: (context) => Container(
height: 100,
child: Center(
child: Text(
'No Namespace Found.',
style: TextStyle(fontSize: 24),
),
),
),
onSuggestionSelected: (Post? suggestion) {
final user = suggestion!;
displaySnack(context, ' Namespace: '+user.title);
testList(context); ################################ HERE
},
),
),
),
);
}
Widget testList(BuildContext context) {
return ListView.separated(
separatorBuilder: (BuildContext context, int index) => const Divider(),
itemCount: 2,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text("ppp"),
subtitle: Text("ppp"),
leading: CircleAvatar(
backgroundImage: NetworkImage(
"https://images.unsplash.com/photo-1547721064-da6cfb341d50"))
));
});
}
I need that : https://prnt.sc/136njev
It is obvious that you want the widget to rebuild to show the result. The most straightforward method is to use StatefulWidget. So I use it in your case(You can also find lots of ways to manage the state List of state management approaches)
Change your PostsPage to a StatefulWidget and rebuild when the user is selected
Add a Column in your PostsPage and separate into 2 parts: TypeAheadField & Result
Result part can use FutureBuilder (which can show loading indicator when data is not ready)
PostsPage:
class PostsPage extends StatefulWidget {
final String title;
const PostsPage({Key? key, required this.title}) : super(key: key);
static Future<List<Post>> filterList(String value) async {
// skip
}
#override
_PostsPageState createState() => _PostsPageState();
}
class _PostsPageState extends State<PostsPage> {
Post? user;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SafeArea(
child: Column(
children: [
Container(
padding: EdgeInsets.all(16),
child: TypeAheadField<Post>(
// ...
// skip
// ...
onSuggestionSelected: (Post? suggestion) {
setState(() {
user = suggestion!;
displaySnack(context, ' Namespace: '+user.title);
});
},
),
),
Expanded(child: MyResult(post: user)),
],
),
),
);
}
}
Result part:
(I make it an isolated StatelessWidget just for better reading. You can use the original method to build the widget)
class MyResult extends StatelessWidget {
const MyResult({
required this.post,
Key? key,
}) : super(key: key);
final Post? post;
Future<List<OtherObject>> getOtherObjects(Post? post) async{
if(post == null){
return [];
}else{
return Future.delayed(Duration(seconds:3),()=>[OtherObject(title: '001'),OtherObject(title: '002'),OtherObject(title: '003')]);
}
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<OtherObject>>(
future: getOtherObjects(post),
builder: (context, snapshot) {
if(snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
final result = snapshot.data!;
return ListView.separated(
separatorBuilder: (BuildContext context,
int index) => const Divider(),
itemCount: result.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text(result[index].title),
subtitle: Text("ppp"),
leading: CircleAvatar(
backgroundImage: NetworkImage(
"https://images.unsplash.com/photo-1547721064-da6cfb341d50"),
),
),
);
},
);
}else {
return Center(child: CircularProgressIndicator());
}
}
);
}
}
So what you are doing is basically just creating a ListView with your testList() function call and doing nothing with it, but what you want to do is to have that widget show up on the screen, right?
Flutter doesn't just show Widget if you create a new one, you must tell it to render. Just imagine you are doing preparing Widgets (e.g. Widgets in Widgets) and Flutter would render it immediately to the screen without you being finished, that wouldn't be that great.
You need to push that Widget over the Navigator widget that Flutter provides you.
onSuggestionSelected: (Post? suggestion) {
final user = suggestion!;
displaySnack(context, ' Namespace: '+user.title);
Navigator.push(
context,
MaterialPageRoute(builder: (context) => testList(context)),
);
}
I suggest you to read this article to Navigation Basics.
you can use listView builder to show the selected results.
onSuggestionSelected: (Post? suggestion) {
final user = suggestion!;
displaySnack(context, ' Namespace: '+user.title);
//get results
var results = fetchResult(suggestion);
//return a listview of the results
return ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemCount: results.length,
itemBuilder: (_context, index) {
Post post = results[index];
return Card(
elevation: 2,
child: InkWell(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
border: Border.all(color: Colors.black),
),
child: DefaultTextStyle(
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
color: Colors.white),
child: Row(children: [
Expanded(
flex: 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(post.data),
],
),
),
]),
),
),
onTap: () {
//do something when user clicks on a result
},
));
}
},
If you want to show a list of selected items then you will have to add a ListView in the widget tree. Also use a StatefullWidget instead of StatelessWidget, because whenever you select an item, the selected list gets changed thus state.
sample code for state
List<Post> selectedPosts;
#override
void initState() {
super.initState();
selectedPosts = [];
}
#override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: Text(title),
),
body: SafeArea(
child: Column(
children: [
Container(
padding: EdgeInsets.all(16),
child: TypeAheadField<Post?>(
debounceDuration: Duration(milliseconds: 500),
hideSuggestionsOnKeyboardHide: false,
textFieldConfiguration: TextFieldConfiguration(
decoration: InputDecoration(
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(),
hintText: 'Select the namespace...',
),
),
suggestionsCallback: filterList,
itemBuilder: (context, Post? suggestion) {
final user = suggestion!;
return ListTile(
title: Text(user.title),
);
},
noItemsFoundBuilder: (context) => Container(
height: 100,
child: Center(
child: Text(
'No Namespace Found.',
style: TextStyle(fontSize: 24),
),
),
),
onSuggestionSelected: (Post? suggestion) {
final user = suggestion!;
displaySnack(context, ' Namespace: '+user.title);
setState(()=> selectedPosts.add(suggestion));
},
),
),
testList(context),
],
),
),
);
and in the testList function change the itemcount
itemCount: 2,
to
itemCount: selectedPosts?.length ?? 0,

How to show different listviews from pressing the item of a diffrent listview in the same screen in Flutter?

Suppose, I have a list List1 = [A,E,M,X] which I can show using listview.builder horizontally. I have some other lists such as List2 = [a,b,c,d], List3 = [e,f,g,h] etc which I can show under the listview of List1 on a similar way.
What I want is whenever a user presses A from List1, the below listview will automatically show the list List2 = [a,b,c,d]. When a user presses B from List1, the below list will automatically show the list List3 = [e,f,g,h]. I used GestureDetector to detect the press in List1 which works fine. And, all these has to happen in one screen.
Here is my home screen where I'm calling those 2 listviews,
Scaffold(
body: ListView(
padding: EdgeInsets.only(top: 50, left: 20, right: 20),
children: <Widget>[
HomeTopInfo(),
FoodCategory(),
SubFoodCategory(),
SizedBox(
height: 20.0,
),
This is the first list
class FoodCategory extends StatefulWidget {
#override
_FoodCategoryState createState() => _FoodCategoryState();
}
class _FoodCategoryState extends State<FoodCategory> {
final List<Category> _catagories = categories;
int _selectedIndex = 0;
_onSelected(int index) {
setState(() => _selectedIndex = index);
}
#override
Widget build(BuildContext context) {
return Container(
height: 80.0,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: _catagories.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {
_onSelected(index);
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
child: Column(
children: <Widget>[
Text(
_catagories[index].title,
style: _selectedIndex != null && _selectedIndex == index
? TextStyle(
color: kTextColor,
fontWeight: FontWeight.bold,
)
: TextStyle(fontSize: 12),
),
if (_selectedIndex != null && _selectedIndex == index)
Container(
margin: EdgeInsets.symmetric(vertical: 5),
height: 3,
width: 22,
decoration: BoxDecoration(
color: kPrimaryColor,
borderRadius: BorderRadius.circular(10),
),
),
],
),
),
);
},
),
);
}
}
This is the second list which I'm showing under the first list
class SubFoodCategory extends StatelessWidget {
final String category;
SubFoodCategory({Key key, this.category}) : super(key: key);
final List<SubCategory> _subCategories = subCategories;
#override
Widget build(BuildContext context) {
print(category);
return Container(
height: 80.0,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: _subCategories.length,
itemBuilder: (BuildContext context, int index) {
return FoodCard(
subCategoryName: _subCategories[index].subCategoryName,
imagePath: _subCategories[index].imagePath,
numberOfItems: _subCategories[index].numberOfItems,
);
},
),
);
}
}
I have seperate model class for both the list and used dummy data to populate all this. Which I'm doing right now is showing to list diffrently.
What I want is whenever I press a list item from the first list, I want to show a similar type of list under the first list.

Flutter GridView.Builder isn't scrolling (height issues)

I'm trying to make a grid of products using GridView.Builder but it gives error :
Vertical viewport was given unbounded height.
I tried to use flexible on GridView it worked but I need to use GridView.Builder Specifically
and if I tried to wrap it with Flexible or specific height container it doesn't scroll ,any tips?
import 'package:flutter/material.dart';
class Products extends StatefulWidget {
#override
_ProductsState createState() => _ProductsState();
}
class _ProductsState extends State<Products> {
var productList=[
{
"name":"Blazer",
"picture":"images/products/blazer1.jpeg",
"oldPrice":120,
"price":100
},
{
"name":"Dress",
"picture":"images/products/dress1.jpeg",
"oldPrice":120,
"price":100
},
{
"name":"hills",
"picture":"images/products/hills1.jpeg",
"oldPrice":11,
"price":10
},
{
"name":"pants",
"picture":"images/products/pants2.jpeg",
"oldPrice":12,
"price":200,
}
];
#override
Widget build(BuildContext context) {
return GridView.builder(
scrollDirection: Axis.vertical,
itemCount: productList.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemBuilder: (context,index){
return SingalProduct(
name: productList[index]['name'],
picture: productList[index]['picture'],
oldPrice: productList[index]['oldPrice'],
price: productList[index]['price'],
);
},
);
}
}
class SingalProduct extends StatelessWidget {
final name,picture,oldPrice,price;
SingalProduct({this.name,this.picture,this.oldPrice,this.price});
#override
Widget build(BuildContext context) {
return Card(
child: Hero(
tag: name,
child: InkWell(
onTap: (){},
child: GridTile(
footer: Container(
height: 40,
color: Colors.white,
child: Padding(
padding: EdgeInsets.fromLTRB(8, 12, 0, 0),
child: Text(name,textAlign: TextAlign.start,style: TextStyle(fontWeight: FontWeight.bold,fontSize: 16),),
),
),
child: Image.asset(picture,fit: BoxFit.cover, ),
),
),
),
);
}
}