Почему мой пользовательский ScrollPhysics, использующий Flutter, ломает GestureDetectors в Scrollable? - PullRequest
0 голосов
/ 06 ноября 2018

Почему мой пользовательский ScrollPhysics из Flutter ломает GestureDetectors в моем Scrollable? Я использую SingleChildScrollView, который использует пользовательский ScrollPhysics, который я написал, и по какой-то причине те элементы GestureDetectors, которые есть в ScrollView, вообще не реагируют на прикосновения, ЕСЛИ БЕЗ ScrollView не перепрограммирован.

По сути, если я только что недавно прокрутил до экстента прокрутки и ScrollView не остановился на экстенте прокрутки, я не могу обнаружить какие-либо жесты в пользовательском физическом ScrollView. Конечно, обнаружение жестов внутри обычного ScrollView работает нормально.

Вот видео моего затруднительного положения; синий ScrollView слева использует ScrollPhysics по умолчанию, а янтарный справа использует мой пользовательский ScrollPhysics с пятью накладными полями в каждом ScrollView:

Вот 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 голосов
/ 03 декабря 2018

Я нашел свой ответ -

Оказывается, что ScrollPosition моего Scrollable постоянно вызывало goBallistic (скорость: 0.0) для себя, что означает, что оно не анимировалось, но никогда не работало, поэтому оно было заблокировано в состоянии, когда оно не отвечало на указатель событий.

Проблема была в BallisticScrollActivity, частице прокрутки прокрутки, которое происходит после того, как указатель исчезает с экрана. Как только BallisticScrollAcitivity заканчивается, его ScrollPosition вызывает ScrollPositionWithSingleContext.goBallistic (speed: 0.0), который создает симуляцию с использованием текущего ScrollPhysics ScrollPhysics.createBallisticSimulation (scrollMetrics: this, speed: 0.0).

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

Однако ScrollPhysics по умолчанию имеет оператор if, который говорит, что должен возвращать ноль, если текущая скорость равна нулю:

   if (velocity.abs() < tolerance.velocity)
      return 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;
}

в createBallisticSimulation моего CustomScrollPhysics () до того, как я вернул свой CustomScrollSimulation, и все работает довольно хорошо.

Конечно, есть проблема в том, что невозможно зарегистрировать нажатие на движущийся GestureDetector без предварительной остановки прокрутки от прокрутки, что выглядит ужасно, но у каждого приложения есть такая проблема.

Надеюсь, это поможет!

...