HarmonyNext双向滑动列表的实现

文章目录

概要

技术细节

小结

概要

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

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 //点击按钮,更换子组件选中的父组件序号

      }

    })

  }

}

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)

  }

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 })

        }

      })

    }

  }

小结

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

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容