DEV Community

HarmonyOS
HarmonyOS

Posted on

How to handle the issue where a single drag operation cannot simultaneously scroll the List component and …?

Read the original article:How to handle the issue where a single drag operation cannot simultaneously scroll the List component and change the progress bar?

Context

When touching the position of the progress bar and dragging without lifting the finger, if the drag starts vertically, subsequent drags will only affect the scrolling of the List (regardless of the subsequent drag direction). Conversely, if the drag starts horizontally, subsequent drags will only affect the progress bar and not the list scrolling. This effect persists until the finger is lifted.

Description

Currently, many pages require multi-directional movement with a single gesture operation, involving multiple components. The current version only supports nestedScroll for passing through drag events in a single direction (vertical or horizontal) for non-touch (Touch) interactive events such as clicks (press + lift). In other cases, passing through is not supported, and the component will consume the event if recognized. Additionally, the Slider component does not support nestedScroll, so it is necessary to intervene with the PanGesture.

Solution

From the description, it is understood that using a PanGesture can influence multiple directions based on the distance the finger moves in each direction. The subsequent steps only need to address which component the gesture is bound to and how to implement the internal parameters of the gesture.

1 - Component for gesture binding:

  • Only when the finger drags on the Slider will it involve different components affecting two directions. Therefore, the gesture is bound to the Slider. This makes it convenient to determine which Slider is affected by the drag and to avoid affecting the click and drag of other components, reducing coupling and system performance consumption.

2 - Internal implementation of gesture parameters:

  • Use scrollBy to affect external sliding components.
  • Use value to affect the internal progress bar component.

That is, bind the PanGesture to the progress bar. Note that the Slider needs to be wrapped in an additional container, and the Slider itself needs to be disabled or set to hitTestBehavior.None to prevent the component from consuming the gesture. The gesture is bound to the container. The reason for the additional binding is that when a component consumes the gesture, any additional gestures bound to the component will not take effect. Therefore, the gesture needs to be passed through to the parent component for processing. The corresponding control interface can be obtained through the internal gesture parameters in the positioning logic. The implementation code is as follows:

@Entry
@Component
struct Index {
  dataArr: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 171, 172, 173, 174];
  @State eventType: string = ''
  @State sliderNum: number[] = []
  @State offsetX: number = 0
  @State offsetY: number = 0
  scroller: Scroller = new Scroller()

  aboutToAppear(): void {
    for (let i = 0; i < this.dataArr.length; i++) {
      this.sliderNum[i] = 0
    }
  }

  build() {
    Scroll() {
      WaterFlow({ scroller: this.scroller }) {
        ForEach(this.dataArr, (item: number, index: number) => {
          FlowItem() {
            Column() {
              Slider({
                value: this.sliderNum[index],
                min: 0,
                max: 100,
                style: SliderStyle.OutSet
              })
                .enabled(false)
            }
            .hitTestBehavior(HitTestMode.Block)
            .gesture(
              PanGesture()
                .onActionStart((event: GestureEvent) => {
                  this.offsetX = 0
                  this.offsetY = 0
                })
                .onActionUpdate((event: GestureEvent) => {
                  if (event) {
                    if (event.offsetY != 0) {
                      this.scroller.scrollBy(0, -px2vp(event.offsetY - this.offsetY))
                      this.offsetY = event.offsetY
                    }
                    if (event.offsetX != 0) {
                      this.sliderNum[index] += px2vp(event.offsetX - this.offsetX)
                      this.offsetX = event.offsetX
                    }
                  }
                })
                .onActionEnd((event: GestureEvent) => {
                  console.info('Pan end')
                })
            )
          }
          .height(100)
        })
      }
      .columnsTemplate("1fr 1fr")
      .columnsGap(10)
      .rowsGap(5)
      .backgroundColor(0xFAEEE0)
      .width('100%')
      .height('100%')
    }
    .height('100%')
    .width('100%')
  }
}
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • Do not bind gestures to the List or its parent component, as this will complicate the determination of which progress bar to control.
  • Dragging is real-time, so the parameters for each drag need to be calculated by subtracting the current drag distance from the previous one.
  • It is recommended to convert the drag distance into a different base.

Written by Mehmet Karaaslan

Top comments (0)