为什么我的Flutter自定义ScrollPhysics破坏了Scrollable中的GestureDetectors?我正在使用使用我编写的自定义ScrollPhysics的SingleChildScrollView,由于某种原因,除非ScrollView处于过度滚动状态,否则我在ScrollView中拥有的GestureDetectors根本不会做出触摸反应。
基本上,除非我最近才滚动到滚动范围并且ScrollView在滚动范围处停止,否则我将无法检测到自定义物理ScrollView内部的任何手势。当然,检测普通ScrollView内的手势也可以。
这是代码本身:
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');
}
}
答案 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上注册水龙头,这感觉很糟糕,但是每个应用程序都有这个问题。
希望这会有所帮助!