ListView.builder doesn't work in alertDialog - flutter

I use ListView.builder to show image. it is works when i show it in main page but when i use it in alertDialog it doesn't work at all. This is my code for alertDialog.
void rateVideo(BuildContext context){
var alertDialog = AlertDialog(
title: Text("video rate"),
content: RateClip(),
actions: <Widget>[
FlatButton(
child: Text('ok'),
onPressed: () {
//rateVideo(context);
Navigator.of(context).pop();
}),
],
);
showDialog(context: context,
builder: (BuildContext context){
return alertDialog;
}
);
}
and this code is about ListView.builder that i try to show images
class RateClip extends StatelessWidget {
//const ActivityClip ({Key key}) : super(key: key);
final List<String> pic = [
'assets/images/LG5.png',
'assets/images/stress.png',
'assets/images/stress.png',
'assets/images/normal.png',
'assets/images/happy.png'
];
#override
Widget build(BuildContext context) {
return Container(
height: 60,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: pic.length,
itemBuilder: (context, index) {
return Container(
width: 30,
child: ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
InkWell(
onTap: () {
},
child: Image.asset(pic[index].toString(), height: 30, width: 30),
)]));
}));
}
}

As can be seen from error it is assertion error of alert dialog. Element in alert dialog must be specific width, which you can see in log. it change based on device.
So to solve your error you have to provide specific width of container, which is above list view. To be more specific it is because your listview is horizontal and set width to infinity and that’s why it is throwing assertion error. If is was vertical listview then you have to provide height of container.
#override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width*0.75 // here i set width of container to 75% of screen
child: ListView.builder(
scrollDirection: Axis.horizontal,

here is my new code
class RateClip extends StatelessWidget {
//const ActivityClip ({Key key}) : super(key: key);
final List<String> pic = [
'assets/images/LG5.png',
'assets/images/stress.png',
'assets/images/stress.png',
'assets/images/normal.png',
'assets/images/happy.png'
];
#override
Widget build(BuildContext context) {
return Container(
//margin: const EdgeInsets.only(right: 10, left: 10, top: 200),
height: 60,
//width: 40,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: pic.length,
itemBuilder: (context, index) {
return InkWell(
onTap: () {
},
child: Image.asset(pic[index].toString(), height: 30, width: 30),
);
}));
}
}

Related

Flutter - make ListTile fill remaining screen space

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;
}

Flutter PageView and ListWheelScrollView asset image error

i use images in ListWheelScrollView in PageView. i have 3 pages and 60 same images in all pages in listwheelscrollview. when i try to change to second page (red page), I see that the images are not loading. i tried to precacheImage for all images but it didnt work.
i think the problem is with ListWheelScrollView's lazy loading. how can i load all widgets in ListWheelScrollView when ListWheelScrollView created?
i try to create sample code, i hope it is enough. there are 3 images in code but if you try 10-15 images, you will see error.
import 'package:flutter/material.dart';
class IntroPage extends StatelessWidget {
final _images = ["image1", "image3", "image2"];
late List<FixedExtentScrollController> _scrollControllers;
IntroPage({Key? key}) : super(key: key) {
_scrollControllers = List.generate(_images.length, (index) => FixedExtentScrollController(initialItem: index));
}
#override
Widget build(BuildContext context) {
// _precacheImages(context);
return Scaffold(
body: SafeArea(
child: PageView.builder(
clipBehavior: Clip.none,
itemCount: 3,
itemBuilder: (_, index) => _buildImagesWheel(index),
),
),
);
}
Widget _buildImagesWheel(final int index) {
return RotatedBox(
quarterTurns: -1,
child: ListWheelScrollView(
itemExtent: 250.0,
controller: _scrollControllers[index],
physics: const NeverScrollableScrollPhysics(),
children: [for (final image in _images) _buildImage(image)],
),
);
}
Widget _buildImage(final String image) {
return RotatedBox(
quarterTurns: 1,
child: SizedBox(
width: 250.0,
height: 250.0,
child: Image.asset(
image,
fit: BoxFit.contain,
color: Colors.black,
),
),
);
}
/// methods
void _precacheImages(final BuildContext context) {
for (final image in _images) {
precacheImage(AssetImage(image), context);
}
}
}

Draw outside listview bounds in Flutter

I want to transform my item that it is bigger than the listview itself. (intention for focused navigation)
My List:
Container(
height: 100,
child: ListView.builder(
itemBuilder: (context, index) => HomeItem(title: '$index'),
scrollDirection: Axis.horizontal,
),
),
My Item:
class HomeItem extends StatelessWidget {
final String title;
final bool expand;
const HomeItem({
#required this.title,
this.expand = false,
});
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: ThemeDimens.padding8),
child: Transform.scale(
scale: expand ? 1.5 : 1,
child: AnimatedContainer(
width: 50,
height: 100,
color: expand ? ThemeColors.accent : ThemeColors.primary,
duration: ThemeDurations.shortAnimationDuration(),
child: Center(
child: Text(title),
),
),
),
);
}
}
Current behaviour
Expected behaviour
If you try to use OverflowBox or Transform, content of an item will still clip and won't be drawn outside of its bounding box. But it's possible to use Overlay to draw an element on top of list and position it on a specific list item, though it's a bit complicated.
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
final elements = List.generate(12, (i) => i);
int selectedIndex;
OverlayEntry overlayEntry;
List<LayerLink> layerLinks;
#override
void initState() {
super.initState();
// Creating a layer link for each list cell
layerLinks = List.generate(elements.length, (i) => LayerLink());
}
void createOverlayEntry(int i, BuildContext context) {
// Removing an overlay entry, if there was one
overlayEntry?.remove();
final renderBox = context.findRenderObject() as RenderBox;
final size = renderBox.size;
final offset = renderBox.localToGlobal(Offset.zero);
// Creating a new overlay entry linked to specific list element
overlayEntry = OverlayEntry(
builder: (context) => Positioned(
left: 0,
top: 0,
child: CompositedTransformFollower(
link: layerLinks[i],
showWhenUnlinked: false,
offset: Offset(-20, 0),
child: Material(
color: Colors.yellow,
child: InkWell(
onTap: () {
setState(() {
selectedIndex = null;
});
overlayEntry?.remove();
overlayEntry = null;
},
child: Container(
alignment: Alignment.center,
width: 70,
height: elementHeight,
child: Text('$i')
),
)
),
)
)
);
// Inserting an entry
Overlay.of(context).insert(overlayEntry);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
height: elementHeight,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: elements.length,
itemBuilder: (c, i) {
return CompositedTransformTarget(
link: layerLinks[i],
child: Material(
color: Colors.red,
child: InkWell(
onTap: () {
setState(() {
selectedIndex = i;
});
createOverlayEntry(i, context);
},
child: Container(
alignment: Alignment.center,
width: 30,
child: Text('${elements[i]}'),
),
),
),
);
},
separatorBuilder: (c, i) {
return Container(width: 10, height: 10);
},
),
),
);
}
}

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, ),
),
),
),
);
}
}

How to give some space (margin/padding) between pages in PageView?

I am using PageView.builder to create pages.
PageView.builder(
itemCount: _pagesList.length,
itemBuilder: (BuildContext context, int index) {
return Container(
color: _pagesList[index],
);
}
)
What I currently have:
What I want:
i.e. I want to provide some Padding between pages (when they are being scrolled)
Reason: I will display Images in these pages, and since the Images will cover the full width of each page, it doesn't look nice when we scroll pages, since they are knitted together, like this:
How can I solve this?
PageController imagesController =
PageController(initialPage: 0, viewportFraction: 1.1);
PageView(
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: EdgeInsets.only(left: 10, right: 10),
child: Container(
color: _pagesList[index],
),
);
}
),
If you want to add padding and still have your pages as wide as the screen:
I needed this exact same thing, also for displaying images. I wanted to add padding but at the same time have each image take up the entire screen width. I figured I could use Fahad Javed's technique and tweaking it a little bit by calculating the viewPortFraction based on the screen width and padding.
#override
Widget build(BuildContext context) {
double screenWidth = MediaQuery.of(context).size.width; // screen width
double screenPad = 16.0; // screen padding for swiping between pages
int _currentPosition = 0;
return PageView.builder(
scrollDirection: Axis.horizontal,
itemCount: data.length,
controller: PageController(
initialPage: _currentPosition,
viewportFraction:
1 + (screenPad * 2 / screenWidth)), // calculate viewPortFraction
onPageChanged: (int value) {
_currentPosition = value;
},
itemBuilder: (BuildContext context, int position) {
return Padding(
padding: EdgeInsets.only(left: screenPad, right: screenPad),
child: Text('YOUR PAGE CONTENT'),
);
},
);
}
This answer from on the question asked by Amon Kataria Github
final pageController = PageController(viewportFraction: 1.1);
PageView.builder(
controller: pageController,
itemCount: _pagesList.length,
itemBuilder: (BuildContext context, int index) {
return FractionallySizedBox(
widthFactor: 1 / pageController.viewportFraction,
child: Container(
color: _pagesList[index],
),
);
},
);
Thanks #mono0926
Best effort:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
body: MyPageView()
)
);
}
}
class MyPageView extends StatefulWidget {
MyPageView({Key key}) : super(key: key);
_MyPageViewState createState() => _MyPageViewState();
}
class _MyPageViewState extends State<MyPageView> {
#override
Widget build(BuildContext context) {
return PageView(
children: <Widget>[
Container(
color: Colors.black,
child: Card(
color: Colors.red,
)
),
Container(
color: Colors.black,
child: Card(
color: Colors.blue,
),
),
Container(
color: Colors.black,
child: Card(
color: Colors.green,
),
),
],
);
}
}
You just need to add some padding around each page and the width of the page view must be at least the 'card width + the padding from both sides'. This worked for me:
class MyWidget extends StatelessWidget {
final _CARD_WIDTH = 220.0;
final PageController _controller = PageController(initialPage: 0);
#override
Widget build(BuildContext context) {
return Container(
height: _CARD_WIDTH,
width: _CARD_WIDTH + 32,
child: PageView(
scrollDirection: Axis.horizontal,
controller: _controller,
children: <Widget>[
_buildImageCard("1"),
_buildImageCard("2"),
_buildImageCard("3"),
],
),
);
}
Widget _buildImageCard(String text) {
return Padding(
padding: const EdgeInsets.only(left: 16.0, right: 16),
child: Container(
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(15),
),
width: _CARD_WIDTH,
height: _CARD_WIDTH,
child: Center(
child: Text(text),
),
),
);
}
}