如何在Flutter中ListView中缩放图像

10

我正在编写一个Flutter应用程序,想知道如何在ListView中使用/实现可缩放的图像。我已经在我的应用程序中使用了以下插件。

它们都不能在我的项目中工作并抛出不同的异常。这是一个复现错误的示例代码:

flutter_advanced_networkimage:

import 'package:flutter/material.dart';
import 'package:flutter_advanced_networkimage/flutter_advanced_networkimage.dart';
import 'package:flutter_advanced_networkimage/transition_to_image.dart';
import 'package:flutter_advanced_networkimage/zoomable_widget.dart';

void main() {
  runApp(new ZoomableImageInListView());
}

class ZoomableImageInListView extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return new _ZoomableImageInListViewState();
  }
}

final List<String> _urlList = [
  'https://www.w3schools.com/htmL/pic_trulli.jpg',
  'https://www.w3schools.com/htmL/img_girl.jpg',
  'https://www.w3schools.com/htmL/img_chania.jpg',
];

class _ZoomableImageInListViewState extends State<ZoomableImageInListView> {
  @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: _buildVerticalChild,
              ),
            ),
          ],
        ),
      ),
    );
  }

  _buildVerticalChild(BuildContext context, int index) {
    index++;
    if (index > _urlList.length) return null;
    TransitionToImage imageWidget = TransitionToImage(
      AdvancedNetworkImage(
        _urlList[index],
        useDiskCache: true,
      ),
      useReload: true,
      reloadWidget: Icon(Icons.replay),
    );
    return new ZoomableWidget(
      minScale: 1.0,
      maxScale: 5.0,
      child: imageWidget,
      tapCallback: imageWidget.reloadImage,
    );
  }
}

抛出了这个异常:

I/flutter (13594): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter (13594): The following assertion was thrown building ZoomableImageInListView(dirty, state:
I/flutter (13594): _ZoomableImageInListViewState#39144):
I/flutter (13594): type '(BuildContext, int) => dynamic' is not a subtype of type '(BuildContext, int) => Widget'
I/flutter (13594): 
I/flutter (13594): Either the assertion indicates an error in the framework itself, or we should provide substantially
I/flutter (13594): more information in this error message to help you determine and fix the underlying cause.
I/flutter (13594): In either case, please report this assertion by filing a bug on GitHub:
I/flutter (13594):   https://github.com/flutter/flutter/issues/new
.
.
.
I/flutter (13594): ════════════════════════════════════════════════════════════════════════════════════════════════════

可缩放图片:

import 'package:flutter/material.dart';
import 'package:zoomable_image/zoomable_image.dart';

void main() {
  runApp(new ZoomableImageInListView());
}

class ZoomableImageInListView extends StatefulWidget {
  @override
  _ZoomableImageInListViewState createState() =>
      new _ZoomableImageInListViewState();
}

final List<String> _urlList = [
  'https://www.w3schools.com/htmL/pic_trulli.jpg',
  'https://www.w3schools.com/htmL/img_girl.jpg',
  'https://www.w3schools.com/htmL/img_chania.jpg',
];

class _ZoomableImageInListViewState extends State<ZoomableImageInListView> {
  @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) => new ZoomableImage(
                    new NetworkImage(_urlList[index], scale: 1.0)),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

抛出了这个异常:

I/flutter (13594): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter (13594): The following assertion was thrown building ZoomableImage(dirty, state: _ZoomableImageState#d60f4):
I/flutter (13594): A build function returned null.
I/flutter (13594): The offending widget is: ZoomableImage
I/flutter (13594): Build functions must never return null. To return an empty space that causes the building widget to
I/flutter (13594): fill available room, return "new Container()". To return an empty space that takes as little room as
I/flutter (13594): possible, return "new Container(width: 0.0, height: 0.0)".
.
.
.
I/flutter (13594): ════════════════════════════════════════════════════════════════════════════════════════════════════

我在 ListView 之外检查了这两个插件,它们都运行良好。我的实现有问题吗?这些插件支持 ListView 吗?如果答案是肯定的,请告诉我如何使用?

3个回答

16

如果我错了,请纠正我,但从堆栈跟踪中看,我认为您的问题是您正在尝试在大小未知的父级中添加一个未知大小的子级,而Flutter无法计算布局。要解决此问题,您需要创建一个具有固定大小的小部件(可能是从其子级的初始状态计算出来的,例如,在您的情况下是Image),例如ClipRect
虽然这解决了错误,但会留下一些带有故障行为,因为在您的情况下,我们面临的是手势消歧义,意味着您有多个手势检测器同时尝试识别特定的手势。确切地说,一个处理scale,它是用于缩放和平移图像的pan的超集,并且一个处理drag,它用于在您的ListView中滚动。

为了解决此问题,我认为您需要实现一个控制输入手势并手动决定在gesture arena中是否宣布胜利或宣布失败的小部件。
我附上了我在这里那里找到的一些代码行,以实现所需的行为,您将需要flutter_advanced_networkimage库来进行此特定示例,但您可以将AdvancedNetworkImage替换为其他小部件:

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

允许多个水平拖曳识别器:

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

允许多个比例识别器

import 'package:flutter/gestures.dart';

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

然后这样使用它:

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

希望这有所帮助。

更新:

根据评论中的要求,为了支持双击,您应该进行以下更改:

AllowMultipleDoubleTapRecognizer:

import 'package:flutter/gestures.dart';

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

允许多次轻击识别器

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

1
这是整个Flutter社区中最好的答案和解决方案。我找不到任何一个像这样运作良好的缩放效果。但有一个小请求,你能不能添加双击效果。谢谢。 - anass naoushi
1
谢谢@mariaMad。当然,我会尝试一下,但可能需要一些时间,因为这对我来说是一个忙碌的星期。 - Conscript
@mariaMad,希望不算太晚,我已经更新了答案以支持双击操作。 - Conscript

1

我也遇到了这个问题,但是一旦你将ZoomableWidget包装在一个容器内,它就会得到修复。基本上高度没有被限制。我对Flutter还很陌生,所以请再检查一次。

    children: <Widget>[

                   Container(
                    height: 450.0,
                    child: ZoomableWidget(
                      minScale: 0.3,
                      maxScale: 2.0,
                      // default factor is 1.0, use 0.0 to disable boundary
                      panLimit: 0.8,


                        child: TransitionToImage(

                          AdvancedNetworkImage(imageUrl, timeoutDuration: Duration(minutes: 2), useDiskCache: true),
                          // This is the default placeholder widget at loading status,
                          // you can write your own widget with CustomPainter.
                          placeholder: CircularProgressIndicator(),
                          // This is default duration
                          duration: Duration(milliseconds: 300),
                          height: 350.0,
                          width: 400.0,
                        ),

                    ),
                  ),
//                ),
                new Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: new Center(
                    child: new Text(
                      desc,
                      style: new TextStyle(fontSize: 16.0),
                      textAlign: TextAlign.start,
                    ),
                  ),
                ),

              ],

1
在您的第一个示例中,您需要定义函数_buildVerticalChild如下: Widget _buildVerticalChild(BuildContext context, int index) { 不指定Widget将使编译器认为_buildVerticalChild可以返回任何内容。
而且,在这两种情况下,您需要指定itemCount
new ListView.builder(
    itemCount: _urlList.length
)

2
谢谢,我明白了,但是结果显示了这个异常:在performLayout()期间抛出了以下断言:RenderCustomMultiChildLayoutBox对象在布局期间被赋予了无限大小。 - Erfan Jazeb Nikoo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接