鸿蒙手势简单使用2

0 阅读5分钟

手势组合:

 鸿蒙还提供了实现手势组合方式;同时也规定了手势组合的三方模式:如下:

GestureMode.Sequence:顺序识别,根据注册顺序依次进行手势识别,直到所有手势识别成功。如果任一手势识别失败,则后续手势识别均无法完成。在顺序识别手势组中,仅最后一个手势能响应onActionEnd事件。

GestureMode.Parallel:并发识别,注册的手势同时识别,直到所有手势识别结束,手势识别互相不影响。

GestureMode.Exclusive:互斥识别,注册的手势同时识别,若有一个手势识别成功,则结束手势识别,其他手势识别均失败。

    Column() {
      Text('验证手势的组合')
        .fontSize(20)
        .fontColor('#333')
    }
    .gesture(
      GestureGroup(
        GestureMode.Sequence, //组合手势识别的顺序
        //双击触发
        // TapGesture({ count: 2 }).onAction(() => {
        //   console.log('-----------> gesture  group TapGesture ')
        // }),
        //长点击相应
        LongPressGesture({
          fingers:1,
        })
          .onAction(() => {
            console.log('-----------> gesture  group LongPressGesture ')
          }),

        PanGesture()
          .onActionStart(() => {
            console.log('-----------> gesture  group PanGesture onActionStart')
          })
          .onActionUpdate(() => {
            console.log('-----------> gesture  group PanGesture onActionUpdate')
          })
          .onActionEnd(() => {
            console.log('-----------> gesture  group PanGesture onActionEnd')
          })
      )
    )
    .width('100%')
    .height(50)
    .alignItems(HorizontalAlign.Center)
    .justifyContent(FlexAlign.Center)
    .backgroundColor(Color.Gray)

注意:如果在组合手势中,模式顺序执行,如果第一个是TapGesture 那么将导致后续无法相应;因为只有松手时才响应onAction 事件;

手势的拦截处理:

手势拦截可以查看鸿蒙的拦截处理;

文档中心

针对 Scroll 嵌套 list 滑动引发的冲突,可以借助于两种方式处理;

方案:1.onScrollFrameBegin() 在外部scroll 或者list 中设置;同时设置两者的的 scroller 通过scroller 实现滚动交替;可以设置在父容器中 将滚动事件传递给子容器,也可以设置到子容器中,将滚动事件传递到父容器中 ;来实现滚动事件的交互;例如设置嵌套滚动中的代码如下:

 //当前在父容器 scroll 设置,也可以设置在内部
 .onScrollFrameBegin((offset: number, state: ScrollState) => {
       //如果想要将滚动消费交给子容器消费,可以将滑动交给 childScroll
         if (offset < 0) {
             //手指向下滑动
              if (this.scrollerForChild.currentOffset().yOffset != 0 ){
                 //没有滚动到了 list 最顶部
                 this.scrollerForChild.scrollBy(0, offset);//将事件交给子组件滚动
                 return {offsetRemain:0}//表示当前父容器消费偏移量 为0
             }
         }
       return { offsetRemain: offset };//表示滚动由父容器 scroll 消费
 }

注意:onScrollFrameBegin:在该方法中手指向上滑动 offset>0 ,向下滑动 offset<0;这个地方和滑动手势中的 offsetY 是不同的;

方案2.nestedScroll 通过设置交替滚动来满足,但是这种情况,一般应用于scroll+tab+list,实现沉浸式效果;同样也能更好的实现scroll+list的滚动交互效果;常见的设置如下:

  .nestedScroll({
          //当可滚动组件向前滚动时,设置嵌套滚动模式
          scrollForward: NestedScrollMode.PARENT_FIRST,
          //当可滚动组件向后滚动时,设置嵌套滚动模式
          scrollBackward: NestedScrollMode.SELF_FIRST
        })

   注意:在 Scroll+list时,如果 list 外部存在 Column 如果设置height('100%')的话,将列表将无法滚动;

实现下拉功能验证:

image.png

     下面是一个demo,效果是实现下拉拖拽效果。类似下拉刷新;只作为验证功能使用,没有考虑到扩展,封装其他;

使用到了技能点:onScrollFrameBegin+nestedScroll处理交互冲突;onTouch 处理松手后操作;animateTo实现自动的回弹和展示效果;具体代码如下:

  private scrollerForParent: Scroller = new Scroller();
  private scrollerForChild: Scroller = new Scroller();
  @Local list: number[] = new Array(50).fill(0)
  .map((_: number, index) => index + 1)
  @Local translateY: number = -200

  @Builder
  refresh() {
    //编写一个功能,就是类似于下拉刷新,上拉加载功能;
    Scroll(this.scrollerForParent) {
      Column() {
        Row()
          .width('100%')
          .height(200)
          .backgroundColor(Color.Brown)

        List({ space: 10, scroller: this.scrollerForChild }) {
          Repeat(this.list)
            .each((item: RepeatItem<number>) => {
              ListItem() {
                Text('这个是测试数据' + item.index)
                  .fontSize(20)
                  .fontColor('#333')
                  .width('100%')
                  .textAlign(TextAlign.Center)
                  .height(80);
              };
            })
            .key((item: number, index: number) => {
              return `${item}_${index}_${Math.random()}`;
            });
        }
        .width('100%')
        .height('100%')
        .enableScrollInteraction(false) //禁止滚动
        .scrollBar(BarState.Off)
        .nestedScroll({
          scrollForward: NestedScrollMode.PARENT_FIRST,
          scrollBackward: NestedScrollMode.SELF_FIRST
        });
      }
      .width('100%')
      .translate({ y: this.translateY })
      .onTouch((event: TouchEvent) => {
        switch (event.type) {
          case TouchType.Cancel:
          case TouchType.Up:
            //在松手的时候,判断当前展示同步的位置
            /**
             * 如果头部区域没有展示出来,就不做处理
             * 如果头部区域展示出来,就判断展示出来的大小,如果超出指定的阈值就完全展示,如果小于,就进行隐藏
             */
            if (this.translateY == -200) {
              return;
            }

            if (this.translateY >= -120) {
              //表示滑动超出了120的距离
              this.getUIContext().animateTo({
                delay: 0,
                duration: 200,
                curve: Curve.Linear,
                iterations: 1,
                onFinish: () => {
                }
              }, () => {
                this.translateY = 0;
              });
            } else {
              //表示没有超出一般的距离
              this.getUIContext().animateTo({
                delay: 0,
                duration: 200,
                curve: Curve.Linear,
                iterations: 1,
                onFinish: () => {
                  this.translateY = -200;
                }
              }, () => {
                this.translateY = -200;
              });
            }
            break;
        }
      });
    }
    .width('100%')
    .height('100%')
    .scrollable(ScrollDirection.Vertical)
    .scrollBar(BarState.Off)
    .onScrollFrameBegin((offset: number, state: ScrollState) => {
      /**
       * 想要实现的功能,就是在下滑的时候将因此区域的父容器跟随滚动显示出来
       *1.在手指向下滑动的时候,获取到滑动的值,然后更改平移的值;但是最大的平移的值是200
       */
      let resOffset: number = offset;
      let currentHeight: number = this.scrollerForParent.currentOffset().yOffset;
      let newOffset = currentHeight + offset;
      console.log('--------- > PanGesture ' + currentHeight);

      if (offset < 0) {
        //手指向下滑动
        console.log('--------- > PanGesture 1 ' + this.scrollerForChild.currentOffset().yOffset + ' ...state ' + state);
        if (this.scrollerForChild.currentOffset().yOffset == 0 && state == ScrollState.Scroll) {
          //如果子滚动器滑动到了顶部,同时是拖拽,那么就平移布局
          this.translateY = (this.translateY - offset >= 0) ? 0 : this.translateY - offset;
          return { offsetRemain: 0 };
        }
        //如果触发了 fling 效果后,如果不足以完整展示头部局,就将移动效果交给动画
        if (this.scrollerForChild.currentOffset().yOffset == 0 && state == ScrollState.Fling) {
          return { offsetRemain: 0 };
        }

        //如果没有滚动到最上方,就由子列表滚动
        if (newOffset < 0) {
          //表示当前头部区域还是没有展示出来
          resOffset = 0 - currentHeight;
        }

        console.log('--------- > PanGesture 2' + (offset - resOffset));

        this.scrollerForChild.scrollBy(0, offset - resOffset);
      } else {
        //手指向上移动
        //在向上移动的时候,如果处于顶部,当前展示了头部局,同时处于手势滑动时
        if (this.scrollerForChild.currentOffset().yOffset <= 0 && this.translateY > -200 &&
          state == ScrollState.Scroll) {
          this.translateY = (this.translateY - offset <= -200) ? -200 : this.translateY - offset;
          return { offsetRemain: 0 };
        }

        //如果处于顶部,同时头部局可见,同时处于 fling 状态,那么就不仅移动,将移动效果交给动画处理
        if (this.scrollerForChild.currentOffset().yOffset <= 0 && this.translateY > -200 &&
          state == ScrollState.Fling) {
          return { offsetRemain: 0 };
        }

        //这个地方不考虑 有脚布局
        if (newOffset > 0) {
          resOffset = 0 - currentHeight;
        }
        console.log('--------- > PanGesture 3 ' + resOffset + ' ....child ' + (offset - resOffset));
        this.scrollerForChild.scrollBy(0, offset - resOffset);
      }
      return { offsetRemain: resOffset };
    })
  }

由于个人能力有限;如果存在问题,望各位大佬及时指正;