颤动中的多向滚动

Posted

技术标签:

【中文标题】颤动中的多向滚动【英文标题】:Multidirectional scroll in flutter 【发布时间】:2019-05-18 20:52:03 【问题描述】:

我来自 web 开发背景,并且习惯于创建一个同时具有 x 和 y 溢出的元素(允许向任何方向滚动)。我正在努力使用 Flutter 实现相同的功能。

查看文档,我找到了 SingleChildScrollView,但它只允许 Axis.horizo​​ntal 或 Axis.vertical,而不是两者。

所以我尝试了以下方法:

return SingleChildScrollView( // horizontal scroll widget
    scrollDirection: Axis.horizontal,
        child: SingleChildScrollView( // vertical scroll widget
            scrollDirection: Axis.vertical,
            child: ...content of the container etc...
        )
    );

这适用于 x 和 y,但它不允许 diagonal 滚动。

有没有办法实现对角滚动,或者有没有更好的材料小部件,我完全错过了?

谢谢

【问题讨论】:

【参考方案1】:

我已经设法找到了一个解决方案,虽然它并不完美:

我创建了一个带有Offset _scrollOffset 的StatefulWidget,它使用带有Transform 类型的子元素的ClipRect。变换矩阵 (Matrix4.identity()..translate(_offset.dx, _offset.dy)) 应用于变换。

GestureDetector 分配了一个 onPanUpdate 回调来更新滚动位置。 _scrollOffset += e.delta。如果滚动位置太低或太高,这可以通过设置滚动位置来限制在小部件的边界内。

Animation 和 AnimationController 用于设置投掷速度。 onPanEnd 提供最后一个平移的速度,因此只需执行一个 Tween 并根据该速度进行抛掷。

动画在TapDown 上停止,因此用户可以停止滚动速度。

这样做的主要问题是它不能完美地模仿 androidios 滚动速度,尽管我正在努力尝试使用 Flutter 提供的 ScrollSimulation 类使其更好地工作。

【讨论】:

InteractiveViewer 是另一种选择,它支持对角拖动(非滚动)手势。 api.flutter.dev/flutter/widgets/InteractiveViewer-class.html【参考方案2】:
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

class FreeScrollView extends StatefulWidget 
  final Widget child;
  final ScrollPhysics physics;

  const FreeScrollView(Key? key, this.physics = const ClampingScrollPhysics(), required this.child) : super(key: key);

  @override
  State<FreeScrollView> createState() => _FreeScrollViewState();


class _FreeScrollViewState extends State<FreeScrollView> 
  final ScrollController _verticalController = ScrollController();
  final ScrollController _horizontalController = ScrollController();
  final Map<Type, GestureRecognizerFactory> _gestureRecognizers = <Type, GestureRecognizerFactory>;

  @override
  void initState() 
    super.initState();
    _gestureRecognizers[PanGestureRecognizer] = GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
        () => PanGestureRecognizer(),
        (instance) => instance
          ..onDown = _handleDragDown
          ..onStart = _handleDragStart
          ..onUpdate = _handleDragUpdate
          ..onEnd = _handleDragEnd
          ..onCancel = _handleDragCancel
          ..minFlingDistance = widget.physics.minFlingDistance
          ..minFlingVelocity = widget.physics.minFlingVelocity
          ..maxFlingVelocity = widget.physics.maxFlingVelocity
          ..velocityTrackerBuilder = ScrollConfiguration.of(context).velocityTrackerBuilder(context)
          ..dragStartBehavior = DragStartBehavior.start);
  

  @override
  Widget build(BuildContext context) => Stack(children: [
        SingleChildScrollView(
            scrollDirection: Axis.horizontal,
            controller: _horizontalController,
            physics: widget.physics,
            child: SingleChildScrollView(
                scrollDirection: Axis.vertical, // ignore: avoid_redundant_argument_values
                controller: _verticalController,
                physics: widget.physics,
                child: widget.child)),
        Positioned.fill(
            child: RawGestureDetector(
          gestures: _gestureRecognizers,
          behavior: HitTestBehavior.opaque,
          excludeFromSemantics: true,
        )),
      ]);

  Drag? _horizontalDrag;
  Drag? _verticalDrag;
  ScrollHoldController? _horizontalHold;
  ScrollHoldController? _verticalHold;

  void _handleDragDown(DragDownDetails details) 
    _horizontalHold = _horizontalController.position.hold(() => _horizontalHold = null);
    _verticalHold = _verticalController.position.hold(() => _verticalHold = null);
  

  void _handleDragStart(DragStartDetails details) 
    _horizontalDrag = _horizontalController.position.drag(details, () => _horizontalDrag = null);
    _verticalDrag = _verticalController.position.drag(details, () => _verticalDrag = null);
  

  void _handleDragUpdate(DragUpdateDetails details) 
    _horizontalDrag?.update(DragUpdateDetails(
        sourceTimeStamp: details.sourceTimeStamp,
        delta: Offset(details.delta.dx, 0),
        primaryDelta: details.delta.dx,
        globalPosition: details.globalPosition));
    _verticalDrag?.update(DragUpdateDetails(
        sourceTimeStamp: details.sourceTimeStamp,
        delta: Offset(0, details.delta.dy),
        primaryDelta: details.delta.dy,
        globalPosition: details.globalPosition));
  

  void _handleDragEnd(DragEndDetails details) 
    _horizontalDrag
        ?.end(DragEndDetails(velocity: details.velocity, primaryVelocity: details.velocity.pixelsPerSecond.dx));
    _verticalDrag
        ?.end(DragEndDetails(velocity: details.velocity, primaryVelocity: details.velocity.pixelsPerSecond.dy));
  

  void _handleDragCancel() 
    _horizontalHold?.cancel();
    _horizontalDrag?.cancel();
    _verticalHold?.cancel();
    _verticalDrag?.cancel();
  

【讨论】:

以上是关于颤动中的多向滚动的主要内容,如果未能解决你的问题,请参考以下文章

自定义滚动谷歌颤动中的水平列表视图

如何通过颤动列表视图中的索引号滚动到索引?

SpriteKit 多向滚动

如何创建多向无限/圆形滚动视图,如 HBO GO iPad 应用程序

一些滚动后颤动ListView KeepAlive

在颤动的webview中禁用水平滚动