HarmonyOS Tabs嵌套TabContent内部嵌套一个可拖动的组件导致冲突

这边使用Tabs组件嵌套两个TabContent页面,第一个TabContent页面内嵌套了一个可随意拖动的组件。

拖动FloatWindowView这个组件时,会触发TabContent左右滑动,导致了滑动冲突。在onTouchEvent事件内调用了event.stopPropagation(),好像并没有拦截成功。或者有没有其他办法可以解决此冲突?以下是demo代码:

import { FloatWindowView } from './FloatWindowView';

@Entry
@Component
struct Index {
  // 父组件宽度
  @State containerWidth: number = 0;
  // 父组件高度
  @State containerHeight: number = 0;

  array?: Array<string> = new Array

  aboutToAppear(): void {
    this.array?.push('page1')
    this.array?.push('page2')
  }

  build() {
    Stack() {
      Tabs() {
        ForEach(this.array, (item: string, index) => {
          // 模拟两个页面
          TabContent() {
            // 第一个
            if (index == 0) {
              Stack() {
                List().width('100%')
                  .height('100%')
                  .backgroundColor(Color.Gray)

                FloatWindowView({
                  containerWidth: this.containerWidth, containerHeight: this.containerHeight,
                  initPositionX: 0, initPositionY: 300,
                })
              }
            } else {
              // 第二个
              List().width('100%')
                .height('100%')
                .backgroundColor(Color.Black)
            }
          }
          .tabBar(item)
          .onAreaChange((oldValue: Area, newValue: Area) => {
            console.log('test55 父级onAreaChange ')
          })
        }, (item: string) => JSON.stringify(item))

      }
    }
    .height('100%')
    .width('100%')
    .onAreaChange((oldValue: Area, newValue: Area) => {
      // TODO:onAreaChange是高频回调,仅在父组件尺寸改变时获取新的父组件宽高,避免性能损耗
      if (oldValue.width !== newValue.width) {
        this.containerWidth = newValue.width as number;
      }
      if (oldValue.height !== newValue.height) {
        this.containerHeight = newValue.height as number;
      }
    })
  }
}
import { curves, display } from '@kit.ArkUI';

@Component
export struct FloatWindowView {
  // 悬浮窗相对于父组件四条边的距离,top和bottom同时设置时top生效,right和left同时设置时left生效
  @State edge: Edges = null!!;
  @Link containerWidth: number;
  @Link containerHeight: number;
  // 拖拽移动开始时悬浮窗在窗口中的坐标,每次移动回调触发时更新
  private windowStartX: number = 0;
  private windowStartY: number = 0;
  openAdsorb: boolean = true

  @Prop PAGE_PADDING: number = 0; // 页面内容内边距,用于悬浮窗位置计算
  @Prop FLOAT_WINDOW_WIDTH: number = 100; // 悬浮窗宽度
  @Prop FLOAT_WINDOW_HEIGHT: number = 30; // 悬浮窗高度
  @Prop initPositionY: number = 300; // 悬浮窗相对父容器左上角的Y坐标初始值
  @Prop initPositionX: number = 0; // 悬浮窗相对父容器左上角的X坐标初始值

  aboutToAppear(): void {
    this.edge = { top: this.initPositionY, right: this.initPositionX }
  }

  /**
   * 触摸回调,悬浮窗跟手和贴边动画
   */
  onTouchEvent(event: TouchEvent): void {
    switch (event.type) {
      case TouchType.Down: {
        // 获取拖拽开始时悬浮窗在窗口中的坐标
        this.windowStartX = event.touches[0].windowX;
        this.windowStartY = event.touches[0].windowY;
        AppStorage.set('childScrolling', true)
        console.log('test551: 子的touch')
        break;
      }
      case TouchType.Move: {
        const windowX: number = event.touches[0].windowX;
        const windowY: number = event.touches[0].windowY;
        // TODO:知识点:跟手动画,推荐使用默认参数的弹性跟手动画曲线curves.responsiveSpringMotion。
        animateTo({ curve: curves.responsiveSpringMotion() }, () => {
          // 判断当前edge中属性left和right哪个不为undefined,用于控制悬浮窗水平方向的位置
          if (this.edge.left !== undefined) {
            this.edge.left = this.edge.left as number + (windowX - this.windowStartX);
          } else {
            this.edge.right = this.edge.right as number - (windowX - this.windowStartX);
          }

          this.edge.top = this.edge.top as number + (windowY - this.windowStartY);
          this.windowStartX = windowX;
          this.windowStartY = windowY;
        })
        break;
      }
      case TouchType.Up: {
        console.log('test551: 子的touch up')
        // 计算悬浮窗中心点在父组件中水平方向的坐标
        let centerX: number;
        if (this.edge.left !== undefined) {
          centerX = this.edge.left as number + this.FLOAT_WINDOW_WIDTH / 2;
        } else {
          centerX = this.containerWidth - (this.edge.right as number) - this.FLOAT_WINDOW_WIDTH / 2;
        }
        // TODO:通过判断悬浮窗在父组件中的位置,设置悬浮窗贴边,使用curves.springMotion()弹性动画曲线,可以实现阻尼动画效果
        animateTo({ curve: curves.springMotion() }, () => {
          // 判断悬浮窗中心在水平方向是否超过父组件宽度的一半,根据结果设置靠左或靠右
          if (this.edge.top as number < this.PAGE_PADDING) {
            this.edge.top = this.PAGE_PADDING;
          } else if (this.edge.top as number >
            this.containerHeight - this.FLOAT_WINDOW_HEIGHT - this.PAGE_PADDING) {
            this.edge.top = this.containerHeight - this.FLOAT_WINDOW_HEIGHT - this.PAGE_PADDING;
          }

          let width = px2vp(display.getDefaultDisplaySync().width)

          if (this.openAdsorb) {
            if (centerX > (this.containerWidth / 2)) {
              this.edge.right = this.PAGE_PADDING;
              this.edge.left = undefined;
              this.currentAdsorbLeft = false
            } else {
              this.edge.right = undefined;
              this.edge.left = this.PAGE_PADDING;
              this.currentAdsorbLeft = true
            }
          } else {
            // 是否超出了左侧屏幕
            let overflowLeft = this.edge.right! > width - this.FLOAT_WINDOW_WIDTH
            // 是否超出了右侧屏幕
            let overflowRight = this.edge.right! < 0

            if (overflowLeft) { // 超出左边屏幕了
              this.edge.left = undefined;
              this.edge.right = width - this.FLOAT_WINDOW_WIDTH;
            } else if (overflowRight) { // 超出右边屏幕了
              this.edge.right = this.PAGE_PADDING;
              this.edge.left = undefined;
            }
          }

          AppStorage.set('childScrolling', false)
        })
        break;
      }
      default: {
        break;
      }
    }

    // 停止向父级传递事件
    event.stopPropagation()
  }

  @State currentAdsorbLeft?: boolean = false

  build() {
    Row() {

    }
    .backgroundColor(Color.Pink)
    .width(this.FLOAT_WINDOW_WIDTH)
    .height(this.FLOAT_WINDOW_HEIGHT)
    .position(this.edge)
    .onTouch((event: TouchEvent) => {
      this.onTouchEvent(event);
    })
    .onClick(() => {

    })
  }
}
HarmonyOS
18h前
浏览
收藏 0
回答 1
待解决
回答 1
按赞同
/
按时间
aquaa

可以通过给tabs添加scrollable,通过true或false来控制tabs是否可滑动,通过FloatWindowView中的触摸事件和手抬起事件来控制

示例demo如下:

import { FloatWindowView } from './FloatWindowView';

@Entry
@Component
struct Index {
  // 父组件宽度
  @State containerWidth: number = 0;
  // 父组件高度
  @State containerHeight: number = 0;
  @State canScroll: boolean = true
  array?: Array<string> = new Array

  aboutToAppear(): void {
    this.array?.push('page1')
    this.array?.push('page2')
  }
  controlScroll(){
    this.canScroll = false
  }
  outtouch(){
    this.canScroll = true
  }

  build() {
    Stack() {
      Tabs() {
        ForEach(this.array, (item: string, index) => {
          // 模拟两个页面
          TabContent() {
            // 第一个
            if (index == 0) {
              Stack() {
                List().width('100%')
                  .height('100%')
                  .backgroundColor(Color.Gray)

                FloatWindowView({
                  ontouch:()=>{this.controlScroll()},
                  outtouch:()=>{this.outtouch()},
                  containerWidth: this.containerWidth,
                  containerHeight: this.containerHeight,
                  initPositionX: 0,
                  initPositionY: 300,
                })
              }
            } else {
              // 第二个
              List().width('100%')
                .height('100%')
                .backgroundColor(Color.Black)
            }
          }
          // .scale()
          .tabBar(item)
          .onAreaChange((oldValue: Area, newValue: Area) => {
            console.log('test55 父级onAreaChange ')
          })
        }, (item: string) => JSON.stringify(item))

      }
      .scrollable(this.canScroll)
    }
    .height('100%')
    .width('100%')
    .onAreaChange((oldValue: Area, newValue: Area) => {
      // TODO:onAreaChange是高频回调,仅在父组件尺寸改变时获取新的父组件宽高,避免性能损耗
      if (oldValue.width !== newValue.width) {
        this.containerWidth = newValue.width as number;
      }
      if (oldValue.height !== newValue.height) {
        this.containerHeight = newValue.height as number;
      }
    })
  }
}
import { curves, display } from '@kit.ArkUI';

@Component
export struct FloatWindowView {
  // 悬浮窗相对于父组件四条边的距离,top和bottom同时设置时top生效,right和left同时设置时left生效
  @State edge: Edges = null!!;
  @Link containerWidth: number;
  @Link containerHeight: number;
  // 拖拽移动开始时悬浮窗在窗口中的坐标,每次移动回调触发时更新
  private windowStartX: number = 0;
  private windowStartY: number = 0;
  openAdsorb: boolean = true
  @Prop PAGE_PADDING: number = 0; // 页面内容内边距,用于悬浮窗位置计算
  @Prop FLOAT_WINDOW_WIDTH: number = 100; // 悬浮窗宽度
  @Prop FLOAT_WINDOW_HEIGHT: number = 30; // 悬浮窗高度
  @Prop initPositionY: number = 300; // 悬浮窗相对父容器左上角的Y坐标初始值
  @Prop initPositionX: number = 0; // 悬浮窗相对父容器左上角的X坐标初始值

  aboutToAppear(): void {
    this.edge = { top: this.initPositionY, right: this.initPositionX }
  }

  /**
   * 触摸回调,悬浮窗跟手和贴边动画
   */
  onTouchEvent(event: TouchEvent): void {
    switch (event.type) {
      case TouchType.Down: {
        this.ontouch()
        // 获取拖拽开始时悬浮窗在窗口中的坐标
        this.windowStartX = event.touches[0].windowX;
        this.windowStartY = event.touches[0].windowY;
        AppStorage.set('childScrolling', true)
        console.log('test551: 子的touch')
        break;
      }
      case TouchType.Move: {
        const windowX: number = event.touches[0].windowX;
        const windowY: number = event.touches[0].windowY;
        // TODO:知识点:跟手动画,推荐使用默认参数的弹性跟手动画曲线curves.responsiveSpringMotion。
        animateTo({ curve: curves.responsiveSpringMotion() }, () => {
          // 判断当前edge中属性left和right哪个不为undefined,用于控制悬浮窗水平方向的位置
          if (this.edge.left !== undefined) {
            this.edge.left = this.edge.left as number + (windowX - this.windowStartX);
          } else {
            this.edge.right = this.edge.right as number - (windowX - this.windowStartX);
          }

          this.edge.top = this.edge.top as number + (windowY - this.windowStartY);
          this.windowStartX = windowX;
          this.windowStartY = windowY;
        })
        break;
      }
      case TouchType.Up: {
        this.outtouch()
        console.log('test551: 子的touch up')
        // 计算悬浮窗中心点在父组件中水平方向的坐标
        let centerX: number;
        if (this.edge.left !== undefined) {
          centerX = this.edge.left as number + this.FLOAT_WINDOW_WIDTH / 2;
        } else {
          centerX = this.containerWidth - (this.edge.right as number) - this.FLOAT_WINDOW_WIDTH / 2;
        }
        // TODO:通过判断悬浮窗在父组件中的位置,设置悬浮窗贴边,使用curves.springMotion()弹性动画曲线,可以实现阻尼动画效果
        animateTo({ curve: curves.springMotion() }, () => {
          // 判断悬浮窗中心在水平方向是否超过父组件宽度的一半,根据结果设置靠左或靠右
          if (this.edge.top as number < this.PAGE_PADDING) {
            this.edge.top = this.PAGE_PADDING;
          } else if (this.edge.top as number >
            this.containerHeight - this.FLOAT_WINDOW_HEIGHT - this.PAGE_PADDING) {
            this.edge.top = this.containerHeight - this.FLOAT_WINDOW_HEIGHT - this.PAGE_PADDING;
          }

          let width = px2vp(display.getDefaultDisplaySync().width)

          if (this.openAdsorb) {
            if (centerX > (this.containerWidth / 2)) {
              this.edge.right = this.PAGE_PADDING;
              this.edge.left = undefined;
              this.currentAdsorbLeft = false
            } else {
              this.edge.right = undefined;
              this.edge.left = this.PAGE_PADDING;
              this.currentAdsorbLeft = true
            }
          } else {
            // 是否超出了左侧屏幕
            let overflowLeft = this.edge.right! > width - this.FLOAT_WINDOW_WIDTH
            // 是否超出了右侧屏幕
            let overflowRight = this.edge.right! < 0

            if (overflowLeft) { // 超出左边屏幕了
              this.edge.left = undefined;
              this.edge.right = width - this.FLOAT_WINDOW_WIDTH;
            } else if (overflowRight) { // 超出右边屏幕了
              this.edge.right = this.PAGE_PADDING;
              this.edge.left = undefined;
            }
          }

          AppStorage.set('childScrolling', false)
        })
        break;
      }
      default: {
        break;
      }
    }

    // 停止向父级传递事件
    event.stopPropagation()
  }

  ontouch: () => void = () => {

  }
  outtouch: () => void = () => {

  }
  @State currentAdsorbLeft?: boolean = false

  build() {
    Row() {

    }
    .backgroundColor(Color.Pink)
    .width(this.FLOAT_WINDOW_WIDTH)
    .height(this.FLOAT_WINDOW_HEIGHT)
    .position(this.edge)
    .onTouch((event: TouchEvent) => {

      this.onTouchEvent(event);
    })

    .onClick(() => {

    })
  }
}
分享
微博
QQ
微信
回复
15h前
相关问题
JS如开发一个横向拖动表格
6382浏览 • 1回复 待解决
HarmonyOS Tabs组件嵌套Tabs组件问题
849浏览 • 1回复 待解决
Tabs组件嵌套滑动组件
1469浏览 • 1回复 待解决
HarmonyOS Tabs组件嵌套滑动
437浏览 • 1回复 待解决
HarmonyOS 嵌套滚动冲突
33浏览 • 1回复 待解决
HarmonyOS Refresh组件嵌套滑动冲突问题
1026浏览 • 1回复 待解决
HarmonyOS一个自定义tabs冲突
32浏览 • 1回复 待解决
滑动嵌套事件冲突处理
289浏览 • 0回复 待解决
HarmonyOS Scroll嵌套web手势冲突
0浏览 • 0回复 待解决
HarmonyOS Tabs嵌套使用问题
28浏览 • 1回复 待解决
HarmonyOS Tabs嵌套Grid问题
83浏览 • 1回复 待解决
HarmonyOS ListItem嵌套Tabs显示不全
31浏览 • 1回复 待解决
Web和List嵌套手势冲突问题
1043浏览 • 1回复 待解决