Flutter PageView and ListWheelScrollView asset image error - flutter

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

Related

Combine scrolling listview with draggable items that limit draggable direction to vertical?

I am attempting to have a horizontal gallery of elements which probably extends beyond the edges of the view so it needs to be horizontally scrollable. I have this. (This is primarily for a desktop app)
The elements in the gallery should be draggable but only for vertical dragging so that the user can place them somewhere else.
I've tried various approaches including a listener. Below seems to get closest to what I need, however, the picture elements are draggable in all directions. What I would like is when the user starts dragging them in a horizontal direction, instead of them being draggable, the gesture/control passes to the parent listview. So basically user can drag horizontally to scroll, and vertically to pull elements out of the gallery.
With current code, the user can scroll the listview by clicking and dragging between the elements, it seems the gesture detector never calls the onHorizontalDragStart (or onHorizontalDragUpdate.
(I have also tried with two nested GestureDetectors, one around the listview, and one around the PictElementDisplay but that didn't seem to make much sense.)
class PictGalleryView extends StatelessWidget {
PictGalleryView({
Key? key,
required this.size,
}) : super(key: key);
final Size size;
final projectSettings = GetIt.I<ProjectSettings>();
ScrollController _scrollController = ScrollController();
#override
Widget build(BuildContext context) {
return SizedBox(
height: size.height,
width: size.width,
child: ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(
dragDevices: {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
},
),
child: ListView.builder(
controller: _scrollController,
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: projectSettings.numPictElements,
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(5),
child: Center(
child: GestureDetector(
onHorizontalDragStart: (details) {
dev.log('onHorizontalDragStart');
// this doesn't happen?
},
child: PictElementDisplay(
//this shouldn't be horizontally draggable but it is!
element: projectSettings.pictElementDataList[index],
size: Size(75, 60),
),
),
),
);
},
),
),
);
}
}
//...
class PictElementDisplay extends StatelessWidget {
PictElementDisplay({
Key? key,
required this.element,
required this.size,
}) : super(key: key);
final PictElementData element;
final Size size;
#override
Widget build(BuildContext context) {
return SizedBox.fromSize(
child: Draggable(
data: element,
feedback: Container(
height: size.height,
width: size.width,
decoration: const BoxDecoration(
color: Colors.green, //todo
),
child: Text('id: ${element.id.toString()}'),
),
child: Container(
height: size.height,
width: size.width,
decoration: const BoxDecoration(
color: Colors.red, //todo
),
child: Text('id: ${element.id.toString()}'),
),
),
);
}
}
(and ChatGPT doesn't seem to quite know how to do it either. :-) ). Thanks in advance.
The GestureDetector has to be inside the Draggable then it can be used to override the default behaviour. In this use-case, you can pass in the ScrollControler associated with the parent ListView as a parameter to be modified which can control the listview. If you want to use the PictElementDisplay in different contexts and the override to only apply when it appears in the gallery, you can make the widget nullable and add logic only to change the behaviour when the scroll controller is present i.e.
class PictElementDisplay extends StatelessWidget {
PictElementDisplay({
required this.element,
required this.size,
this.scrollController,
});
final PictElementData element;
final Size size;
final ScrollController? scrollController;
#override
Widget build(BuildContext context) {
Widget listChild = Container(
height: size.height,
width: size.width,
decoration: const BoxDecoration(
color: Colors.red, //todo
),
child: Text('id: ${element.id.toString()}'),
);
Widget gestureWidget = GestureDetector(
onHorizontalDragUpdate: (details) {
// Pass the control to the parent ListView to handle horizontal scrolling
dev.log('onHorizontalDragUpdate ${details.toString()}');
scrollController?.jumpTo(scrollController!.offset - details.delta.dx);
},
onTap: () {
//E.g. add to currently active page without needing to drag it
dev.log('tapped ${element.id}');
},
child: listChild,
);
Widget draggableChildWidget =
(scrollController != null) ? gestureWidget : listChild;
return SizedBox.fromSize(
child: Draggable(
data: element,
feedback: Container(
height: size.height,
width: size.width,
decoration: const BoxDecoration(
color: Colors.green, //todo
),
child: Text('id: ${element.id.toString()}'),
),
child: draggableChildWidget,
),
);
}
}
and in the parent level
#override
Widget build(BuildContext context) {
return SizedBox(
height: widget.size.height,
width: widget.size.width,
child: ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(
dragDevices: {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
},
),
child: ListView.builder(
controller: scrollController,
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: projectSettings.numPictElements,
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
PictElementData element =
projectSettings.pictElementDataList[index];
Size size = Size(75, 60);
return Padding(
padding: const EdgeInsets.all(5),
child: Center(
child: PictElementDisplay(
element: element,
size: size,
// scrollController allows the widget to
// override its own horizontal dragging
scrollController: scrollController,
),
),
);
},
),
),
);
}

ListView keeps data even if you exit the page

Good morning/evening. Hope you are well.
I have a problem with my ListView.
I receive an Article object in the constructor of my EditArticlePage(StatefulWidget) and one of the properties of my Article is a List of links that I transmit in the constructor of my provider EditArticlePageProvider, then I retrieve these images which I display in a ListView.
When I delete an image from my ListView, it is deleted, so far, no problem ( 2 images - 1 image = 1 image).
But when I leave the EditArticlePage(press back button or back button on appbar), and come back to it, my ListView is kept with the modifications I made before, instead of the initial data of the Article.
P.S: all other data is reset except the ListView containing the images.
Here is my code:
My EditArticlePage class
class EditArticlePage extends StatefulWidget {
final Article article;
EditArticlePage(this.article, {Key key}) : super(key: key);
#override
_EditArticlePageState createState() => _EditArticlePageState();
}
class _EditArticlePageState extends State<EditArticlePage> {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<EditArticlePageProvider>(
create: (_) =>
EditArticlePageProvider(originalImages: widget.article.imagesLinks),
child: Consumer<EditArticlePageProvider>(
builder: (context, editProvider, __) {
final screenWidth = MediaQuery.of(context).size.width;
final screenHeight = MediaQuery.of(context).size.height;
return WillPopScope(
onWillPop: () {
return Future<bool>.value(true);
},
child: Scaffold(
body: ListView(
children: [
buildArticleImages(screenWidth, screenHeight, editProvider),
],
),
),
);
},
),
);
}
}
buildArticleImages methodes
Widget buildArticleImages(double screenWidth, double screenHeight,
EditArticlePageProvider editProvider) =>
Container(
padding: EdgeInsets.symmetric(horizontal: 5.0),
width: screenWidth,
height: screenHeight * 0.15,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemBuilder: (_, index) {
final imageLink = editProvider.originalImages[index];
return Stack(
children: [
SizedBox(
height: screenHeight * 0.15,
width: screenWidth * 0.35,
child: ClipRRect(
borderRadius: BorderRadius.all(
Radius.circular(9.0),
),
child: Image.network(
imageLink,
fit: BoxFit.cover,
)
),
Positioned(
child: DeleteButton(
onPressed: () {
editProvider.addToDeleteList(imageLink, index);
},
),
right: 3.0,
top: 3.0,
)
],
);
},
separatorBuilder: (_, index) => SizedBox(width: 5.0),
itemCount: editProvider.originalImages.length,
),
);
My provider
class EditArticlePageProvider extends ChangeNotifier {
final List originalImages;
EditArticlePageProvider({#required this.originalImages});
List<String> imagesToDelete = [];
List<String> get imagesToDelete => _imagesToDelete;
void addToDeleteList(String imageLink, int index) {
if (imageLink != null) {
imagesToDelete.add(imageLink);
originalImages.removeAt(index);
notifyListeners();
}
}
}
May you guy help me please ? And tell me if i'm doing wrong something THANKS!
Because the originalImages and the widget.article.imagesLinks are actually references to the same list object, when originalImages.removeAt(index); in addToDeleteList method, it will remove the image at the index of this list object and the two references are affected.
Then, if you want to keep the images in widget.article.imagesLinks, you can copy the list in EditArticlePageProvider:
EditArticlePageProvider({#required List originalImages}) : this.originalImages = List.of(originalImages);

Flutter. GridView inside Container

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:carousel_slider/carousel_slider.dart';
import 'Login.dart';
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
image:DecorationImage(
image: AssetImage("images/black_background_logo.png"),
fit: BoxFit.cover,
)
),
child: Column(
children: [
CarouselDemo(),
HomePanel()
],
),
);
}
}
List<String> images = [
'https://skalka-app.ru/banners/1.png',
'https://skalka-app.ru/banners/2.png',
'https://skalka-app.ru/banners/3.png',
] ;
class CarouselDemo extends StatelessWidget {
CarouselController buttonCarouselController = CarouselController();
#override
Widget build(BuildContext context) => CarouselSlider(
options: CarouselOptions(
height: MediaQuery.of(context).size.height*0.7,
viewportFraction: 1.0,
enableInfiniteScroll: true,
reverse: false,
autoPlay: true,
autoPlayInterval: Duration(seconds: 8),
autoPlayAnimationDuration: Duration(milliseconds: 800),
autoPlayCurve: Curves.fastOutSlowIn,
),
items: images.map((i) {
return Builder(
builder: (BuildContext context) {
return Container(
//width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height*0.7,
decoration: BoxDecoration(
color: Colors.amber
),
child: Image.network(i,fit: BoxFit.cover, height: MediaQuery.of(context).size.height*0.7,)
);
},
);
}).toList(),
);
}
class HomePanel extends StatelessWidget {
#override
Widget build(BuildContext context) {
final double height = MediaQuery.of(context).size.height;
List<String> data = <String>["Twitter", "Reddit", "YouTube", "Facebook",
"Vimeo", "GitHub", "GitLab", "BitBucket", "LinkedIn", "Medium",
"Tumblr", "Instagram", "Pinterest"];
List<RaisedButton> myWidgets = data.map((item) {
return new RaisedButton(
child: new Text(item),
onPressed: () async {
}
);
}).toList();
GridView myGrid = GridView.count(
crossAxisCount: 3,
children: myWidgets
);
return Container(
height: height*0.3,
width: MediaQuery.of(context).size.width,
color: Colors.red,
child: myGrid
);
}
}
I'm trying to add a GridView to a Container, but an indent appears at the top. Please tell me how to fix this?
I painted the Container red to show that there is a padding on top. I could not find a solution to this problem on the Internet. I'm new to Flutter, maybe I missed an important point in building this widget.
You can try wrap GridView with a MediaQuery.removePadding() then set removeTop property to True.
MediaQuery.removePadding(
context: context,
removeTop: true,
child: GridView(
.......
)
);
I have used your code pretty much, just for the Carousel, I have used the ListView.builder(). Rest is fine.
The catch is to use Expanded class inside your Column() to take the height automatically for the Carousel
Follow the code along, and see the result as well, no extra space in the UI in the GridView
class _MyHomePageState extends State<MyHomePage> {
List<String> images = [
'https://skalka-app.ru/banners/1.png',
'https://skalka-app.ru/banners/2.png',
'https://skalka-app.ru/banners/3.png',
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
height: double.infinity,
child: Column(
children: [
// Expanded used to take up the space
Expanded(
// ListView.builder, use your carousel here
child: ListView.builder(
shrinkWrap: true,
itemCount: images.length,
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext context, int index){
// look at this as well, no height, only width
// given for the image
return Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: NetworkImage(images[index])
)
)
);
}
)
),
HomePanel()
],
),
)
);
}
}
class HomePanel extends StatelessWidget {
#override
Widget build(BuildContext context) {
final double height = MediaQuery.of(context).size.height;
List<String> data = <String>["Twitter", "Reddit", "YouTube", "Facebook",
"Vimeo", "GitHub", "GitLab", "BitBucket", "LinkedIn", "Medium",
"Tumblr", "Instagram", "Pinterest"];
List<RaisedButton> myWidgets = data.map((item) {
return new RaisedButton(
child: new Text(item),
onPressed: () async {
}
);
}).toList();
GridView myGrid = GridView.count(
crossAxisCount: 3,
children: myWidgets
);
return Container(
height: height*0.3,
width: MediaQuery.of(context).size.width,
color: Colors.red,
child: myGrid
);
}
}
Result
Look at the design closely in the result, no extra spacing or padding

ListView.builder doesn't work in alertDialog

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

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