HarmonyOS 鸿蒙Next中pc/2in1设备实现鼠标拖动组件边缘调整宽度或高度的问题

HarmonyOS 鸿蒙Next中pc/2in1设备实现鼠标拖动组件边缘调整宽度或高度的问题 尝试写一个Table组件,实现拖动单元格边框时调整宽度或高度,尝试了下面的方法方法:

// TableComponent
// 列配置接口
import { util } from '@kit.ArkTS';

@ObservedV2
export class ColumnConfig {
  @Trace public id: string
  @Trace title: string;
  @Trace width?: number
  /**
   * 返回指定单元格是否可以拖拽改变列宽
   */
  couldDragChangeWidth?: (column: number) => boolean;
  /**
   * 返回指定单元格是否可以拖拽改变行高
   */
  couldDragChangeHeight?: (row: number) => boolean;

  constructor(title: string, width: number) {
    this.title = title;
    this.width = width;
    this.id = util.generateRandomUUID();
  }
}

@ObservedV2
class DragState {
  @Trace isDragging: false | true = false
  @Trace type: 'col' | 'row' | 'none' = 'none'
  @Trace index: number = -1
  @Trace startValue: number = 0

  constructor(isDragging: false | true, type: 'col' | 'row' | 'none', index: number, startValue: number) {
    this.isDragging = isDragging;
    this.type = type;
    this.index = index;
    this.startValue = startValue;
  }
}

@Preview
@ComponentV2
export struct TableComponent {
  // 原始数据
  @Param @Require originalData: Array<Array<Object>> = []
  // 列配置
  @Param @Require columns: Array<ColumnConfig> = []
  @BuilderParam headerCellBuilder?: (column: ColumnConfig, index: number) => void;
  @BuilderParam cellBuilder?: (value: Object, rowIndex: number, colIndex: number) => void;
  @Param @Once cellDefaultHeight: number = 50;
  private scroller: Scroller = new Scroller();
  @Local dragState: DragState = new DragState(false, 'col', -1, 0)
  private handleWidth = 10;
  // 控制手柄显示/隐藏的状态变量
  @Local private showRightHandleIndex: number = -1;

  build() {
    Column() {
      // 表头区域
      this.HeaderArea()
    }
    .height('100%')
    .width('100%')
    .backgroundColor('#F5F5F5')
  }

  // 表头区域
  @Builder
  HeaderArea() {
    Scroll(this.scroller) {
      // 表头行
      Row() {
        ForEach(this.columns, (column: ColumnConfig, index: number) => {
          // 表头标题和排序指示器
          Row() {
            if (this.headerCellBuilder) {
              this.headerCellBuilder(column, index)
            } else {
              Row() {
                Row({ space: 5 }) {
                  Text(column.title)
                    .fontSize(16)
                    .fontWeight(FontWeight.Bold)
                }
              }
              .width(this.columns[index].width || 100)
              .height(this.cellDefaultHeight)
              .padding({ left: 5, right: 5 })
              .border({
                width: {
                  left: index === 0 ? 1 : 0,
                  top: 1,
                  bottom: 1
                },
                color: '#696969'
              })
              .justifyContent(FlexAlign.SpaceBetween)
              .alignItems(VerticalAlign.Center)
              .constraintSize({ minWidth: 100 + this.handleWidth })
            }

            // 列宽拖拽手柄
            Column() {
              if (index === this.showRightHandleIndex) {
                Text('||')
                  .fontColor(Color.White)
                  .textAlign(TextAlign.Center)
                  .width('100%')
              }
            }
            .width(this.showRightHandleIndex === index ? this.handleWidth : 1)
            .height(this.cellDefaultHeight)
            .justifyContent(FlexAlign.Center)
            .alignItems(HorizontalAlign.Center)
            .backgroundColor('#696969')
            .onHover((isHover) => this.showRightHandleIndex = isHover ? index : -1)
            .onTouch((event: TouchEvent) => {
              if (event) {
                switch (event.type) {
                  case TouchType.Down:
                    // 记录触摸起始点
                    if (!this.dragState.isDragging) {
                      this.dragState.isDragging = true; // 标记开始拖动
                      this.dragState.startValue = event.touches[0].x;
                      // 拖拽开始时确保手柄可见
                      this.showRightHandleIndex = index;
                    }
                    break;
                  case TouchType.Move:
                    if (this.dragState.isDragging) {
                      // 计算相对于起始点的移动距离
                      const deltaX = event.touches[0].x - this.dragState.startValue;
                      // 可以添加一个阈值来避免微动误触发
                      const moveThreshold = 5; // 假设5vp的移动阈值
                      if (Math.abs(deltaX) > moveThreshold) {
                        // 处理拖动逻辑,例如调整列宽或行高
                        let origin = this.columns[index].width || 100
                        this.columns[index].width = origin + deltaX
                        // 更新起始点为当前点,以便下一次计算增量
                        this.dragState.startValue = event.touches[0].x;
                      }
                      this.showRightHandleIndex = index;
                    }
                    break;
                  case TouchType.Up:
                  case TouchType.Cancel:
                    // 重置拖动状态
                    this.dragState.isDragging = false;
                    this.showRightHandleIndex = -1;
                    break;
                }
              }
            })
          }
          .backgroundColor('#fbfbfb')
          .justifyContent(FlexAlign.SpaceBetween)
        }, (item: ColumnConfig, index) => item.id)
      }
      .height(this.cellDefaultHeight)
      .width('100%')
    }
    .scrollBar(BarState.Off)
    .scrollable(ScrollDirection.Horizontal)
  }
}
import { ColumnConfig, TableComponent } from "./TableComponent"

// Index
@Entry
@ComponentV2
struct Index {
  private data = [
    ['张三', 25, '开发工程师', '技术部'],
    ['李四', 30, '产品经理', '产品部'],
    ['王五', 28, 'UI设计师', '设计部'],
    ['赵六', 32, '测试工程师', '质量部'],
    ['钱七', 26, '前端开发', '技术部']
  ]
  private columns: Array<ColumnConfig> = [
    new ColumnConfig('name', 200),
    new ColumnConfig('age', 100),
    new ColumnConfig('title', 150),
    new ColumnConfig('department', 250),
  ]

  build() {
    Column() {
      // 表格组件
      TableComponent({
        originalData: this.data,
        columns: this.columns,
      })
        .height('80%')
        .width('100%')
    }
    .height('100%')
    .width('100%')
  }
}

问题:第一个单元拖拽手柄不容易触发;拖动结束时有些右侧单元格拖拽手柄会消失,拖动宽度还原时又显示出来。有没有大佬知道怎么处理?


更多关于HarmonyOS 鸿蒙Next中pc/2in1设备实现鼠标拖动组件边缘调整宽度或高度的问题的实战教程也可以访问 https://www.itying.com/category-93-b0.html

3 回复

已解决

// TableComponent
// 列配置接口
import { util } from '@kit.ArkTS';

@ObservedV2
export class ColumnConfig {
  @Trace public id: string
  @Trace title: string;
  @Trace width?: number
  /**
   * 返回指定单元格是否可以拖拽改变列宽
   */
  couldDragChangeWidth?: (column: number) => boolean;
  /**
   * 返回指定单元格是否可以拖拽改变行高
   */
  couldDragChangeHeight?: (row: number) => boolean;

  constructor(title: string, width: number) {
    this.title = title;
    this.width = width;
    this.id = util.generateRandomUUID();
  }
}

@ObservedV2
class DragState {
  @Trace isDragging: false | true = false
  @Trace type: 'col' | 'row' | 'none' = 'none'
  @Trace index: number = -1
  @Trace startValue: number = 0

  constructor(isDragging: false | true, type: 'col' | 'row' | 'none', index: number, startValue: number) {
    this.isDragging = isDragging;
    this.type = type;
    this.index = index;
    this.startValue = startValue;
  }
}

@Preview
@ComponentV2
export struct TableComponent {
  // 原始数据
  @Param @Require originalData: Array<Array<Object>> = []
  // 列配置
  @Param @Require columns: Array<ColumnConfig> = []
  @BuilderParam headerCellBuilder?: (column: ColumnConfig, index: number) => void;
  @BuilderParam cellBuilder?: (value: Object, rowIndex: number, colIndex: number) => void;
  @Param @Once cellDefaultHeight: number = 50;
  private scroller: Scroller = new Scroller();
  @Local dragState: DragState = new DragState(false, 'col', -1, 0)
  private handleWidth = 10;
  // 控制手柄显示/隐藏的状态变量
  @Local private showRightHandleIndex: number = -1;

  build() {
    Column() {
      // 表头区域
      this.HeaderArea()
    }
    .height('100%')
    .width('100%')
    .backgroundColor('#F5F5F5')
  }

  // 表头区域
  @Builder
  HeaderArea() {
    Scroll(this.scroller) {
      // 表头行
      Row() {
        ForEach(this.columns, (column: ColumnConfig, index: number) => {
          // 表头标题和排序指示器
          Row() {
            if (this.headerCellBuilder) {
              this.headerCellBuilder(column, index)
            } else {
              Row() {
                Row({ space: 5 }) {
                  Text(column.title)
                    .fontSize(16)
                    .fontWeight(FontWeight.Bold)
                }
              }
              // .width(this.columns[index].width || 100) 删除本行
              .height(this.cellDefaultHeight)
              .padding({ left: 5, right: 5 })
              .border({
                width: {
                  left: index === 0 ? 1 : 0,
                  top: 1,
                  bottom: 1
                },
                color: '#696969'
              })
              .layoutWeight(1) // 新增本行
              .justifyContent(FlexAlign.SpaceBetween)
              .alignItems(VerticalAlign.Center)
              .constraintSize({ minWidth: 100 + this.handleWidth })
            }

            // 列宽拖拽手柄
            Column() {
              if (index === this.showRightHandleIndex) {
                Text('||')
                  .fontColor(Color.White)
                  .textAlign(TextAlign.Center)
                  .width('100%')
              }
            }
            .width(this.showRightHandleIndex === index ? this.handleWidth : 1)
            .height(this.cellDefaultHeight)
            .justifyContent(FlexAlign.Center)
            .alignItems(HorizontalAlign.Center)
            .backgroundColor('#696969')
            .onHover((isHover) => this.showRightHandleIndex = isHover ? index : -1)
            .onTouch((event: TouchEvent) => {
              if (event) {
                switch (event.type) {
                  case TouchType.Down:
                    // 记录触摸起始点
                    if (!this.dragState.isDragging) {
                      this.dragState.isDragging = true; // 标记开始拖动
                      this.dragState.startValue = event.touches[0].x;
                      // 拖拽开始时确保手柄可见
                      this.showRightHandleIndex = index;
                    }
                    break;
                  case TouchType.Move:
                    if (this.dragState.isDragging) {
                      // 计算相对于起始点的移动距离
                      const deltaX = event.touches[0].x - this.dragState.startValue;
                      // 可以添加一个阈值来避免微动误触发
                      const moveThreshold = 5; // 假设5vp的移动阈值
                      if (Math.abs(deltaX) > moveThreshold) {
                        // 处理拖动逻辑,例如调整列宽或行高
                        let origin = this.columns[index].width || 100
                        this.columns[index].width = origin + deltaX
                        // 更新起始点为当前点,以便下一次计算增量
                        this.dragState.startValue = event.touches[0].x;
                      }
                      this.showRightHandleIndex = index;
                    }
                    break;
                  case TouchType.Up:
                  case TouchType.Cancel:
                    // 重置拖动状态
                    this.dragState.isDragging = false;
                    this.showRightHandleIndex = -1;
                    break;
                }
              }
            })
          }
          .width(this.columns[index].width || 100) // 自定义
          .constraintSize({ minWidth: 200, maxWidth: Number.MAX_VALUE }) // 自定义
          .backgroundColor('#fbfbfb')
          .justifyContent(FlexAlign.SpaceBetween)
        }, (item: ColumnConfig, index) => item.id)
      }
      .height(this.cellDefaultHeight)
      .width('100%')
    }
    .scrollBar(BarState.Off)
    .scrollable(ScrollDirection.Horizontal)
  }
}

更多关于HarmonyOS 鸿蒙Next中pc/2in1设备实现鼠标拖动组件边缘调整宽度或高度的问题的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS Next中,PC/2合1设备上实现鼠标拖动组件边缘调整尺寸,需使用ArkUI框架的拖拽事件处理。通过onDragStartonDragonDragEnd事件监听鼠标操作,结合PanGestureMouseEvent捕获拖动起始位置和移动距离。利用组件的widthheight属性绑定动态数据,根据拖动差值实时更新尺寸。确保事件仅在组件边缘触发,可通过命中测试或区域判断实现。

在HarmonyOS Next中实现表格列宽拖拽调整时,你遇到的问题主要源于触摸事件处理和UI状态管理的逻辑。以下是具体分析和解决方案:

问题分析:

  1. 第一个单元拖拽手柄触发困难:由于手柄宽度仅为1vp,触摸区域过小,在PC/2in1设备上鼠标操作时难以精准定位
  2. 拖拽手柄显示异常:showRightHandleIndex状态在拖拽过程中被频繁重置,导致视觉反馈不稳定

优化建议:

  1. 扩大触摸区域
// 将手柄宽度增加到更适合鼠标操作的尺寸
private handleWidth = 16; // 从10增加到16vp
  1. 优化拖拽状态管理
.onTouch((event: TouchEvent) => {
  if (!event) return;
  
  switch (event.type) {
    case TouchType.Down:
      if (!this.dragState.isDragging) {
        this.dragState.isDragging = true;
        this.dragState.startValue = event.touches[0].x;
        this.dragState.index = index; // 记录当前拖拽的列索引
      }
      break;
      
    case TouchType.Move:
      if (this.dragState.isDragging && this.dragState.index === index) {
        const deltaX = event.touches[0].x - this.dragState.startValue;
        const moveThreshold = 5;
        
        if (Math.abs(deltaX) > moveThreshold) {
          let origin = this.columns[index].width || 100;
          this.columns[index].width = Math.max(50, origin + deltaX); // 添加最小宽度限制
          this.dragState.startValue = event.touches[0].x;
        }
      }
      break;
      
    case TouchType.Up:
    case TouchType.Cancel:
      if (this.dragState.index === index) {
        this.dragState.isDragging = false;
        this.dragState.index = -1;
      }
      break;
  }
})
  1. 分离悬停和拖拽状态
// 保持悬停逻辑独立,不受拖拽影响
.onHover((isHover) => {
  if (!this.dragState.isDragging) {
    this.showRightHandleIndex = isHover ? index : -1;
  }
})
  1. 添加拖拽视觉反馈
// 在拖拽时改变手柄样式
.backgroundColor(this.dragState.isDragging && this.dragState.index === index ? 
  '#1890FF' : '#696969')

这些调整能显著改善拖拽体验,确保在PC/2in1设备上鼠标操作时获得流畅的列宽调整效果。关键是要明确区分悬停状态和拖拽状态的管理,避免状态冲突导致的显示异常。

回到顶部