HarmonyNext双向滑动列表的实现 原创

zhouchy
发布于 2025-3-8 21:39
1495浏览
0收藏


 文章目录

概要

支持横向、纵向双向滑动的列表控件。实现思路是:

1、在封装的控件内实现首行布局,第一列为固定列,不可滑动,其它列通过Scroll组件包裹,实现横向滑动。

2、数据列通过List组件包裹,实现纵向滑动。数据通过LazyForEach实现懒加载,行布局第一列为固定列,不可滑动,其它列通过Scroll组件包裹,实现横向滑动。为了APP可以自定义数据列样式,因此通过定义Builder参数,由调用者动态传入,在调用方实现数据列的布局。

技术细节

1、定义表头单元格布局

/**
 * 表头组件
 */
@Component
struct itemRank {
  //当前列对应的下标
  private thisColumnIndex: number = 0;
  //当前列名称
  private thisColumnName: string = '';
  //图标宽度
  private iconSize = 15;
  //排序状态图标
  private rankIcon: ResourceStr[] = [$r('app.media.icon_multi_list_rank_down'), $r('app.media.icon_multi_list_rank_up'),
    $r('app.media.icon_multi_list_rank_nor')]
  //点击列对应的下标
  @Link clickedColumnIndex: number;
  //排序状态下标
  @Link rankSelect: number;
  //列宽
  @Prop itemWidth: ResourceStr | number;
  @Prop itemHeight: ResourceStr | number;
  // 字号
  @Prop fontSize: ResourceStr | number

  build() {
    Row() {
      Text(`${this.thisColumnName}`)
        .fontSize(this.fontSize)
        .textAlign(TextAlign.End)
        .layoutWeight(1)
        .height(this.itemHeight)

      if (this.thisColumnIndex === this.clickedColumnIndex) {
        //点击的为自己,自己开始切换图片
        Image(this.rankIcon[this.rankSelect]).height(this.iconSize).margin({ left: 3 })
      } else {
        //点击的不是自己,显示默认图片
        Image(this.rankIcon[2]).height(this.iconSize).margin({ left: 3 })
      }
    }
    .width(this.itemWidth)
    .height(this.itemHeight)
    .justifyContent(FlexAlign.End)
    .alignItems(VerticalAlign.Center)
    .onClick(() => {
      //点击的为自己,自己开始切换图片
      if (this.thisColumnIndex === this.clickedColumnIndex) {
        this.rankSelect = (this.rankSelect + 1) % this.rankIcon.length
      } else {
        this.rankSelect = 0 // 点击按钮的时候,其他状态的按钮在恢复默认图片的同时,rankSelect 也要恢复为0
        this.clickedColumnIndex = this.thisColumnIndex //点击按钮,更换子组件选中的父组件序号
      }
    })
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.

2、定义表头行

//表头布局
  @Builder
  headerScroll() {
    Row() {
      Text(this.fixColumn.name)
        .width(100)
        .height(this.itemHeight)
        .fontSize(this.fontSize)
        .textAlign(TextAlign.Start)
        .margin({left: $r('app.float.multi_list_margin_left')})

      // 右侧可横向滚动列
      Scroll(this.headerRowScroller) {
        Row() {
          ForEach(this.scrollColumns, (item: ColumnField, index: number) => {
            if (index == this.scrollColumns.length - 1) {
              itemRank({
                thisColumnName: item.name,
                thisColumnIndex: index + 1,
                itemWidth: this.itemWidth,
                itemHeight: this.itemHeight,
                fontSize: this.fontSize,
                clickedColumnIndex: this.clickedColumnIndex,
                rankSelect: this.rankSelect
              })
                .margin({ right: $r('app.float.multi_list_margin_right') })
                .height(this.itemHeight)
            } else {
              itemRank({
                thisColumnName: item.name,
                thisColumnIndex: index + 1,
                itemWidth: this.itemWidth,
                itemHeight: this.itemHeight,
                fontSize: this.fontSize,
                clickedColumnIndex: this.clickedColumnIndex,
                rankSelect: this.rankSelect
              })
                .height(this.itemHeight)
            }
          }, (item: ColumnField) => JSON.stringify(item))
        }
        .height(this.itemHeight)
      }
      .layoutWeight(1)
      .height(this.itemHeight)
      .scrollable(ScrollDirection.Horizontal)
      .scrollBar(BarState.Off)
      .edgeEffect(EdgeEffect.None)
      .onWillScroll((xOffset: number, yOffset: number, scrollState: ScrollState, scrollSource: ScrollSource) => {
        if (scrollSource == ScrollSource.SCROLLER || scrollSource == ScrollSource.SCROLLER_ANIMATION) {
          return
        }
        this.listRowScroller.forEach((scroller) => {
          scroller.scrollBy(xOffset, 0)
        })
      })
    }
    .width('100%')
    .height(this.itemHeight)
    .backgroundColor(Color.White)
  }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.


3、构造数据列表

build() {
    Column() {
      this.headerScroll()
      List({ scroller: this.listScroller }) {
        LazyForEach(this.dataSource, (item: IMultiItem, position: number) => {
          ListItem() {
            if (item.itemType == 'data') {
              Row() {
                this.fixColumnBuilder(item)

                // 右侧可横向滚动列
                Scroll(this.listRowScroller[position]) {
                  Column() {
                    this.scrollColumnBuilder(item, position, this.scrollColumns)
                  }
                  .height('100%')
                }
                .layoutWeight(1)
                .height('100%')
                .scrollable(ScrollDirection.Horizontal)
                .scrollBar(BarState.Off)
                .edgeEffect(EdgeEffect.None)
                .onAttach(() => {
                  // 每次刷新数据时,右侧每一行的Scroll要和头部的Scroller同步
                  for (let index = this.startRowIndex, length = this.endRowIndex; index <= length; index++) {
                    this.listRowScroller[index]?.scrollTo({
                      xOffset: this.headerRowScroller.currentOffset().xOffset,
                      yOffset: 0
                    })
                  }
                })
                .onWillScroll((x: number, y: number, state: ScrollState, source: ScrollSource) => {
                  if (source == ScrollSource.SCROLLER || source == ScrollSource.SCROLLER_ANIMATION) {
                    return
                  }
                  this.headerRowScroller.scrollBy(x, 0)
                  for (let index = this.startRowIndex, length = this.endRowIndex; index <= length; index++) {
                    if (index != position) {
                      this.listRowScroller[index]?.scrollBy(x, 0)
                    }
                  }
                  this.scrollX = this.listRowScroller[position]?.currentOffset().xOffset
                })
              }
              .width('100%')
              .height(this.itemHeight)
              .onClick((event) => {
                if (this.onItemClick) {
                  this.onItemClick(position)
                }
              })
            } else {
              this.scrollColumnBuilder(item, position, this.scrollColumns)
            }
          }
        }, (item: object, index: number) => `${index}_${JSON.stringify(item)}`)
      }
      .divider({
        strokeWidth: 0.5,
        startMargin: 0,
        endMargin: 0,
        color: $r('app.color.multi_list_divider')
      })
      .nestedScroll({
        scrollForward: NestedScrollMode.PARENT_FIRST,
        scrollBackward: NestedScrollMode.SELF_FIRST,
      })
      .onScrollIndex((start: number, end: number) => {
        this.startRowIndex = start;
        this.endRowIndex = end;
      })
      .scrollBar(BarState.Off)
      .width('100%')
      .height('100%')
      .edgeEffect(EdgeEffect.None)
      .onDidScroll(() => {
        for (let index = this.startRowIndex, length = this.endRowIndex; index <= length; index++) {
          this.listRowScroller[index].scrollTo({ xOffset: this.scrollX, yOffset: 0 })
        }
      })
    }
  }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.

小结

双向滑动列表的实现,需要使用复杂的手势处理。感兴趣的朋友可以参考​​@sunshine/multilist · git_zhaoyang/MultiList - 码云 - 开源中国​


©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2025-3-14 20:25:24修改
收藏
回复
举报


回复
    相关推荐