在 SingleChildScrollView 中使用 onPan GestureDetector
Use onPan GestureDetector inside a SingleChildScrollView
问题
所以我在 SingleChildScrollView 中有一个 GestureDetector 小部件(滚动视图是为了适应较小的屏幕)。 GestureDetector 正在侦听平移更新。
当 SingleChildScrollView“正在使用”时,GestureDetector 无法接收平移更新,因为来自用户的“拖动”输入被转发到 SingleChildScrollView。
我想要的
当拖动到 GestureDetector 顶部时,使子 GestureDetector 优先于 SingleChildScrollView -- 但仍然具有在 GestureDetector 外部滚动 SingleChildScrollView 的功能。
例子
如果你把这段代码copy/paste改成dart pad你就能明白我的意思了。当渐变容器很大时,SingleChildScrollView 不活动——您可以拖动蓝色框并在控制台中查看更新。但是,一旦您按下切换按钮,容器就会变小并且 SingleChildScrollView 会变为活动状态。您现在无法再在只能滚动容器的控制台中获取平移更新。
旁注:似乎如果您快速拖动蓝色框,您可以获得拖动更新,但缓慢拖动它只会滚动容器。我不确定这是错误还是功能,但我无法在我的生产应用程序中重现相同的结果。
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
bool enabled = false;
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
height: enabled ? 200 : 400,
child: SingleChildScrollView(
child: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment(0.8, 0.0),
colors: <Color>[Color(0xffee0000), Color(0xffeeee00)],
tileMode: TileMode.repeated,
),
),
height: 400,
width: 200,
child: Center(
child: GestureDetector(
onPanUpdate: (details) => print(details),
child: Container(
height: 100,
width: 100,
color: Colors.blue,
child: Center(
child: Text(
"Drag\nme",
textAlign: TextAlign.center,
),
),
),
),
),
),
),
),
ElevatedButton(
onPressed: () => setState(() {
enabled = !enabled;
}),
child: Text("Switch"))
],
);
}
}
As @PatrickMahomes said this answer (by @Chris) 将解决问题。但是,它只会检查拖动是否与 GestureDetector 一致。所以一个完整的解决方案是这样的:
bool _dragOverMap = false;
GlobalKey _pointerKey = new GlobalKey();
_checkDrag(Offset position, bool up) {
if (!up) {
// find your widget
RenderBox box = _pointerKey.currentContext.findRenderObject();
//get offset
Offset boxOffset = box.localToGlobal(Offset.zero);
// check if your pointerdown event is inside the widget (you could do the same for the width, in this case I just used the height)
if (position.dy > boxOffset.dy &&
position.dy < boxOffset.dy + box.size.height) {
// check x dimension aswell
if (position.dx > boxOffset.dx &&
position.dx < boxOffset.dx + box.size.width) {
setState(() {
_dragOverMap = true;
});
}
}
} else {
setState(() {
_dragOverMap = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Scroll Test"),
),
body: new Listener(
onPointerUp: (ev) {
_checkDrag(ev.position, true);
},
onPointerDown: (ev) {
_checkDrag(ev.position, false);
},
child: ListView(
// if dragging over your widget, disable scroll, otherwise allow scrolling
physics:
_dragOverMap ? NeverScrollableScrollPhysics() : ScrollPhysics(),
children: [
ListTile(title: Text("Tile to scroll")),
Divider(),
ListTile(title: Text("Tile to scroll")),
Divider(),
ListTile(title: Text("Tile to scroll")),
Divider(),
// Your widget that you want to prevent to scroll the Listview
Container(
key: _pointerKey, // key for finding the widget
height: 300,
width: double.infinity,
child: FlutterMap(
// ... just as example, could be anything, in your case use the color picker widget
),
),
],
),
),
);
}
}
问题
所以我在 SingleChildScrollView 中有一个 GestureDetector 小部件(滚动视图是为了适应较小的屏幕)。 GestureDetector 正在侦听平移更新。
当 SingleChildScrollView“正在使用”时,GestureDetector 无法接收平移更新,因为来自用户的“拖动”输入被转发到 SingleChildScrollView。
我想要的
当拖动到 GestureDetector 顶部时,使子 GestureDetector 优先于 SingleChildScrollView -- 但仍然具有在 GestureDetector 外部滚动 SingleChildScrollView 的功能。
例子
如果你把这段代码copy/paste改成dart pad你就能明白我的意思了。当渐变容器很大时,SingleChildScrollView 不活动——您可以拖动蓝色框并在控制台中查看更新。但是,一旦您按下切换按钮,容器就会变小并且 SingleChildScrollView 会变为活动状态。您现在无法再在只能滚动容器的控制台中获取平移更新。
旁注:似乎如果您快速拖动蓝色框,您可以获得拖动更新,但缓慢拖动它只会滚动容器。我不确定这是错误还是功能,但我无法在我的生产应用程序中重现相同的结果。
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
bool enabled = false;
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
height: enabled ? 200 : 400,
child: SingleChildScrollView(
child: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment(0.8, 0.0),
colors: <Color>[Color(0xffee0000), Color(0xffeeee00)],
tileMode: TileMode.repeated,
),
),
height: 400,
width: 200,
child: Center(
child: GestureDetector(
onPanUpdate: (details) => print(details),
child: Container(
height: 100,
width: 100,
color: Colors.blue,
child: Center(
child: Text(
"Drag\nme",
textAlign: TextAlign.center,
),
),
),
),
),
),
),
),
ElevatedButton(
onPressed: () => setState(() {
enabled = !enabled;
}),
child: Text("Switch"))
],
);
}
}
As @PatrickMahomes said this answer (by @Chris) 将解决问题。但是,它只会检查拖动是否与 GestureDetector 一致。所以一个完整的解决方案是这样的:
bool _dragOverMap = false;
GlobalKey _pointerKey = new GlobalKey();
_checkDrag(Offset position, bool up) {
if (!up) {
// find your widget
RenderBox box = _pointerKey.currentContext.findRenderObject();
//get offset
Offset boxOffset = box.localToGlobal(Offset.zero);
// check if your pointerdown event is inside the widget (you could do the same for the width, in this case I just used the height)
if (position.dy > boxOffset.dy &&
position.dy < boxOffset.dy + box.size.height) {
// check x dimension aswell
if (position.dx > boxOffset.dx &&
position.dx < boxOffset.dx + box.size.width) {
setState(() {
_dragOverMap = true;
});
}
}
} else {
setState(() {
_dragOverMap = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Scroll Test"),
),
body: new Listener(
onPointerUp: (ev) {
_checkDrag(ev.position, true);
},
onPointerDown: (ev) {
_checkDrag(ev.position, false);
},
child: ListView(
// if dragging over your widget, disable scroll, otherwise allow scrolling
physics:
_dragOverMap ? NeverScrollableScrollPhysics() : ScrollPhysics(),
children: [
ListTile(title: Text("Tile to scroll")),
Divider(),
ListTile(title: Text("Tile to scroll")),
Divider(),
ListTile(title: Text("Tile to scroll")),
Divider(),
// Your widget that you want to prevent to scroll the Listview
Container(
key: _pointerKey, // key for finding the widget
height: 300,
width: double.infinity,
child: FlutterMap(
// ... just as example, could be anything, in your case use the color picker widget
),
),
],
),
),
);
}
}