Flutter:GestureHandler 和 Transform.scale 导致点击框较小

Dab*_*bel 1 transform gesturedetector flutter

背景

尝试设置一个简单的图像编辑器,允许用户通过手势缩放和移动图像。

GestureDetector通过,Transform.scale和可以很好地进行缩放和移动Transform.rotate

问题

缩放后,用户仍然可以缩放已经缩放的图像。

但是:这GestureDetector不会改变执行命中测试的区域。

问题:用户只能使用原始的 hitbox 来操作图像。无法通过在扩展的外部形状上使用两指捏合手势来缩放图像。

图片

第一张图片演示了基本设置。

第二张图片演示了使用手势的结果。它显示了小的、未改变的内部碰撞箱。以及由此产生的缩放和旋转形状。

填充的框是碰撞框。外部矩形显示缩放后的图像。

在此输入图像描述 在此输入图像描述在此输入图像描述

期望的行为

在缩放后的外部形状上使用两指捏合手势应该允许进一步操作对象。

相反,内部打击盒可以单独使用。但用户期望使用缩放后的外部形状来进一步缩放和移动对象。

代码

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: GestureTest(),
    );
  }
}

class DrawContainer {
  Color color;
  Offset offset;
  double scale;
  double angle;
  late double baseScaleFactor;

  DrawContainer(this.color, this.offset, this.scale, this.angle) {
    baseScaleFactor = scale;
  }

  onScaleStart() => baseScaleFactor = scale;

  onScaleUpdate(double scaleNew) =>
      scale = (baseScaleFactor * scaleNew).clamp(0.5, 5);
}

class GestureTest extends StatefulWidget {
  const GestureTest({Key? key}) : super(key: key);

  @override
  // ignore: library_private_types_in_public_api
  _GestureTestState createState() => _GestureTestState();
}

class _GestureTestState extends State<GestureTest> {
  bool doRedraw = false;

  final List<DrawContainer> containers = [
    DrawContainer(Colors.red, const Offset(50, 50), 1.0, 0.0),
    DrawContainer(Colors.yellow, const Offset(100, 100), 1.0, 0.0),
    DrawContainer(Colors.green, const Offset(150, 150), 1.0, 0.0),
  ];

  void onGestureStart(DrawContainer e) => e.onScaleStart();

  onGestureUpdate(DrawContainer e, ScaleUpdateDetails d) {
    e.offset = e.offset + d.focalPointDelta;
    if (d.rotation != 0.0) e.angle = d.rotation;
    if (d.scale != 1.0) e.onScaleUpdate(d.scale);
    setState(() => doRedraw = !doRedraw); // redraw
  }

  void rebuildAllChildren(BuildContext context) {
    void rebuild(Element el) {
      el.markNeedsBuild();
      el.visitChildren(rebuild);
    }

    (context as Element).visitChildren(rebuild);
  }

  @override
  Widget build(BuildContext context) {
    rebuildAllChildren(context);
    return SafeArea(
        child: Scaffold(
      body: Stack(
        fit: StackFit.expand,
        children: [
          doRedraw ? const SizedBox.shrink() : const SizedBox.shrink(),
          ...containers.map((e) {
            return Positioned(
                top: e.offset.dy,
                left: e.offset.dx,
                child: Container(
                  color: e.color,
                  child: GestureDetector(
                      onScaleStart: (details) {
                        if (details.pointerCount == 2) {
                          onGestureStart(e);
                        }
                      },
                      onScaleUpdate: (details) => onGestureUpdate(e, details),
                      child: Transform.rotate(
                          angle: e.angle,
                          child: Transform.scale(
                            scale: e.scale,
                            child: Container(
                                decoration: BoxDecoration(
                                    border: Border.all(color: e.color)),
                                width: 100,
                                height: 100),
                            // Text(e.label, style: const TextStyle(fontSize: 40)),
                          ))),
                  // ),
                ));
          }).toList(),
        ],
      ),
    ));
  }
}

Run Code Online (Sandbox Code Playgroud)

Dab*_*bel 7

下面是一个通用的工作示例,其中碰撞框的大小与缩放后的小部件的大小相匹配。

基本结构如下:

SizedBox (infinite size) # may not be needed
- Stack
  - GestureDetector for each Widget
    - Stack 
      - Positioned, Transform 
        - Widget
Run Code Online (Sandbox Code Playgroud)
SizedBox (infinite size) # may not be needed
- Stack
  - GestureDetector for each Widget
    - Stack 
      - Positioned, Transform 
        - Widget
Run Code Online (Sandbox Code Playgroud)

这个测试用例很有帮助:/sf/answers/4785231321/