我们的应用程序现在可以点击查看图像,但还没有实现查看时放大、缩小与移动图像。要实现这个功能,需要监听用户在图像上的操作,并调用相应的回调处理用户操作。我们先将Transform控件从_ImageZoomableStatebuild方法中拆分出来。在_ImageZoomableState类中添加_drawImage方法。

class _ImageZoomableState extends State<ImageZoomable> {
  //...
  Widget _drawImage() {
    if (_image == null) {
      return null;
    }

    return new Transform(
      transform: new Matrix4.diagonal3Values(1.0, 1.0, 1.0),
      child: new CustomPaint(
        painter: new _ImageZoomablePainter(
          image: _image,
          offset: Offset.zero,
          zoom: 1.0,
        )
      )
    );
  }
  //...
}

修改_ImageZoomableStatebuild方法,使用GestureDetector控件包装_drawImage方法中的Transform控件,我们可以使用GestureDetector控件监听用户的各种操作手势。

class _ImageZoomableState extends State<ImageZoomable> {
  //...
  @override
  Widget build(BuildContext context) {
    return new GestureDetector(
      child: _drawImage(),
    );
  }
  //...
}

图片缩放需要一个比例,在ImageZoomable类定义中声明一个成员变量scale来设置图像的显示比例,同时为变量scale设置默认值,使scale变量为可选参数。

class ImageZoomable extends StatefulWidget {
  ImageZoomable(this.image, {Key key, this.scale = 2.0}) : super(key: key);

  final ImageProvider image;
  final double scale;

  @override
  _ImageZoomableState createState() => new _ImageZoomableState(scale);
}

_ImageZoomableState类定义中接收ImageZoomable类成员变量scale,并声明三个Offset(不可变的二维浮点偏移量)类型的成员变量。_startingFocalPoint变量存储初始焦点值,_previousOffset变量存储历史偏移量,_offset变量使用静态方法Offset.zero为其赋值,Offset类的静态方法zero返回一个零幅度偏移量,即常量Offset(0.0, 0.0)_zoom_previousZoom则是用于存储缩放值与存储历史缩放值。

class _ImageZoomableState extends State<ImageZoomable> {
  _ImageZoomableState(this._scale);

  final double _scale;
  ImageStream _imageStream;
  ui.Image _image;

  Offset _startingFocalPoint;
  Offset _previousOffset;
  Offset _offset = Offset.zero;

  double _zoom = 1.0;
  double _previousZoom;
  //...
}

现在我们可以修改_drawImage方法中Transform控件的硬编码。

class _ImageZoomableState extends State<ImageZoomable> {
//...
  Widget _drawImage() {
    //...
    return new Transform(
      transform: new Matrix4.diagonal3Values(_scale, _scale, _scale),
      child: new CustomPaint(
        painter: new _ImageZoomablePainter(
          image: _image,
          offset: _offset,
          zoom: _zoom / _scale,
        )
      )
    );
  }
  //...
}

现在回到main.dart文件中来,修改ImageZoomable类的调用。

class ChatMessage extends StatelessWidget {
  //...
  @override
  Widget build(BuildContext context) {
    return new SizeTransition(
        //...
                      onTap: (){
                        Navigator.of(context).push( new MaterialPageRoute<Null>(
                          builder: (BuildContext context) {
                            return new ImageZoomable(
                              new NetworkImage(snapshot.value['imageUrl'])
                            );
                          }
                        ));
                      },
        //...
    );
  }
}

这里写图片描述

我们要在用户对图像操作之前获取图像偏移位置相关的参数,也就是之前声明一些_ImageZoomableState类成员变量。我们需要在_ImageZoomableState类中增加_handleScaleStart方法作为手势监听器onScaleStart的回调函数。

手势监听器onScaleStart触发回调的条件:与屏幕接触的指针已经建立起了一个焦点时,此时初始图像显示比例为1.0。参数details的类型为ScaleStartDetails,该类型存储手势监听器onScaleStart的详细信息,其focalPoint属性以全局坐标方式返回与屏幕接触的指针初始焦点。

class _ImageZoomableState extends State<ImageZoomable> {
  //...
  void _handleScaleStart(ScaleStartDetails details) {
    if (_image == null) {
      return;
    }
    _startingFocalPoint = details.focalPoint / _scale;
    _previousOffset = _offset;
    _previousZoom = _zoom;
  }
  //...
}

现在我们在_ImageZoomableState类的GestureDetector控件中添加一个onScaleStart监听器,把上面的_handleScaleStart方法作为回调。

class _ImageZoomableState extends State<ImageZoomable> {
  //...
  @override
  Widget build(BuildContext context) {
    return new GestureDetector(
      child: _drawImage(),
      onScaleStart: _handleScaleStart,
    );
  }
  //...
}

在用户对图像操作时,我们需要跟踪焦点的移动,并重新绘制图像。因此我们需要在_ImageZoomableState类中增加_handleScaleUpdate方法作为手势监听器onScaleUpdate的回调函数,手势监听器onScaleUpdate会监听焦点的变化,并在焦点变化时调用回调处理。

_handleScaleUpdate方法的参数details的类型为ScaleUpdateDetails,该类型存储手势监听器onScaleUpdate的详细信息,其focalPoint属性以全局坐标方式返回与屏幕接触的指针焦点。

void _handleScaleUpdate(Size size, ScaleUpdateDetails details) {
    if (_image == null) {
      return;
    }
    double newZoom = _previousZoom * details.scale;
    bool tooZoomedIn = _image.width * _scale / newZoom <= size.width ||
        _image.height * _scale / newZoom <= size.height || newZoom <= 0.8;
    if (tooZoomedIn) {
      return;
    }

    setState(() {
      _zoom = newZoom;
      final Offset normalizedOffset = (_startingFocalPoint - _previousOffset) / _previousZoom;
      _offset = details.focalPoint / _scale - normalizedOffset * _zoom;
    });
  }

上面定义了两个局部变量,newZoom存储最新缩放值,tooZoomedIn变量判断图像是否过于放大或缩小。如果tooZoomedIn变量为真时,则不会重新绘制图像。局部变量normalizedOffset用于确保焦点下方的图像保持在相同位置的前提下放大图像。

现在我们在_ImageZoomableState类的GestureDetector控件中添加一个onScaleUpdate监听器,把上面的_handleScaleUpdate方法作为回调。

class _ImageZoomableState extends State<ImageZoomable> {
  //...
  @override
  Widget build(BuildContext context) {
    return new GestureDetector(
      child: _drawImage(),
      onScaleStart: _handleScaleStart,
      onScaleUpdate: (d) => _handleScaleUpdate(context.size, d),
    );
  }
  //...
}

这里写图片描述

目前用户在查看图像时,需要点击系统的后退按钮才能返回到聊天屏幕。在大部分应用程序中,在查看图像时点击即可返回聊天屏幕,我们也可以这么做。在ImageZoomable类定义中声明一个GestureTapCallback类型的成员变量onTapGestureTapCallback类型表示点击发生时的回调函数。

class ImageZoomable extends StatefulWidget {
  ImageZoomable(this.image, {Key key, this.scale = 2.0, this.onTap}) : super(key: key);

  final ImageProvider image;
  final double scale;
  final GestureTapCallback onTap;

  @override
  _ImageZoomableState createState() => new _ImageZoomableState(scale);
}

然后我们需要在_ImageZoomableState类的GestureDetector控件中添加一个onTap监听器,并把ImageZoomable类的成员变量onTap作为回调。

class _ImageZoomableState extends State<ImageZoomable> {
  //...
  @override
  Widget build(BuildContext context) {
    return new GestureDetector(
      child: _drawImage(),
      onTap: widget.onTap,
      onScaleStart: _handleScaleStart,
      onScaleUpdate: (d) => _handleScaleUpdate(context.size, d),
    );
  }
  //...
}

最后回到main.dart文件中来,修改ImageZoomable类的调用。

class ChatMessage extends StatelessWidget {
  //...
  @override
  Widget build(BuildContext context) {
    return new SizeTransition(
        //...
                      onTap: (){
                        Navigator.of(context).push( new MaterialPageRoute<Null>(
                          builder: (BuildContext context) {
                            return new ImageZoomable(
                              new NetworkImage(snapshot.value['imageUrl']),
                              onTap: (){
                                Navigator.of(context).pop();
                              },
                            );
                          }
                        ));
                      },
        //...
    );
  }
}

现在图像查看,且查看时可以缩放、移动图像的功能已经完成了!

Logo

致力于链接即构和开发者,提供实时互动和元宇宙领域的前沿洞察、技术分享和丰富的开发者活动,共建实时互动世界。

更多推荐