HarmonyOS鸿蒙Next中两层视图如何在点击的时候动态选择点击事件分发给哪一层?
HarmonyOS鸿蒙Next中两层视图如何在点击的时候动态选择点击事件分发给哪一层?
背景:
下层是信息流,上层是全屏png图片(类似烟花,大部分区域是透明,烟花部分非透明)。
需求:
触摸透明区域,上层不响应触摸事件, 下层信息流正常滑动和点击。
点击位置是非透明区域, 上层响应点击事件, 下层信息流不响应点击。
安卓端因为灵活的事件分发,实现很容易。但是鸿蒙端应该如何实现?
【解决方案】
开发者你好,可以使用自定义事件分发实现,在onChildTouchTest回调中判断点击或者触摸是否在图片区域,如果在图片区域,就响应图片的点击事件,如果不在就响应列表的点击事件,实现代码如下:
import { PromptAction } from '@kit.ArkUI';
@Entry
@Component
struct Index {
  private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
  promptAction: PromptAction = this.getUIContext().getPromptAction();
  @State text: string = 'Button';
  build() {
    RelativeContainer() {
      List({ space: 12, initialIndex: 0 }) {
        ForEach(this.arr, (item: number) => {
          ListItem() {
            Text('Item ' + item)
              .width('100%')
              .height(56)
              .fontSize(16)
              .textAlign(TextAlign.Start)
          }.borderRadius(24)
          .backgroundColor(Color.White)
          .padding({ left: 12, right: 12 })
          .onClick(() => {
            this.promptAction.showToast({ message: 'you click the listItem.', duration: 300 });
          })
        }, (item: number) => item.toString())
      }
      .listDirection(Axis.Vertical)
      .scrollBar(BarState.Off)
      .edgeEffect(EdgeEffect.Spring)
      .width('100%')
      .height('100%')
      .id('MyList')
      Row() {
        Image($r('app.media.startIcon'))
          .width('200vp')
          .onClick(() => {
            this.promptAction.showToast({ message: 'you click the button.', duration: 300 });
          })
          .id('MyImage')
      }
      .width('100%')
      .height('100%')      
    }
    .width('100%')
    .height('100%')
    .backgroundColor(0xF1F3F5)
    .padding({ left: 12, right: 12, bottom: 24 })
    .onChildTouchTest((touchInfo) => {
      for (let info of touchInfo) {
        // 触摸或者点击在图片区域时,相应图片的点击事件
        if (info.windowX > 13 && info.windowX < 208.5 && info.windowY > 297.4 && info.windowY < 495.1) {
          return { id: 'MyImage', strategy: TouchTestStrategy.FORWARD_COMPETITION };
        }
      }
      return { id: 'MyList', strategy: TouchTestStrategy.FORWARD_COMPETITION };
    })
  }
}
【背景知识】
自定义事件分发:在处理触屏事件时,ArkUI会在触屏事件触发前进行按压点和组件区域的触摸测试,收集需要响应触屏事件的组件,再基于触摸测试结果分发相应的触屏事件。在父节点,可以通过onChildTouchTest决定子节点的触摸测试方式,影响子组件的触摸测试,从而影响后续的触屏事件分发。
更多关于HarmonyOS鸿蒙Next中两层视图如何在点击的时候动态选择点击事件分发给哪一层?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
在鸿蒙ArkUI中实现两层视图的点击事件动态分发,可以通过设置上层组件的响应区域(responseRegion) 来实现。这种方法允许您定义上层组件仅在某些特定区域响应触摸事件,而其他区域事件会自动穿透到下层。
解决方案:使用 responseRegion 属性
responseRegion 属性可以设置组件的触摸热区,只有落在热区内的触摸事件才会被该组件处理。对于透明区域,您可以不设置热区,使事件穿透;对于非透明区域,设置热区使上层响应事件。
实现步骤:
- 定义非透明区域:根据上层PNG图片中非透明部分(烟花)的位置和大小,用一个或多个矩形(Rect)定义热区。如果烟花区域不规则,可以用多个矩形近似覆盖。
 - 设置上层组件的 responseRegion:将定义的热区数组赋值给上层图片的 
responseRegion属性。 - 上层组件绑定点击事件:为上层图片设置 
onClick事件,处理烟花部分的点击逻辑。 - 下层组件正常处理事件:下层信息流(如List)无需特殊设置,事件在穿透后会正常触发滑动手势和点击。
 
代码示例:
@Entry
@Component
struct Index {
  // 定义烟花非透明区域的热区(示例坐标,需根据实际图片调整)
  private fireworkRects: Array<Rect> = [
    { x: 100, y: 200, width: 150, height: 150 }, // 烟花区域1
    { x: 300, y: 400, width: 100, height: 100 }  // 烟花区域2
  ];
  build() {
    Stack() {
      // 下层信息流(如列表)
      List() {
        ForEach([1, 2, 3, 4, 5], (item: number) => {
          ListItem() {
            Text(`列表项 ${item}`)
              .fontSize(20)
              .padding(10)
          }
        })
      }
      .width('100%')
      .height('100%')
      .onClick(() => {
        console.log('下层列表被点击');
      })
      // 可以添加滑动手势等
      // 上层全屏PNG图片
      Image($r('app.media.fireworks')) // 替换为您的图片资源
        .width('100%')
        .height('100%')
        .responseRegion(this.fireworkRects) // 关键:设置响应区域仅为非透明部分
        .onClick(() => {
          console.log('上层烟花图片被点击');
          // 处理烟花点击事件
        })
    }
    .width('100%')
    .height('100%')
  }
}
替代方案(onTouch):
如果非透明区域无法用矩形近似,可以考虑使用 onTouch 事件手动控制事件冒泡,但需要预先知道非透明区域的坐标范围:
Image($r('app.media.fireworks'))
  .width('100%')
  .height('100%')
  .onTouch((event: TouchEvent) => {
    if (event.type === TouchType.Down) {
      const x = event.touches[0].x;
      const y = event.touches[0].y;
      if (this.isInFireworkRegion(x, y)) { // 自定义判断函数
        event.stopPropagation(); // 阻止事件冒泡到下层
      }
    }
  })
  .onClick(() => {
    console.log('上层烟花图片被点击');
  })
- 缺点:需要手动实现 
isInFireworkRegion逻辑,且无法直接获取像素透明度,需依赖预定义坐标。 
想让系统知道将事件传给谁?不如试着将事件传给所有组件。然后在组件的点击事件中判断要不要继续执行后面的代码。
HitTestMode
系统能力: SystemCapability.ArkUI.ArkUI.Full
| 名称 | 说明 | 
|---|---|
| Default | 默认触摸测试效果。自身及子节点响应触摸测试,但阻塞兄弟节点的触摸测试,不影响祖先节点的触摸测试。 元服务API: 从API version 11开始,该接口支持在元服务中使用。  | 
| Block | 自身响应触摸测试,阻塞子节点、兄弟节点和祖先节点的触摸测试。 元服务API: 从API version 11开始,该接口支持在元服务中使用。  | 
| Transparent | 自身和子节点均响应触摸测试,不会阻塞兄弟节点和祖先节点的触摸测试。 元服务API: 从API version 11开始,该接口支持在元服务中使用。  | 
| None | 自身不响应触摸测试,不会阻塞子节点、兄弟节点和祖先节点的触摸测试。 元服务API: 从API version 11开始,该接口支持在元服务中使用。  | 
| BLOCK_HIERARCHY | 自身和子节点响应触摸测试,阻止所有优先级较低的兄弟节点和父节点参与触摸测试。 元服务API: 从API version 20开始,该接口支持在元服务中使用。  | 
| BLOCK_DESCENDANTS | 自身不响应触摸测试,并且所有的后代(孩子,孙子等)也不响应触摸测试,不会影响祖先节点的触摸测试。 元服务API: 从API version 20开始,该接口支持在元服务中使用。  | 
参考文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-appendix-enums#hittestmode9
下层是信息流,组件就太多了,给所有组件加拦截逻辑不太现实,
可以可以监听你点击的区域是否未透明 如果透明
触摸透明区域,上层不响应触摸事件, 下层信息流正常滑动和点击。
点击位置是非透明区域, 上层响应点击事件, 下层信息流不响应点击。
在HarmonyOS Next中,可通过HitTestMode属性控制视图点击事件分发。设置父视图HitTestMode为HitTestMode.Block时,事件由父视图处理;设为HitTestMode.Transparent则事件穿透至子视图。使用onTouch事件结合HitTestMode动态调整分发目标,实现点击时选择事件接收层。
在HarmonyOS Next中,可以通过自定义组件的事件拦截机制实现分层点击事件分发。具体步骤如下:
- 上层图片组件使用
TouchTargetMode.Transparent模式,配合hitTestBehavior设置触摸测试行为: 
@Component
struct TopLayer {
  build() {
    Image($r('app.media.fireworks'))
      .hitTestBehavior(HitTestMode.Transparent)
      .onTouch((event: TouchEvent) => {
        if (this.isTransparent(event)) {
          event.stopPropagation() // 阻止事件传递
        }
      })
  }
  
  private isTransparent(event: TouchEvent): boolean {
    // 实现透明度检测逻辑
    // 返回true表示透明区域,false表示非透明区域
  }
}
- 下层信息流使用默认事件处理:
 
@Component
struct BottomLayer {
  build() {
    List() {
      // 信息流内容
    }
    .onClick(() => {
      // 信息流点击处理
    })
  }
}
- 组合两层视图时,通过
Stack容器嵌套: 
build() {
  Stack() {
    BottomLayer()
    TopLayer()
  }
}
关键点:
- 上层通过
hitTestBehavior和onTouch事件判断点击区域透明度 - 透明区域调用
event.stopPropagation()允许事件穿透 - 非透明区域不阻止事件传播,由上层自己处理
 - 下层信息流保持默认事件响应逻辑
 
这种方式通过组件级的事件控制,实现了类似Android事件分发的效果,同时符合HarmonyOS的声明式开发范式。
        
      
                  
                  
                  
