Flutter Web sliver: customscrollview - flutter

I'm making a flutter web.
I want to resize a horizontal image in a CustomScrollView.
Ex: On a product detail page, I want to place the top image size 600x400 width in the center of the layout.
Regardless of the image size, it is a problem that it expands to 900x or more horizontally.
layout:
code:
class ServiceProductDetailPage extends StatelessWidget {
final String productId;
final ServiceProductModel product;
ServiceProductDetailPage({required this.product, required this.productId});
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: Colors.transparent,
body: CustomScrollView(
slivers: [
SliverAppBar(
pinned: true,
expandedHeight: 50,
title: Text('Title'),
stretch: true,
backgroundColor: Colors.black,
),
SliverToBoxAdapter(child: _Body(context, _product)),
],
),
);
}
}
Widget _Body(BuildContext context, ServiceProductModel product) {
return Card(
child: ConstrainedBox(
constraints: BoxConstraints.tight(Size(900,400)
),
child: LoadingImage(url: product.image),
),
);
}
class LoadingImage extends StatelessWidget {
const LoadingImage({
Key? key,
required this.url,
}) : super(key: key);
final String url;
#override
Widget build(BuildContext context) {
return Image.network(
url,
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
errorBuilder: (BuildContext c, Object err, StackTrace? stack) {
return const Center(child: Text('error'));},
frameBuilder: (BuildContext c, Widget image, int? frame, bool sync){
if (!sync && frame == null) {
return const Center(child: CircularProgressIndicator());
}
return image;
},
);
}
}

What you are looking for is an UnconstrainedBox. As per the documentation, the UnconstrainedBox makes itself take the full width of it's parent, but it allows it's child to be of any width the child decides.
So, you can use it like this.
Widget _Body(BuildContext context, ServiceProductModel product) {
return Card(
child: UnconstrainedBox(
child: Container(
color: Colors.deepOrange,
width: 900,
height: 400,
child: LoadingImage(url: product.image),
),
),
);
}
Next, you given some properties to you Image.network that is making it expand.
So remove the following
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
And just add this
fit: BoxFit.contain,
I have given your 900 width Container a color to see that it stays at 900 despite the window being larger than 900.

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

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

Could not complete vertical scroll of Listview inside InteractiveViewer

I have a requirement where I need to scroll through a list of images and also zoom and pan them. Very similar to a pdf document viewer. So I Used a ListView to show the pages and added the ListView as child to InteractiveViewer.
After zooming in I could not scroll to the top or bottom end of the ListView.
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: InteractiveViewer(
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: 10,
itemBuilder: (context, _index) {
print(_index);
return Container(
color: Colors.grey,
height: MediaQuery.of(context).size.width * 1.1,
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.all(2),
child: Container(
padding: EdgeInsets.all(2),
color: Colors.white,
child: Center(
child: Text('Page index: $_index'),
),
),
);
},
),
scaleEnabled: true,
panEnabled: true,
),
);
}
I guess it might be due to the InteractiveViewer handling the scroll gesture of ListView.
Is there a way to avoid vertical gesture to be handled by InteractiveViewer?
I don't think there is a way to make the two element have their scroll behavior working together, as InteractiveViewer allows you to move after zooming in your image.
Would it fulfill your requirement to set the image to fullscreen when taping on the image to zoom ?
That way you keep the scroll handled by the ScrollView and separate the InteractiveViewer to another view.
Something like that, you wrap all of your images with the ImageDetails widget and remove your InteractiveViewer from the Scaffold:
class ImageDetails extends StatelessWidget {
final String url;
const ImageDetails({Key key, this.url}) : super(key: key);
#override
Widget build(BuildContext context) {
return GestureDetector(
child: Hero(
tag: 'tag$url',
child: NetworkImage(
imageUrl: url,
fit: BoxFit.cover,
),
),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return FullScreenImage(
url: url,
);
}));
});
}
}
class FullScreenImage extends StatelessWidget {
final String url;
const FullScreenImage({Key key, this.url})
: super(key: key);
#override
Widget build(BuildContext context) {
return GestureDetector(
child: InteractiveViewer(
maxScale: 2.0,
minScale: 1.0,
child: Scaffold(
backgroundColor: Colors.black,
body: Center(
child: Hero(
tag: 'tag$url',
child: SizedBox(
width: MediaQuery.of(context).size.width,
child:
NetworkImage(imageUrl: url, fit: BoxFit.fitWidth),
),
),
),
),
),
onTap: () {
Navigator.pop(context);
},
);
}
}

Flutter prevent widget rebuild

When i select TextField()(means keyboard open) my widget tree rebuilt.
I know the reason for this is because when the keyboard is opened, the screen sizes change and the whole class is rebuilt (both StateFull and Stateless widgets). And this is normal.
And so the AnimatedList() in my class is rebuilt. I do not want it to be rebuilt (because list indexes and list items in it change). How can I prevent this AnimatedList() Widget rebuilt? Or is it possible to prevent the class from being rebuilt when the keyboard is opened in general?
Please Help Me
Here my code:
class CommentPage extends StatelessWidget {
final String activityname;
final String postid;
final String usernameuid;
const CommentPage({
Key key,
#required this.activityname,
#required this.postid,
#required this.usernameuid,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return SafeArea(
child: Container(
height: 400,
child: Scaffold(
resizeToAvoidBottomInset: false,
resizeToAvoidBottomPadding: false,
appBar: AppBar(
title: Text('Coments'),
centerTitle: true,
),
body: Container(
height: 350,
width: 300,
child: ChangeNotifierProvider(
create: (context) => CommentLikeController(),
builder: (context, child) => FutureBuilder<List<Comment>>(
future:
Provider.of<CommentLikeController>(context, listen: false)
.initial(),
builder: (context, snapshot) {
if (snapshot.hasData)
return Stack(children: [
Positioned(
bottom: 50,
child: Container(
height: 350,
width: 300,
child: AnimatedList(
shrinkWrap: true,
reverse: true,
key: Provider.of<CommentLikeController>(context)
.listkey,
initialItemCount: snapshot.data.length,
itemBuilder: (context, index, animation) {
return ListItem(
index: index,
postid: postid,
activityname: activityname,
usernameuid: usernameuid,
);
},
),
),
),
Positioned(
bottom: 0,
left: 0,
child: Container(
height: 50,
width: 350,
child: TextField(),
),
),
]);
else
return LinearProgressIndicator();
}),
),
),
),
),
);
}
}
Create another StatefulWidget and place your TextField widget inside it. Create the text field controller in your CommentPage and pass it as parameter to your new created StatefulWidget and use it as the the TextField's controller. The keyboard effect will only impact the new created StatefulWidget

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