How to zoom image inside ListView in flutter

In your first example you need to define the function _buildVerticalChild as such :

Widget _buildVerticalChild(BuildContext context, int index) {

Not specifying Widget will make the compiler think _buildVerticalChild can return anything.

And in both situations, you need to specify an itemCount

new ListView.builder(
    itemCount: _urlList.length
)

Correct me if I am wrong but from the stacktrace I think your problem is that you are trying to add a child with unknown size within a parent also with unknown size and flutter fails to compute the layout. To solve this problem you need to create a widget with a fixed size (probably calculated from the initial state of its child for example, Image in your case) like ClipRect.
Although this solves the error; It leaves you with a glitchy behavior because in your case we are facing with a Gesture disambiguation as mentioned here, meaning that you have multiple gesture detectors trying to recognize specific gestures at the same time. To be exact, one that handles scale which is a super set of pan that is used for zooming and panning your image and one that handles drag which is used for scrolling in your ListView. To overcome this issue, I think you need to implement a widget that controls the input gestures and manually decides whether to declare victory or declare defeat in gesture arena.
I have attached a few lines of code I found here and there together in order to implement the desired behavior, you will need flutter_advanced_networkimage library for this specific example but you can replace AdvancedNetworkImage with other widgets:

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<ZoomablePhotoViewer>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;
  Animation<Offset> _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<Offset>(
            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>(
          () => 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(),
          (AllowMultipleHorizontalDragRecognizer instance) {
            instance.onStart = (details) => this._handleHorizontalDragAcceptPolicy(instance);
            instance.onUpdate = (details) => this._handleHorizontalDragAcceptPolicy(instance);
          },
        ),
        AllowMultipleVerticalDragRecognizer:
            GestureRecognizerFactoryWithHandlers<AllowMultipleVerticalDragRecognizer>(
          () => 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);
  }
}

Then use it like this:

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

I hope this helps.

Update:

As requested in the comments, for supporting double-tap you should make the following changes:

AllowMultipleDoubleTapRecognizer:

import 'package:flutter/gestures.dart';

class AllowMultipleDoubleTapRecognizer extends DoubleTapGestureRecognizer {
  @override
  void rejectGesture(int pointer) {
    acceptGesture(pointer);
  }
}

AllowMultipleTapRecognizer

import 'package:flutter/gestures.dart';

class AllowMultipleTapRecognizer extends TapGestureRecognizer {
  @override
  void rejectGesture(int pointer) {
    acceptGesture(pointer);
  }
}

ZoomableCachedNetworkImage

class ZoomableCachedNetworkImage extends StatelessWidget {
  final String url;
  final bool closeOnZoomOut;
  final Offset focalPoint;
  final double initialScale;
  final bool animateToInitScale;

  ZoomableCachedNetworkImage({
    this.url,
    this.closeOnZoomOut = false,
    this.focalPoint,
    this.initialScale,
    this.animateToInitScale,
  });

  Widget loadImage() {
    return ZoomablePhotoViewer(
      url: url,
      closeOnZoomOut: closeOnZoomOut,
      focalPoint: focalPoint,
      initialScale: initialScale,
      animateToInitScale: animateToInitScale,
    );
  }
}

class ZoomablePhotoViewer extends StatefulWidget {
  const ZoomablePhotoViewer({
    Key key,
    this.url,
    this.closeOnZoomOut,
    this.focalPoint,
    this.initialScale,
    this.animateToInitScale,
  }) : super(key: key);

  final String url;
  final bool closeOnZoomOut;
  final Offset focalPoint;
  final double initialScale;
  final bool animateToInitScale;

  @override
  _ZoomablePhotoViewerState createState() => _ZoomablePhotoViewerState(url,
      closeOnZoomOut: closeOnZoomOut,
      focalPoint: focalPoint,
      animateToInitScale: animateToInitScale,
      initialScale: initialScale);
}

class _ZoomablePhotoViewerState extends State<ZoomablePhotoViewer>
    with TickerProviderStateMixin {
  static const double _minScale = 0.99;
  static const double _maxScale = 4.0;
  AnimationController _flingAnimationController;
  Animation<Offset> _flingAnimation;
  AnimationController _zoomAnimationController;
  Animation<double> _zoomAnimation;
  Offset _offset;
  double _scale;
  Offset _normalizedOffset;
  double _previousScale;
  AllowMultipleHorizontalDragRecognizer _allowMultipleHorizontalDragRecognizer;
  AllowMultipleVerticalDragRecognizer _allowMultipleVerticalDragRecognizer;
  Offset _tapDownGlobalPosition;
  String _url;
  bool _closeOnZoomOut;
  Offset _focalPoint;
  bool _animateToInitScale;
  double _initialScale;

  _ZoomablePhotoViewerState(
    String url, {
    bool closeOnZoomOut = false,
    Offset focalPoint = Offset.zero,
    double initialScale = 1.0,
    bool animateToInitScale = false,
  }) {
    this._url = url;
    this._closeOnZoomOut = closeOnZoomOut;
    this._offset = Offset.zero;
    this._scale = 1.0;
    this._initialScale = initialScale;
    this._focalPoint = focalPoint;
    this._animateToInitScale = animateToInitScale;
  }

  @override
  void initState() {
    super.initState();
    if (_animateToInitScale) {
      WidgetsBinding.instance.addPostFrameCallback(
          (_) => _zoom(_focalPoint, _initialScale, context));
    }
    _flingAnimationController = AnimationController(vsync: this)
      ..addListener(_handleFlingAnimation);
    _zoomAnimationController = AnimationController(
        duration: const Duration(milliseconds: 200), vsync: this);
  }

  @override
  void dispose() {
    _flingAnimationController.dispose();
    _zoomAnimationController.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 = Offset(size.width, size.height) * (1.0 - _scale);
    return 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.
      _flingAnimationController.stop();
    });
  }

  void _handleOnScaleUpdate(ScaleUpdateDetails details) {
    if (_scale < 1.0 && _closeOnZoomOut) {
      _zoom(Offset.zero, 1.0, context);
      Navigator.pop(context);
      return;
    }
    setState(() {
      _scale = (_previousScale * details.scale).clamp(_minScale, _maxScale);
      // 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 = 2000.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 = Tween<Offset>(
            begin: _offset, end: _clampOffset(_offset + direction * distance))
        .animate(_flingAnimationController);
    _flingAnimationController
      ..value = 0.0
      ..fling(velocity: magnitude / 2000.0);
  }

  @override
  Widget build(BuildContext context) {
    return RawGestureDetector(
      gestures: {
        AllowMultipleScaleRecognizer:
            GestureRecognizerFactoryWithHandlers<AllowMultipleScaleRecognizer>(
          () => 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(),
          (AllowMultipleHorizontalDragRecognizer instance) {
            _allowMultipleHorizontalDragRecognizer = instance;
            instance.onStart =
                (details) => this._handleHorizontalDragAcceptPolicy(instance);
            instance.onUpdate =
                (details) => this._handleHorizontalDragAcceptPolicy(instance);
          },
        ),
        AllowMultipleVerticalDragRecognizer:
            GestureRecognizerFactoryWithHandlers<
                AllowMultipleVerticalDragRecognizer>(
          () => AllowMultipleVerticalDragRecognizer(),
          (AllowMultipleVerticalDragRecognizer instance) {
            _allowMultipleVerticalDragRecognizer = instance;
            instance.onStart =
                (details) => this._handleVerticalDragAcceptPolicy(instance);
            instance.onUpdate =
                (details) => this._handleVerticalDragAcceptPolicy(instance);
          },
        ),
        AllowMultipleDoubleTapRecognizer: GestureRecognizerFactoryWithHandlers<
            AllowMultipleDoubleTapRecognizer>(
          () => AllowMultipleDoubleTapRecognizer(),
          (AllowMultipleDoubleTapRecognizer instance) {
            instance.onDoubleTap = () => this._handleDoubleTap();
          },
        ),
        AllowMultipleTapRecognizer:
            GestureRecognizerFactoryWithHandlers<AllowMultipleTapRecognizer>(
          () => AllowMultipleTapRecognizer(),
          (AllowMultipleTapRecognizer instance) {
            instance.onTapDown =
                (details) => this._handleTapDown(details.globalPosition);
          },
        ),
      },
      //Creates the nested container within the first.
      behavior: HitTestBehavior.opaque,
      child: Transform(
        transform: Matrix4.identity()
          ..translate(_offset.dx, _offset.dy)
          ..scale(_scale),
        child: _buildTransitionToImage(),
      ),
    );
  }

  Widget _buildTransitionToImage() {
    return CachedNetworkImage(
      imageUrl: this._url,
      fit: BoxFit.contain,
      fadeOutDuration: Duration(milliseconds: 0),
      fadeInDuration: Duration(milliseconds: 0),
    );
  }

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

  void _handleDoubleTap() {
    setState(() {
      if (_scale >= 1.0 && _scale <= 1.2) {
        _previousScale = _scale;
        _normalizedOffset = (_tapDownGlobalPosition - _offset) / _scale;
        _scale = 2.75;
        _offset = _clampOffset(
            context.size.center(Offset.zero) - _normalizedOffset * _scale);
        _allowMultipleVerticalDragRecognizer.alwaysAccept = true;
        _allowMultipleHorizontalDragRecognizer.alwaysAccept = true;
      } else {
        if (_closeOnZoomOut) {
          _zoom(Offset.zero, 1.0, context);
          _zoomAnimation.addListener(() {
            if (_zoomAnimation.isCompleted) {
              Navigator.pop(context);
            }
          });
          return;
        }
        _scale = 1.0;
        _offset = _clampOffset(Offset.zero - _normalizedOffset * _scale);
        _allowMultipleVerticalDragRecognizer.alwaysAccept = false;
        _allowMultipleHorizontalDragRecognizer.alwaysAccept = false;
      }
    });
  }

  _handleTapDown(Offset globalPosition) {
    final RenderBox referenceBox = context.findRenderObject();
    _tapDownGlobalPosition = referenceBox.globalToLocal(globalPosition);
  }

  _zoom(Offset focalPoint, double scale, BuildContext context) {
    final RenderBox referenceBox = context.findRenderObject();
    focalPoint = referenceBox.globalToLocal(focalPoint);
    _previousScale = _scale;
    _normalizedOffset = (focalPoint - _offset) / _scale;
    _allowMultipleVerticalDragRecognizer.alwaysAccept = true;
    _allowMultipleHorizontalDragRecognizer.alwaysAccept = true;
    _zoomAnimation = Tween<double>(begin: _scale, end: scale)
        .animate(_zoomAnimationController);
    _zoomAnimation.addListener(() {
      setState(() {
        _scale = _zoomAnimation.value;
        _offset = scale < _scale
            ? _clampOffset(Offset.zero - _normalizedOffset * _scale)
            : _clampOffset(
                context.size.center(Offset.zero) - _normalizedOffset * _scale);
      });
    });
    _zoomAnimationController.forward(from: 0.0);
  }
}

abstract class ScaleDownHandler {
  void handleScaleDown();
}