英文:
Combine scrolling listview with draggable items that limit draggable direction to vertical?
问题
我正试图创建一个横向元素的画廊,可能会延伸到视图的边缘,因此需要横向可滚动。我有这个。 (主要是为了桌面应用程序)
画廊中的元素应该是可拖动的,但只能进行垂直拖动,以便用户可以将它们放在其他位置。
我尝试过各种方法,包括侦听器。下面的代码似乎最接近我需要的,然而,图片元素在所有方向上都是可拖动的。我想要的是,当用户开始以水平方向拖动它们时,而不是它们可拖动,手势/控制传递给父级列表视图。基本上用户可以水平拖动来滚动,垂直拖动来将元素拉出画廊。
使用当前的代码,用户可以通过在元素之间点击并拖动来滚动列表视图,似乎手势检测器从不调用onHorizontalDragStart(或onHorizontalDragUpdate)。
(我还尝试过使用两个嵌套的GestureDetectors,一个围绕在列表视图周围,另一个围绕在PictElementDisplay周围,但这似乎没有太多意义。)
// 你的代码保持不变
英文:
I am attempting to have a horizontal gallery of elements which probably extends beyond the edges of the view so it needs to be horizontally scrollable. I have this. (This is primarily for a desktop app)
The elements in the gallery should be draggable but only for vertical dragging so that the user can place them somewhere else.
I've tried various approaches including a listener. Below seems to get closest to what I need, however, the picture elements are draggable in all directions. What I would like is when the user starts dragging them in a horizontal direction, instead of them being draggable, the gesture/control passes to the parent listview. So basically user can drag horizontally to scroll, and vertically to pull elements out of the gallery.
With current code, the user can scroll the listview by clicking and dragging between the elements, it seems the gesture detector never calls the onHorizontalDragStart (or onHorizontalDragUpdate.
(I have also tried with two nested GestureDetectors, one around the listview, and one around the PictElementDisplay but that didn't seem to make much sense.)
class PictGalleryView extends StatelessWidget {
PictGalleryView({
Key? key,
required this.size,
}) : super(key: key);
final Size size;
final projectSettings = GetIt.I<ProjectSettings>();
ScrollController _scrollController = ScrollController();
@override
Widget build(BuildContext context) {
return SizedBox(
height: size.height,
width: size.width,
child: ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(
dragDevices: {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
},
),
child: ListView.builder(
controller: _scrollController,
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: projectSettings.numPictElements,
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(5),
child: Center(
child: GestureDetector(
onHorizontalDragStart: (details) {
dev.log('onHorizontalDragStart');
// this doesn't happen?
},
child: PictElementDisplay(
//this shouldn't be horizontally draggable but it is!
element: projectSettings.pictElementDataList[index],
size: Size(75, 60),
),
),
),
);
},
),
),
);
}
}
//...
class PictElementDisplay extends StatelessWidget {
PictElementDisplay({
Key? key,
required this.element,
required this.size,
}) : super(key: key);
final PictElementData element;
final Size size;
@override
Widget build(BuildContext context) {
return SizedBox.fromSize(
child: Draggable(
data: element,
feedback: Container(
height: size.height,
width: size.width,
decoration: const BoxDecoration(
color: Colors.green, //todo
),
child: Text('id: ${element.id.toString()}'),
),
child: Container(
height: size.height,
width: size.width,
decoration: const BoxDecoration(
color: Colors.red, //todo
),
child: Text('id: ${element.id.toString()}'),
),
),
);
}
}
(and ChatGPT doesn't seem to quite know how to do it either. ). Thanks in advance.
答案1
得分: 0
GestureDetector
必须位于 Draggable
内部,然后才能用于覆盖默认行为。在这种用例中,可以将与父级 ListView
关联的 ScrollControler
作为参数传递,以便修改它,从而控制列表视图。如果要在不同上下文中使用 PictElementDisplay
,并且只希望在出现在画廊中时覆盖,可以将小部件设置为可为空,并且仅在滚动控制器存在时添加逻辑来更改行为,即。
class PictElementDisplay extends StatelessWidget {
PictElementDisplay({
required this.element,
required this.size,
this.scrollController,
});
final PictElementData element;
final Size size;
final ScrollController? scrollController;
@override
Widget build(BuildContext context) {
Widget listChild = Container(
height: size.height,
width: size.width,
decoration: const BoxDecoration(
color: Colors.red, //todo
),
child: Text('id: ${element.id.toString()}'),
);
Widget gestureWidget = GestureDetector(
onHorizontalDragUpdate: (details) {
// 将控制权传递给父 ListView 处理水平滚动
dev.log('onHorizontalDragUpdate ${details.toString()}');
scrollController?.jumpTo(scrollController!.offset - details.delta.dx);
},
onTap: () {
// 例如,无需拖动即可添加到当前活动页面
dev.log('tapped ${element.id}');
},
child: listChild,
);
Widget draggableChildWidget =
(scrollController != null) ? gestureWidget : listChild;
return SizedBox.fromSize(
child: Draggable(
data: element,
feedback: Container(
height: size.height,
width: size.width,
decoration: const BoxDecoration(
color: Colors.green, //todo
),
child: Text('id: ${element.id.toString()}'),
),
child: draggableChildWidget,
),
);
}
}
在父级中
@override
Widget build(BuildContext context) {
return SizedBox(
height: widget.size.height,
width: widget.size.width,
child: ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(
dragDevices: {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
},
),
child: ListView.builder(
controller: scrollController,
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: projectSettings.numPictElements,
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
PictElementData element =
projectSettings.pictElementDataList[index];
Size size = Size(75, 60);
return Padding(
padding: const EdgeInsets.all(5),
child: Center(
child: PictElementDisplay(
element: element,
size: size,
// scrollController 允许小部件覆盖自己的水平拖动
scrollController: scrollController,
),
),
);
},
),
),
);
}
英文:
The GestureDetector
has to be inside the Draggable
then it can be used to override the default behaviour. In this use-case, you can pass in the ScrollControler
associated with the parent ListView
as a parameter to be modified which can control the listview. If you want to use the PictElementDisplay
in different contexts and the override to only apply when it appears in the gallery, you can make the widget nullable and add logic only to change the behaviour when the scroll controller is present i.e.
class PictElementDisplay extends StatelessWidget {
PictElementDisplay({
required this.element,
required this.size,
this.scrollController,
});
final PictElementData element;
final Size size;
final ScrollController? scrollController;
@override
Widget build(BuildContext context) {
Widget listChild = Container(
height: size.height,
width: size.width,
decoration: const BoxDecoration(
color: Colors.red, //todo
),
child: Text('id: ${element.id.toString()}'),
);
Widget gestureWidget = GestureDetector(
onHorizontalDragUpdate: (details) {
// Pass the control to the parent ListView to handle horizontal scrolling
dev.log('onHorizontalDragUpdate ${details.toString()}');
scrollController?.jumpTo(scrollController!.offset - details.delta.dx);
},
onTap: () {
//E.g. add to currently active page without needing to drag it
dev.log('tapped ${element.id}');
},
child: listChild,
);
Widget draggableChildWidget =
(scrollController != null) ? gestureWidget : listChild;
return SizedBox.fromSize(
child: Draggable(
data: element,
feedback: Container(
height: size.height,
width: size.width,
decoration: const BoxDecoration(
color: Colors.green, //todo
),
child: Text('id: ${element.id.toString()}'),
),
child: draggableChildWidget,
),
);
}
}
and in the parent level
@override
Widget build(BuildContext context) {
return SizedBox(
height: widget.size.height,
width: widget.size.width,
child: ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(
dragDevices: {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
},
),
child: ListView.builder(
controller: scrollController,
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: projectSettings.numPictElements,
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
PictElementData element =
projectSettings.pictElementDataList[index];
Size size = Size(75, 60);
return Padding(
padding: const EdgeInsets.all(5),
child: Center(
child: PictElementDisplay(
element: element,
size: size,
// scrollController allows the widget to
// override its own horizontal dragging
scrollController: scrollController,
),
),
);
},
),
),
);
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论