I'm creating a PageView with a list of images, and I want to add interactiveViewer to each image so it can be resized to view details.
here is what I wrote:
PageView.builder(
dragStartBehavior: DragStartBehavior.start,
physics: _viewing ? NeverScrollableScrollPhysics() : ClampingScrollPhysics(),
controller: _pageController,
itemBuilder: (context, index) {
return Container(
child: Expanded(
child: Image.network(widget.snapshotList[index].imgUrl),
)
);
},
onPageChanged: (index) {
setState(() {
this.position = index;
_transformationController.value = Matrix4.identity();
});
},
itemCount: widget.snapshotList.length,
)
But the two scrollables seem to compete with each other and the behavior is messy.
e.g. once the image is enlarged the scroll gesture also triggers page move, seems that because the viewport is not enlarged with the image itself.
Any solutions? Thank you anyone out there.
In my case I want to create a simple swipe-able image gallery: swipe horizontally to go from image to image, and use pinch and zoom to zoom in and out. The problem is this: When I zoom in, I can't pan the enlarged image. Instead, the PageView takes over and pages to the next image.
I was able to work around it by using a TransformationController and listening to the onInteractionEnd event on the InteractiveViewer. In that callback, I check whether the image is zoomed in or not. If it's zoomed in, I deactivate paging in the PageView.
Here's a complete sample app that shows how you can implement it:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'InteractiveViewer inside PageView Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final List<ImageProvider> _imageProviders = [
Image.network("https://picsum.photos/id/1001/5616/3744").image,
Image.network("https://picsum.photos/id/1003/1181/1772").image,
Image.network("https://picsum.photos/id/1004/5616/3744").image,
Image.network("https://picsum.photos/id/1005/5760/3840").image
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: EasyImageViewPager(imageProviders: _imageProviders)
),
);
}
}
/// PageView for swiping through a list of images
class EasyImageViewPager extends StatefulWidget {
final List<ImageProvider> imageProviders;
/// Create new instance, using the [imageProviders] to populate the [PageView]
const EasyImageViewPager({ Key? key, required this.imageProviders }) : super(key: key);
@override
_EasyImageViewPagerState createState() => _EasyImageViewPagerState();
}
class _EasyImageViewPagerState extends State<EasyImageViewPager> {
final PageController _pageController = PageController();
bool _pagingEnabled = true;
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return PageView.builder(
physics: _pagingEnabled ? const PageScrollPhysics() : const NeverScrollableScrollPhysics(),
itemCount: widget.imageProviders.length,
controller: _pageController,
itemBuilder: (context, index) {
final image = widget.imageProviders[index];
return EasyImageView(
imageProvider: image,
onScaleChanged: (scale) {
setState(() {
// Disable paging when image is zoomed-in
_pagingEnabled = scale <= 1.0;
});
},
);
},
);
}
}
/// A full-sized view that displays the given image, supporting pinch & zoom
class EasyImageView extends StatefulWidget {
/// The image to display
final ImageProvider imageProvider;
/// Minimum scale factor
final double minScale;
/// Maximum scale factor
final double maxScale;
/// Callback for when the scale has changed, only invoked at the end of
/// an interaction.
final void Function(double)? onScaleChanged;
/// Create a new instance
const EasyImageView({
Key? key,
required this.imageProvider,
this.minScale = 1.0,
this.maxScale = 5.0,
this.onScaleChanged,
}) : super(key: key);
@override
_EasyImageViewState createState() => _EasyImageViewState();
}
class _EasyImageViewState extends State<EasyImageView> {
final TransformationController _transformationController = TransformationController();
@override
void dispose() {
_transformationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: InteractiveViewer(
transformationController: _transformationController,
minScale: widget.minScale,
maxScale: widget.maxScale,
child: Image(image: widget.imageProvider),
onInteractionEnd: (scaleEndDetails) {
double scale = _transformationController.value.getMaxScaleOnAxis();
if (widget.onScaleChanged != null) {
widget.onScaleChanged!(scale);
}
},
)
);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With