为什么我的Flutter自定义ScrollPhysics破坏了Scrollable中的GestureDetectors?

时间:2018-11-06 17:03:10

标签: flutter scrollview gesturedetector

为什么我的Flutter自定义ScrollPhysics破坏了Scrollable中的GestureDetectors?我正在使用使用我编写的自定义ScrollPhysics的SingleChildScrollView,由于某种原因,除非ScrollView处于过度滚动状态,否则我在ScrollView中拥有的GestureDetectors根本不会做出触摸反应。

基本上,除非我最近才滚动到滚动范围并且ScrollView在滚动范围处停止,否则我将无法检测到自定义物理ScrollView内部的任何手势。当然,检测普通ScrollView内的手势也可以。

Here's a video of my predicament; the blue ScrollView on the left uses the default ScrollPhysics and the amber one on the right uses my custom ScrollPhysics, with five tappable boxes in each ScrollView:

Here's the GitHub:

这是代码本身:

import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
import 'package:flutter/widgets.dart';
import 'dart:math' as math;

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

class FlutterSingleChildScrollViewAbsorbsGesturesExample extends StatefulWidget {
  @override
  State<FlutterSingleChildScrollViewAbsorbsGesturesExample> createState() =>
      FlutterSingleChildScrollViewAbsorbsGesturesExampleState();
}

class FlutterSingleChildScrollViewAbsorbsGesturesExampleState
    extends State<FlutterSingleChildScrollViewAbsorbsGesturesExample> {
  Color colorOfRegularPhysicsBoxOne = Colors.black;
  Color colorOfRegularPhysicsBoxTwo = Colors.black;
  Color colorOfRegularPhysicsBoxThree = Colors.black;
  Color colorOfRegularPhysicsBoxFour = Colors.black;
  Color colorOfRegularPhysicsBoxFive = Colors.black;

  Color colorOfCustomPhysicsBoxOne = Colors.black;
  Color colorOfCustomPhysicsBoxTwo = Colors.black;
  Color colorOfCustomPhysicsBoxThree = Colors.black;
  Color colorOfCustomPhysicsBoxFour = Colors.black;
  Color colorOfCustomPhysicsBoxFive = Colors.black;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'An example of how the SingleChildScrollView with custom ScrollPhysics looks like it is eating gestures '
          'meant for its descendants',
      home: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: <Widget>[
          SingleChildScrollView(
            child: Container(
              height: 1400.0,
              width: 200.0,
              color: Colors.lightBlue,
              child: Center(
                child: Column(
                  children: <Widget>[
                    GestureDetector(
                      onTap: () => setState(() {
                            colorOfRegularPhysicsBoxOne = Colors.white;
                          }),
                      child: Container(
                        color: colorOfRegularPhysicsBoxOne,
                        height: 100.0,
                        width: 100.0,
                      ),
                    ),
                    GestureDetector(
                      onTap: () => setState(() {
                            colorOfRegularPhysicsBoxTwo = Colors.white;
                          }),
                      child: Container(
                        color: colorOfRegularPhysicsBoxTwo,
                        height: 100.0,
                        width: 100.0,
                      ),
                    ),
                    GestureDetector(
                      onTap: () => setState(() {
                            colorOfRegularPhysicsBoxThree = Colors.white;
                          }),
                      child: Container(
                        color: colorOfRegularPhysicsBoxThree,
                        height: 100.0,
                        width: 100.0,
                      ),
                    ),
                    GestureDetector(
                      onTap: () => setState(() {
                            colorOfRegularPhysicsBoxFour = Colors.white;
                          }),
                      child: Container(
                        color: colorOfRegularPhysicsBoxFour,
                        height: 100.0,
                        width: 100.0,
                      ),
                    ),
                    GestureDetector(
                      onTap: () => setState(() {
                            colorOfRegularPhysicsBoxFive = Colors.white;
                          }),
                      child: Container(
                        color: colorOfRegularPhysicsBoxFive,
                        height: 100.0,
                        width: 100.0,
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ),
          SingleChildScrollView(
            physics: CustomSnappingScrollPhysicsForTheControlPanelHousing(stoppingPoints: [
              0.0,
              100.0,
              200.0,
              300.0,
              400.0,
            ]),
            child: Container(
              height: 1400.0,
              width: 200.0,
              color: Colors.amberAccent,
              child: Center(
                child: Column(
                  children: <Widget>[
                    GestureDetector(
                      onTap: () => setState(() {
                            colorOfCustomPhysicsBoxOne = Colors.white;
                          }),
                      child: Container(
                        color: colorOfCustomPhysicsBoxOne,
                        height: 100.0,
                        width: 100.0,
                      ),
                    ),
                    GestureDetector(
                      onTap: () => setState(() {
                            colorOfCustomPhysicsBoxTwo = Colors.white;
                          }),
                      child: Container(
                        color: colorOfCustomPhysicsBoxTwo,
                        height: 100.0,
                        width: 100.0,
                      ),
                    ),
                    GestureDetector(
                      onTap: () => setState(() {
                            colorOfCustomPhysicsBoxThree = Colors.white;
                          }),
                      child: Container(
                        color: colorOfCustomPhysicsBoxThree,
                        height: 100.0,
                        width: 100.0,
                      ),
                    ),
                    GestureDetector(
                      onTap: () => setState(() {
                            colorOfCustomPhysicsBoxFour = Colors.white;
                          }),
                      child: Container(
                        color: colorOfCustomPhysicsBoxFour,
                        height: 100.0,
                        width: 100.0,
                      ),
                    ),
                    GestureDetector(
                      onTap: () => setState(() {
                            colorOfCustomPhysicsBoxFive = Colors.white;
                          }),
                      child: Container(
                        color: colorOfCustomPhysicsBoxFive,
                        height: 100.0,
                        width: 100.0,
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class CustomSnappingScrollPhysicsForTheControlPanelHousing extends ScrollPhysics {
  List<double> stoppingPoints;
  SpringDescription springDescription = SpringDescription(mass: 100.0, damping: .2, stiffness: 50.0);

  @override
  CustomSnappingScrollPhysicsForTheControlPanelHousing({@required this.stoppingPoints, ScrollPhysics parent})
      : super(parent: parent) {
    stoppingPoints.sort();
  }

  @override
  CustomSnappingScrollPhysicsForTheControlPanelHousing applyTo(ScrollPhysics ancestor) {
    return new CustomSnappingScrollPhysicsForTheControlPanelHousing(
        stoppingPoints: stoppingPoints, parent: buildParent(ancestor));
  }

  @override
  Simulation createBallisticSimulation(ScrollMetrics scrollMetrics, double velocity) {
    double targetStoppingPoint = _getTargetStoppingPointPixels(scrollMetrics.pixels, velocity, 0.0003, stoppingPoints);

    return ScrollSpringSimulation(springDescription, scrollMetrics.pixels, targetStoppingPoint, velocity,
        tolerance: Tolerance(velocity: .00003, distance: .003));
  }

  double _getTargetStoppingPointPixels(
      double initialPosition, double velocity, double drag, List<double> stoppingPoints) {
    double endPointBeforeSnappingIsCalculated =
        initialPosition + (-velocity / math.log(drag)).clamp(stoppingPoints[0], stoppingPoints.last);
    if (stoppingPoints.contains(endPointBeforeSnappingIsCalculated)) {
      return endPointBeforeSnappingIsCalculated;
    }
    if (endPointBeforeSnappingIsCalculated > stoppingPoints.last) {
      return stoppingPoints.last;
    }
    for (int i = 0; i < stoppingPoints.length; i++) {
      if (endPointBeforeSnappingIsCalculated < stoppingPoints[i] &&
          endPointBeforeSnappingIsCalculated < stoppingPoints[i] - (stoppingPoints[i] - stoppingPoints[i - 1]) / 2) {
        double stoppingPoint = stoppingPoints[i - 1];
        debugPrint(stoppingPoint.toString());
        return stoppingPoint;
      } else if (endPointBeforeSnappingIsCalculated < stoppingPoints[i] &&
          endPointBeforeSnappingIsCalculated > stoppingPoints[i] - (stoppingPoints[i] - stoppingPoints[i - 1]) / 2) {
        double stoppingPoint = stoppingPoints[i];
        debugPrint(stoppingPoint.toString());
        return stoppingPoint;
      }
    }
    throw Error.safeToString('Failed finding a new scroll simulation endpoint for this scroll animation');
  }
}

1 个答案:

答案 0 :(得分:0)

我找到了自己的答案-

事实证明,我的Scrollable的ScrollPosition一直在不断对其自身调用goBallistic(velocity:0.0),这意味着它没有设置动画,但是它从未处于空闲状态,因此被锁定为无法响应的状态指针事件。

问题出在BallisticScrollActivity上,这是指针离开屏幕后在可滚动对象中发生的猛击的一部分。一旦BallisticScrollAcitivity结束,它的ScrollPosition就会调用ScrollPositionWithSingleContext.goBallistic(velocity:0.0),它使用当前ScrollPhysics的ScrollPhysics.createBallisticSimulation(scrollMetrics:this,velocity:0.0)创建一个仿真。

   BallisticScrollActivity._end() => 
   ScrollPositionWithSingleContext.goBallistic(velocity: 0.0) =>
   ScrollPhysics.createBallisticSimulation(scrollMetrics:this, velocity:0.0)

但是,默认的ScrollPhysics具有if语句,如果当前速度为零,则返回null:

   if (velocity.abs() < tolerance.velocity)
      return null;

如果接收到的Simulation为null,则ScrollPositionWithSingleContext.createBallisticSimulation调用ScrollPositionWithSingleContext.goIdle():

final Simulation simulation = physics.createBallisticSimulation(this, velocity);
if (simulation != null) {
  beginActivity(new BallisticScrollActivity(this, simulation, context.vsync));
} else {
  goIdle();
}

可以处理BallisticScrollActivity和Animations,并允许Scrollable再次响应触摸事件。

所以我要做的就是添加

if (velocity.abs() < .0003) {
  return null;
}
在我返回CustomScrollSimulation之前,先

到我的CustomScrollPhysics的createBallisticSimulation(),一切都很好。

当然,存在的问题是,如果不先停止Scrollable的滚动,就无法在移动的GestureDetector上注册水龙头,这感觉很糟糕,但是每个应用程序都有这个问题。

希望这会有所帮助!