【问题标题】:Flutter: programming a painting app with scaling (GestureDetector onScaleUpdate, offset calculation after scaling)Flutter:编程一个带有缩放的绘画应用(GestureDetector onScaleUpdate,缩放后的偏移量计算)
【发布时间】:2020-06-27 00:08:06
【问题描述】:

我目前正在尝试使用 Flutter 编写一些您可能称之为“绘画应用程序”的东西。 目标是使用 CustomePainter 小部件和路径,在图像上绘画。我想出了那部分,如果我不缩放图像,它就可以很好地工作。但我也想让它可扩展,这样我就可以更接近图像来绘制更详细的图像。 我使用 Paths 是因为我希望能够在 Path 内部点击并获得响应(使用“Path.contains”)。

问题是,当我使用缩放时,如果缩放较小,油漆会被传送到我点击的左上角,当缩放更大时,它会被传送到我点击的右下角. 我这个我的问题只是我错误地计算了偏移量。但我想不出更好的公式。 另外,缩放后的移动/变换有问题。 如果 Scaled 对象的移动速度比手指的实际移动速度慢或快。 我设法找到了一个解决方案(通过尝试理解公式)以正确的速度移动对象,但初始位置要么在右下角,要么在左上角,具体取决于比例。

我认为我所需要的只是计算缩放的偏移量的正确公式。

另外,我注意到,手势检测器没有检测到初始区域之外的任何东西,所以如果我缩小图像,点击图像会被检测到,但图像外的手势会被忽略。 有没有办法检测屏幕上的所有手势?

我对 Flutter 和一般编程还很陌生,所以我可能会问一些非常愚蠢的问题,但不幸的是,我找不到这些问题的任何答案。

在下面的代码中,我包含了用于获取偏移量的两个替代代码。两者都有自己的优势,我很想从一个代码替代方案中获得初始定位,并从另一个代码替代方案中获得正确的运动计算......

关于代码:您可以在应用栏中按“手势”图标在移动内容和在内容上绘图之间切换,使用捏合放大和缩小,使用“垃圾箱”图标撤消绘图

//编辑:解释代码,添加网络图片并更改一个图标 //edit1: 更改标题

import 'dart:ui';

import 'package:flutter/material.dart';

class GesturePaint extends StatefulWidget {
  @override
  _GesturePaintState createState() => _GesturePaintState();
}

class _GesturePaintState extends State<GesturePaint> {
  List<Offset> points = List();

  Offset _startLastOffset = Offset.zero;
  Offset _lastOffset = Offset.zero;
  Offset _currentOffset = Offset.zero;
  double _lastScale = 1.0;
  double _currentScale = 1.0;
  bool _drawable = true;

  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('painting app'),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.gesture),
            onPressed: (){
              setState(() {
                _drawable = _drawable ? false : true;
              });
              print(_drawable);
            },
          ),
          IconButton(
            icon: Icon(Icons.delete),
            onPressed: (){
              setState(() {
                points.clear();
              });
            },
          ),
        ],
      ),
      body: _buildBody(context),
    );
  }

  Widget _buildBody(BuildContext context){
    return GestureDetector(
      child: Stack(
        fit: StackFit.expand,
        children: <Widget>[
          _transformMatrix4Image(),
          //_transformScaleAndTranslate(),
          _transformMatrix4(),
        ],
      ),
      onScaleStart: _onScaleStart,
      onScaleUpdate: _onScaleUpdate,

    );
  }

  void _onScaleStart(ScaleStartDetails details) {
    print('ScaleStartDetails: $details');

    if(_drawable){
      setState(() {
        points.add((details.localFocalPoint/_currentScale).translate(-_currentOffset.dx,-_currentOffset.dy));
      });
    } else{
      _lastScale = _currentScale;
      _lastOffset = _currentOffset;
      _startLastOffset = details.focalPoint;
    }

  }



  void _onScaleUpdate(ScaleUpdateDetails details) {
    print('ScaleUpdateDetails: $details - Scale: ${details.scale}');

    if (details.scale != 1){
      double currentScale = _lastScale * details.scale;

      setState(() {
        _currentScale = currentScale;
      });

      print('_scale: $_currentScale - _lastScale: $_lastScale');

    } else if(details.scale == 1.0 ) {
      // Calculate offset depending on current Image scaling.
      if(_drawable){
        //draw instead
        setState(() {
          // this does generally not work, i also tried to use FocalPoint, which doesn't work either,
          // and i tried to use (details.localFocalPoint).tranlate, which also didn't work well
          //the following is the closest i come to the initial movement, but the offset is off depending on the scale
          points.add((details.localFocalPoint/_lastScale).translate(-_currentOffset.dx,-_currentOffset.dy));
        });
      } else{
          Offset offsetAdjustedForScale = (_startLastOffset -_lastOffset )/ _lastScale;
          //initial offset correct, but moves at different speeds than finger movement with following code:
          //Offset currentOffset = details.focalPoint - offsetAdjustedForScale*_lastScale;
          //initial offset wrong, but moves with correct speed
          Offset currentOffset = details.focalPoint/_lastScale - offsetAdjustedForScale*_lastScale;

          setState(() {
            _currentOffset = currentOffset;
          });
      }
    }

  }


  Transform _transformMatrix4(){
    Matrix4 _mat = Matrix4.identity()
      ..scale(_currentScale,_currentScale)
      ..translate(_currentOffset.dx,_currentOffset.dy);
    return Transform(
        transform: _mat,
        alignment: FractionalOffset.center,
        child:  PathExample(pointsList: points),
    );
  }

  Transform _transformMatrix4Image(){
    Matrix4 _mat = Matrix4.identity()
      ..scale(_currentScale,_currentScale)
      ..translate(_currentOffset.dx,_currentOffset.dy);
    return Transform(
      transform: _mat,
      alignment: FractionalOffset.center,
      child: Image(
        image: NetworkImage('https://png2.kisspng.com/sh/dde5d7f3f60acef81f7e019f4f65cafb/L0KzQYm3VcA1N6Vxj5H0aYP2gLBuTfJtfZYygtNELX3yhbB7gflvNZNxjddraYLnPcjwjvcuPZJqe9ZqZkS5QIi6VcAvPmIAUKoEOEm0RYO8VMcyQWk9S6sAMT7zfri=/kisspng-blue-jay-mountain-bluebird-wing-5aecdaf4607350.6198898915254719883951.png'),
        fit: BoxFit.cover,
      ),
    );
  }


}







class PathExample extends StatelessWidget {
  PathExample({this.pointsList});
  List<Offset> pointsList;

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: PathPainter(pointsList: pointsList),
    );
  }
}

class PathPainter extends CustomPainter {
  PathPainter({this.pointsList});
  List<Offset> pointsList;

  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint()
      ..color = Colors.red
      ..style = PaintingStyle.stroke
      ..strokeWidth = 8;

    for (int i = 0; i < pointsList.length - 1; i++) {

      Path path = Path();
      if(pointsList[i] != null) path.moveTo(pointsList[i].dx, pointsList[i].dy);

      for(int j = i; j < pointsList.length - 1;   j++ ){
        if (pointsList[j] != null ) {
          path.lineTo(pointsList[j].dx, pointsList[j].dy);
        } if(pointsList[j] == null){
        }

        i = j;
      }
      canvas.drawPath(path, paint);
    }

  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => true;
}

【问题讨论】:

  • 我需要一点时间来理解代码,但看起来这正是我所需要的。
  • 我注意到您不支持旋转,因此您可以从我的代码中删除“旋转部分”:line 165
  • 好的,因此使用此代码可以完美地进行缩放和翻译,但我无法弄清楚如何从矩阵4 中获取偏移量(抽头的位置)。编辑:是的,我只是使用了代码并将“shouldRotate”值设置为“false”。
  • “但我不知道如何从矩阵中获取偏移量(水龙头的位置)” - 抱歉,我不知道你的意思

标签: flutter transformation gesturedetector


【解决方案1】:

这是一个具有缩放和绘图模式的示例应用程序,您可以通过按下手形按钮在两者之间切换。但是,在 InteractiveViewer 中包装 GestureDetector 时会出现一个问题(请参阅问题:How to zoom a GestureDetector with onPanUpdate call?

import 'package:flutter/material.dart';

void main() => runApp(new MaterialApp(
  home: new IssueExamplePage(),
  debugShowCheckedModeBanner: false,
));

class IssueExamplePage extends StatefulWidget {
  @override
  _IssueExamplePageState createState() => _IssueExamplePageState();
}

class _IssueExamplePageState extends State<IssueExamplePage> {
  bool drawingBlocked = false;
  List<Offset> points = [];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Container(
          color: Colors.grey,
          padding: EdgeInsets.all(5),
          child: Container(
            color: Colors.white,
            child: InteractiveViewer(
              child: AbsorbPointer(
                absorbing: drawingBlocked,
                child: GestureDetector(
                  behavior: HitTestBehavior.translucent,
                  onPanUpdate: (details) {
                    RenderBox renderBox = context.findRenderObject();
                    Offset cursorLocation = renderBox.globalToLocal(details.localPosition);

                    setState(() {
                      points = List.of(points)..add(cursorLocation);
                    });
                  },
                  onPanEnd: (details) {
                    setState(() {
                      points = List.of(points)..add(null);
                    });
                  },
                  child: CustomPaint(
                    painter: MyPainter(points),
                    size: Size.infinite
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            child: Icon(drawingBlocked? CupertinoIcons.hand_raised_fill : CupertinoIcons.hand_raised),
            onPressed: () {
              setState(() {
                drawingBlocked = !drawingBlocked;
              });
            }
          ),
          SizedBox(
            height: 10
          ),
          FloatingActionButton(
            child: Icon(Icons.clear),
            onPressed: () {
              setState(() {
                points = [];
              });
            }
          ), 
          SizedBox(
            height: 20
          ),
        ],
      ),
    );
  }
}

class MyPainter extends CustomPainter {
  MyPainter(this.points);
  List<Offset> points;
  Paint paintBrush = Paint()
    ..color = Colors.blue
    ..strokeWidth = 5
    ..strokeJoin = StrokeJoin.round
    ..strokeCap = StrokeCap.round;

  @override
  void paint(Canvas canvas, Size size) {
    for (int i = 0; i < points.length - 1; i++) {
      if (points[i] != null && points[i + 1] != null) {
        canvas.drawLine(points[i], points[i + 1], paintBrush);
      }
    }
  }
  
  @override
  bool shouldRepaint(MyPainter oldDelegate) {
    return points != oldDelegate.points;
  }
}

【讨论】:

    猜你喜欢
    • 2011-04-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多