Saltar al contenido

Cómo hacer zoom en la imagen dentro de ListView en flutter

Solución:

Corríjame si me equivoco, pero desde el stacktrace creo que su problema es que está tratando de agregar un niño con un tamaño desconocido dentro de un padre también con un tamaño desconocido y el aleteo no calcula el diseño. Para resolver este problema, debe crear un widget con un tamaño fijo (probablemente calculado a partir del estado inicial de su hijo, por ejemplo, Image en tu caso) como ClipRect.

Aunque esto resuelve el error; Te deja con un comportamiento glitchy porque en tu caso nos enfrentamos a un Desambiguación de gestos como se menciona aquí, lo que significa que tiene múltiples detectores de gestos tratando de reconocer gestos específicos al mismo tiempo. Para ser exactos, uno que maneja scale que es un superconjunto de pan que se utiliza para hacer zoom y paneo de su imagen y uno que maneja drag que se utiliza para desplazarse en su ListView. Para superar este problema, creo que necesita implementar un widget que controle los gestos de entrada y decida manualmente si declarar la victoria o declarar la derrota en arena de gestos.
He adjuntado algunas líneas de código que encontré aquí y allá para implementar el comportamiento deseado, necesitará la biblioteca flutter_advanced_networkimage:

ZoomableCachedNetworkImage:

class ZoomableCachedNetworkImage extends StatelessWidget 
  String url;
  ImageProvider imageProvider;

  ZoomableCachedNetworkImage(this.url) 
    imageProvider = _loadImageProvider();
  

  @override
  Widget build(BuildContext context) 
    return new ZoomablePhotoViewer(
      url: url,
    );
  

  ImageProvider _loadImageProvider() 
    return new AdvancedNetworkImage(this.url);
  


class ZoomablePhotoViewer extends StatefulWidget 
  const ZoomablePhotoViewer(Key key, this.url) : super(key: key);

  final String url;

  @override
  _ZoomablePhotoViewerState createState() => new _ZoomablePhotoViewerState();


class _ZoomablePhotoViewerState extends State
    with SingleTickerProviderStateMixin 
  AnimationController _controller;
  Animation _flingAnimation;
  Offset _offset = Offset.zero;
  double _scale = 1.0;
  Offset _normalizedOffset;
  double _previousScale;
  HitTestBehavior behavior;

  @override
  void initState() 
    super.initState();
    _controller = new AnimationController(vsync: this)
      ..addListener(_handleFlingAnimation);
  

  @override
  void dispose() 
    _controller.dispose();
    super.dispose();
  

  // The maximum offset value is 0,0. If the size of this renderer's box is w,h
  // then the minimum offset value is w - _scale * w, h - _scale * h.
  Offset _clampOffset(Offset offset) 
    final Size size = context.size;
    final Offset minOffset =
        new Offset(size.width, size.height) * (1.0 - _scale);
    return new Offset(
        offset.dx.clamp(minOffset.dx, 0.0), offset.dy.clamp(minOffset.dy, 0.0));
  

  void _handleFlingAnimation() 
    setState(() 
      _offset = _flingAnimation.value;
    );
  

  void _handleOnScaleStart(ScaleStartDetails details) 
    setState(() 
      _previousScale = _scale;
      _normalizedOffset = (details.focalPoint - _offset) / _scale;
      // The fling animation stops if an input gesture starts.
      _controller.stop();
    );
  

  void _handleOnScaleUpdate(ScaleUpdateDetails details) 
    setState(() 
      _scale = (_previousScale * details.scale).clamp(1.0, 4.0);
      // Ensure that image location under the focal point stays in the same place despite scaling.
      _offset = _clampOffset(details.focalPoint - _normalizedOffset * _scale);
    );
  

  void _handleOnScaleEnd(ScaleEndDetails details) 
    const double _kMinFlingVelocity = 800.0;
    final double magnitude = details.velocity.pixelsPerSecond.distance;
    print('magnitude: ' + magnitude.toString());
    if (magnitude < _kMinFlingVelocity) return;
    final Offset direction = details.velocity.pixelsPerSecond / magnitude;
    final double distance = (Offset.zero & context.size).shortestSide;
    _flingAnimation = new Tween(
            begin: _offset, end: _clampOffset(_offset + direction * distance))
        .animate(_controller);
    _controller
      ..value = 0.0
      ..fling(velocity: magnitude / 1000.0);
  

  @override
  Widget build(BuildContext context) 
    return RawGestureDetector(
      gestures: 
        AllowMultipleScaleRecognizer:
            GestureRecognizerFactoryWithHandlers(
          () => AllowMultipleScaleRecognizer(), //constructor
          (AllowMultipleScaleRecognizer instance) 
            //initializer
            instance.onStart = (details) => this._handleOnScaleStart(details);
            instance.onEnd = (details) => this._handleOnScaleEnd(details);
            instance.onUpdate = (details) => this._handleOnScaleUpdate(details);
          ,
        ),
        AllowMultipleHorizontalDragRecognizer:
            GestureRecognizerFactoryWithHandlers(
          () => AllowMultipleHorizontalDragRecognizer(),
          (AllowMultipleHorizontalDragRecognizer instance) 
            instance.onStart = (details) => this._handleHorizontalDragAcceptPolicy(instance);
            instance.onUpdate = (details) => this._handleHorizontalDragAcceptPolicy(instance);
          ,
        ),
        AllowMultipleVerticalDragRecognizer:
            GestureRecognizerFactoryWithHandlers(
          () => AllowMultipleVerticalDragRecognizer(),
          (AllowMultipleVerticalDragRecognizer instance) 
            instance.onStart = (details) => this._handleVerticalDragAcceptPolicy(instance);
            instance.onUpdate = (details) => this._handleVerticalDragAcceptPolicy(instance);
          ,
        ),
      ,
      //Creates the nested container within the first.
      behavior: HitTestBehavior.opaque,
      child: new ClipRect(
        child: new Transform(
          transform: new Matrix4.identity()
            ..translate(_offset.dx, _offset.dy)
            ..scale(_scale),
          child: Image(
            image: new AdvancedNetworkImage(widget.url),
            fit: BoxFit.cover,
          ),
        ),
      ),
    );
  

  void _handleHorizontalDragAcceptPolicy(AllowMultipleHorizontalDragRecognizer instance) 
    _scale > 1.0 ? instance.alwaysAccept = true : instance.alwaysAccept = false;
  

 void _handleVerticalDragAcceptPolicy(AllowMultipleVerticalDragRecognizer instance) 
   _scale > 1.0 ? instance.alwaysAccept = true : instance.alwaysAccept = false;
 

AllowMultipleVerticalDragRecognizer:

import 'package:flutter/gestures.dart';

class AllowMultipleVerticalDragRecognizer extends VerticalDragGestureRecognizer 
  bool alwaysAccept;

  @override
  void rejectGesture(int pointer) 
    acceptGesture(pointer);
  

  @override
  void resolve(GestureDisposition disposition) 
    if(alwaysAccept) 
      super.resolve(GestureDisposition.accepted);
     else 
      super.resolve(GestureDisposition.rejected);
    
  

AllowMultipleHorizontalDragRecognizer:

import 'package:flutter/gestures.dart';

class AllowMultipleHorizontalDragRecognizer extends HorizontalDragGestureRecognizer 
  bool alwaysAccept;

  @override
  void rejectGesture(int pointer) 
    acceptGesture(pointer);
  

  @override
  void resolve(GestureDisposition disposition) 
    if(alwaysAccept) 
      super.resolve(GestureDisposition.accepted);
     else 
      super.resolve(GestureDisposition.rejected);
    
  

AllowMultipleScaleRecognizer

import 'package:flutter/gestures.dart';

class AllowMultipleScaleRecognizer extends ScaleGestureRecognizer 
  @override
  void rejectGesture(int pointer) 
    acceptGesture(pointer);
  

Entonces úsalo así:

@override
Widget build(BuildContext context) 
  return new MaterialApp(
    title: 'Zoomable Image In ListView',
    debugShowCheckedModeBanner: false,
    home: new Scaffold(
      body: new Column(
        children: [
          new Expanded(
            child: new ListView.builder(
              scrollDirection: Axis.vertical,
              itemBuilder: (context, index) => ZoomableCachedNetworkImage(_urlList[index]),
            ),
          ),
        ],
      ),
    ),
  );

Espero que esto ayude.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *