Flutter:如何为任意容器实现旋转和平移/移动手势?

11

我已经为容器实现了缩放手势,还添加了onHorizontalDragUpdate和onVerticalDragUpdate。但是当我尝试同时添加两者时,会出现异常,提示无法与缩放手势一起实现。即使是Pan手势也会抛出相同的异常。以下是我的代码:

我已经为容器实现了缩放手势,还添加了onHorizontalDragUpdate和onVerticalDragUpdate。但是当我尝试同时添加两者时,会出现异常,提示无法与缩放手势一起实现。即使是Pan手势也会抛出相同的异常。以下是我的代码:

import 'package:flutter/material.dart';
import 'package:vector_math/vector_math_64.dart' hide Colors;
 import 'dart: math' as math;

class HomeScreen extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return HomeState();
  }
}

class HomeState extends State<HomeScreen> {

  double _scale = 1.0;
  double _previousScale;
  var yOffset = 400.0;
  var xOffset = 50.0;
  var rotation = 0.0;
  var lastRotation = 0.0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.amber,
      body: Stack(
        children: <Widget>[
          stackContainer(),
        ],
      ),
    );
  }

  Widget stackContainer() {

        return Stack(
          children: <Widget>[
            Positioned.fromRect(
              rect: Rect.fromPoints( Offset(xOffset, yOffset),
                  Offset(xOffset+250.0, yOffset+100.0)),
              child: GestureDetector(
                onScaleStart: (scaleDetails) {
                  _previousScale = _scale;
                  print(' scaleStarts = ${scaleDetails.focalPoint}');
                },
                onScaleUpdate: (scaleUpdates){
                  //ScaleUpdateDetails
                  rotation += lastRotation - scaleUpdates.rotation;
                  lastRotation = scaleUpdates.rotation;
                  print("lastRotation = $lastRotation");
                  print(' scaleUpdates = ${scaleUpdates.scale} rotation = ${scaleUpdates.rotation}');
                  setState(() => _scale = _previousScale * scaleUpdates.scale);
                },
                onScaleEnd: (scaleEndDetails) {
                  _previousScale = null;
                  print(' scaleEnds = ${scaleEndDetails.velocity}');
                },
                child:
                Transform(
                  transform: Matrix4.diagonal3( Vector3(_scale, _scale, _scale))..rotateZ(rotation * math.pi/180.0),
              alignment: FractionalOffset.center,
              child: Container(
                color: Colors.red,
              ),
            )
            ,
          ),
        ),
      ],
    );
  }
}

我想移动红色子视图并随着缩放旋转。


你能添加异常吗? - Rémi Rousselet
当我在上面的代码中添加onPanStart时,我会收到一个异常:“同时拥有平移手势识别器和缩放手势识别器是多余的;缩放是平移的超集。只需使用缩放手势识别器即可。” 当我尝试在上面添加onVerticalDrag和onHorizontalDrag与缩放手势时,我会得到“同时拥有垂直拖动手势识别器、水平拖动手势识别器和缩放手势识别器将导致缩放手势识别器被忽略,因为其他两个手势识别器将捕获所有拖动。” - Ankur Prakash
4个回答

17

在与scale相关的事件中,您可以使用focalPoint来计算平移,除了缩放(变焦)之外。在缩放时也可以支持平移。

Demo:

pan and zoom demo

这是上面演示中使用的代码:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ZoomAndPanDemo(),
    );
  }
}

class ZoomAndPanDemo extends StatefulWidget {
  @override
  _ZoomAndPanDemoState createState() => _ZoomAndPanDemoState();
}

class _ZoomAndPanDemoState extends State<ZoomAndPanDemo> {
  Offset _offset = Offset.zero;
  Offset _initialFocalPoint = Offset.zero;
  Offset _sessionOffset = Offset.zero;

  double _scale = 1.0;
  double _initialScale = 1.0;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onScaleStart: (details) {
        _initialFocalPoint = details.focalPoint;
        _initialScale = _scale;
      },
      onScaleUpdate: (details) {
        setState(() {
          _sessionOffset = details.focalPoint - _initialFocalPoint;
          _scale = _initialScale * details.scale;
        });
      },
      onScaleEnd: (details) {
        setState(() {
          _offset += _sessionOffset;
          _sessionOffset = Offset.zero;
        });
      },
      child: Transform.translate(
        offset: _offset + _sessionOffset,
        child: Transform.scale(
          scale: _scale,
          child: FlutterLogo(),
        ),
      ),
    );
  }
}

顺便提一下:尽管像onHorizontalDragUpdate这样的事件在与缩放相关的事件一起使用时不会导致运行时异常,但它们仍然会发生冲突,并会导致劣质的用户体验。

还值得注意的是,InteractiveViewer是一个内置的Flutter小部件,可以处理大多数您的需求,因此您可能根本不需要使用GestureDetectorTransform


6
我们可以使用ScaleUpdateDetails对象的focalPoint字段,该对象作为onScaleUpdate函数的参数传递。
与上面示例相关的解决方案: 我们需要更新onScaleUpdate方法。
onScaleUpdate: (scaleUpdates) {

      lastRotation += scaleUpdates.rotation;
      var offset = scaleUpdates.focalPoint;
      xOffset = offset.dx;
      yOffset = offset.dy;

      setState(() => _scale = _previousScale * scaleUpdates.scale);
    }

修改上述代码中定位小部件的'rect'字段。

rect: Rect.fromPoints(Offset(xOffset - 125.0, yOffset - 50.0),
              Offset(xOffset + 250.0, yOffset + 100.0))

3

GestureRecognizer默认不支持同时识别平移/拖动和缩放手势。我认为这是一个错误,应该修复。要实现这样的行为 - 您需要基于ImmediateMultiDragGestureRecognizer手势构建自己的识别器RawGestureDetector

我已经在这里实现了PanAndScalingGestureRecognizer类:https://gist.github.com/comm1x/8ffffd08417053043e079878b4bd8d03

因此,您可以查看完整示例或只需复制并使用。


1

我使用指针监听器来实现我的自定义手势检测器。

对于任何遇到同样问题的人,只需查看gesture_x_detector包。

支持(轻触、双击、缩放(开始、更新、结束)、移动(开始、更新、结束)和长按)。所有类型都可以同时使用。

示例:

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

void main() {
  runApp(
    MaterialApp(
      home: XGestureExample(),
    ),
  );
}

class XGestureExample extends StatefulWidget {
  @override
  _XGestureExampleState createState() => _XGestureExampleState();
}

class _XGestureExampleState extends State<XGestureExample> {
  String lastEventName = 'Tap on screen';

  @override
  Widget build(BuildContext context) {
    return XGestureDetector(
      child: Material(
        child: Center(
          child: Text(
            lastEventName,
            style: TextStyle(fontSize: 30),
          ),
        ),
      ),
      doubleTapTimeConsider: 300,
      longPressTimeConsider: 350,
      onTap: onTap,
      onDoubleTap: onDoubleTap,
      onLongPress: onLongPress,
      onMoveStart: onMoveStart,
      onMoveEnd: onMoveEnd,
      onMoveUpdate: onMoveUpdate,
      onScaleStart: onScaleStart,
      onScaleUpdate: onScaleUpdate,
      onScaleEnd: onScaleEnd,
      bypassTapEventOnDoubleTap: false,
    );
  }

  void onScaleEnd() {
    setLastEventName('onScaleEnd');
    print('onScaleEnd');
  }

  void onScaleUpdate(changedFocusPoint, scale) {
    setLastEventName('onScaleUpdate');
    print(
        'onScaleUpdate - changedFocusPoint:  $changedFocusPoint ; scale: $scale');
  }

  void onScaleStart(initialFocusPoint) {
    setLastEventName('onScaleStart');
    print('onScaleStart - initialFocusPoint: ' + initialFocusPoint.toString());
  }

  void onMoveUpdate(localPos, position, localDelta, delta) {
    setLastEventName('onMoveUpdate');
    print('onMoveUpdate - pos: ' + localPos.toString());
  }

  void onMoveEnd(pointer, localPos, position) {
    setLastEventName('onMoveEnd');
    print('onMoveEnd - pos: ' + localPos.toString());
  }

  void onMoveStart(pointer, localPos, position) {
    setLastEventName('onMoveStart');
    print('onMoveStart - pos: ' + localPos.toString());
  }

  void onLongPress(pointer, localPos, position) {
    setLastEventName('onLongPress');
    print('onLongPress - pos: ' + localPos.toString());
  }

  void onDoubleTap(localPos, position) {
    setLastEventName('onDoubleTap');
    print('onDoubleTap - pos: ' + localPos.toString());
  }

  void onTap(pointer, localPos, position) {
    setLastEventName('onTap');
    print('onTap - pos: ' + localPos.toString());
  }

  void setLastEventName(String eventName) {
    setState(() {
      lastEventName = eventName;
    });
  }
}

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