带有叠加层的 Flutter 可缩放图像

Flutter zoomable image with overlays

我正在尝试制作一个自定义小部件,用户可以缩放和平移图像并点击以在图像上叠加点。我想我已经接近了,但不能完全得到点数以在水龙头下排列。

我从这里复制了相关的缩放平移: https://github.com/flutter/flutter/blob/master/examples/layers/widgets/gestures.dart

我还按照这个 post 将我的 GestureDetector 拉出到它自己的小部件中,以获得正确的 RenderBox 以进行全局到局部偏移转换 flutter : Get Local position of Gesture Detector

import 'package:flutter/material.dart';

class ImageWithOverlays extends StatefulWidget {
  final List<FractionalOffset> fractionalOffsets;
  ImageWithOverlays(this.fractionalOffsets);

  @override
  _ImageWithOverlaysState createState() => _ImageWithOverlaysState();    
}


class _ImageWithOverlaysState extends State<ImageWithOverlays> {
  Offset _startingFocalPoint;
  Offset _previousOffset;
  Offset _offset = Offset.zero;

  double _previousZoom;
  double _zoom = 1.0;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
        onScaleStart: _handleScaleStart,
        onScaleUpdate:_handleScaleUpdate,
        onTapDown: (details){          
          setState(() {                        
            widget.fractionalOffsets.add(_getFractionalOffset(context, details.globalPosition));             
          });
        },
        onDoubleTap: _handleScaleReset,
        child: Transform(
        transform: Matrix4.diagonal3Values(_zoom, _zoom, 1.0) + Matrix4.translationValues(_offset.dx, _offset.dy, 0.0),         
        child: Center(
          child: Container(
              child:Stack(
              children: <Widget>[
                Image.network(
                  'https://picsum.photos/250?image=9',
                ), ]
                ..addAll(_getOverlays(context)),
            )),
          ),
      ));
     
  }

  void _handleScaleStart(ScaleStartDetails details) {
    setState(() {
      _startingFocalPoint = details.focalPoint;
      _previousOffset = _offset;
      _previousZoom = _zoom;
    });
  }


  void _handleScaleUpdate(ScaleUpdateDetails details) {
    setState(() {
      _zoom = _previousZoom * details.scale;

      // Ensure that item under the focal point stays in the same place despite zooming
      final Offset normalizedOffset = (_startingFocalPoint - _previousOffset) / _previousZoom;
      _offset = details.focalPoint - normalizedOffset * _zoom;
    });
  }

  void _handleScaleReset() {
    setState(() {
      _zoom = 1.0;
      _offset = Offset.zero;
      widget.fractionalOffsets.clear();
    });
  }

  List<Widget> _getOverlays(BuildContext context) {
    return widget.fractionalOffsets
        .asMap() 
        .map((i, fo) => MapEntry(
            i,
            Align(
                alignment: fo,
                child: _buildIcon((i + 1).toString(), context)
            )           
        ))
        .values
        .toList();
  }

  Widget _buildIcon(String indexText, BuildContext context) {
    return FlatButton.icon(      
      icon: Icon(Icons.location_on, color: Colors.red),
      label: Text(
        indexText,
        style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold),
      ),      
    );
  }

  Widget _buildCircleIcon(String indexText) {
    return Container(          
          margin: const EdgeInsets.all(8.0),
          padding: const EdgeInsets.all(8.0),
          decoration: BoxDecoration(
            color: Colors.red,
            shape: BoxShape.circle,
          ),
          child:  Text(
                indexText,
                style: TextStyle(
                  fontSize: 16,
                  color: Colors.white,
                  fontWeight: FontWeight.bold
                ),
              ));
  }

  FractionalOffset _getFractionalOffset(BuildContext context, Offset globalPosition) {    
    var renderbox = context.findRenderObject() as RenderBox;
    var localOffset = renderbox.globalToLocal(globalPosition);
    var width = renderbox.size.width;
    var height = renderbox.size.height;
    return FractionalOffset(localOffset.dx/width,localOffset.dy/height); 
  }
}

但我很难正确设置布局。我想我有两个问题:

  1. 图片喜欢跳到左上角。我不确定如何让它保持居中。
  2. 当我放大时,我的 _getFractionalOffset 函数没有返回正确的分数。此屏幕截图中的图标未在我的点击下显示 我的矩阵数学很差,我对 Flutter 如何进行转换的理解也很差,所以我很感激任何见解。

弄清楚了,我的代码有几个问题,这些问题加起来会使调试变得烦人。

首先,我应该乘 scale/translate 矩阵,而不是相加:

vector.Matrix4 get _transformationMatrix {
  var scale = vector.Matrix4.diagonal3Values(_zoom, _zoom, 1.0);
  var translation = vector.Matrix4.translationValues(_offset.dx, _offset.dy, 0.0);
  var transform = translation * scale;
  return transform;
}

其次,我试图忙于从局部偏移到图像帧的转换,而不是让 vector_math 为我做这件事。新的覆盖偏移量计算:

Offset _getOffset(BuildContext context, Offset globalPosition) {    
  var renderbox = context.findRenderObject() as RenderBox;
  var localOffset = renderbox.globalToLocal(globalPosition);    
  var localVector = vector.Vector3(localOffset.dx, localOffset.dy,0);
  var transformed =  Matrix4.inverted(_transformationMatrix).transform3(localVector);
  return Offset(transformed.x, transformed.y);
}

由于_transformationmatrix从图像帧到屏幕帧取一个点,我需要左乘逆来得到从屏幕帧(localOffset)到图像帧(返回值).

最后,我使用的是 Positioned 小部件而不是 Align,因此我可以以像素为单位设置定位:

double _width = 100;
double _height = 50;
List<Widget> _getOverlays(BuildContext context) {
  return widget.points
      .asMap() 
      .map((i, offset) => MapEntry(
          i,
          Positioned(
              left: offset.dx-_width/2,
              width:_width,
              top: offset.dy -_height/2,
              height:_height,
              child: _buildIcon((i + 1).toString(), context)
          )           
      ))
      .values
      .toList();
}

其中 widget.points 是上面 _getOffset 返回的 List<Offset> 偏移量,width/height 是我的图标小部件的宽度和高度.

希望这对某人有所帮助!