6

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.

祝望舒
  • 61
  • 4

1 Answers1

8

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
  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);
          }
        },
      )
    );
  }
}
Johannes Fahrenkrug
  • 42,912
  • 19
  • 126
  • 165