将可滚动的列表视图与可拖动的项组合,限制拖动方向为垂直。

huangapple go评论63阅读模式
英文:

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,
                ),
              ),
            );
          },
        ),
      ),
    );
  }

huangapple
  • 本文由 发表于 2023年2月6日 22:10:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/75362407.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定