二级列表联动

发布于:2024-11-04 ⋅ 阅读:(99) ⋅ 点赞:(0)

介绍

本示例主要介绍了List组件实现二级联动(Cascading List)的场景。 该场景多用于商品种类的选择、照片不同类型选择等场景。

效果图

使用说明

  1. 滑动二级列表侧控件(点击没用),一级列表随之滚动。(当最后一次触屏在一级列表,则滑动二级列表,一级列表固定不动)
  2. 点击一级列表(滑动没用),二级列表随之滚动。
  3. 点击一级列表可视区域边界时,选中类别向中间移动

实现思路

  1. 使用两个List。
  2. 一二级列表分别绑定不同的Scroller对象,一级列表(tagLists)绑定classifyScroller对象,二级列表绑定scroller对象。
  3. 维护records数组,一个item数量的前缀和,records[i]表示第i+1种类别的第一个item之前有多少个item,这个数值等于records[i]表示第i+1种类别的第一个item在itemList的下标
  4. 使用List的onTouch,.onScrollIndex组件方法,判断最后一次触屏是否在一级列表,和一级列表的可视区域
  5. 实现itemFindClassIndex(itemIndex:number),itemFindClassIndex(itemIndex:number);
  6. 点击一级列表后,通过一级列表的索引获取二级列表的索引,调用scrollToIndex方法将一二级列表滚动到指定索引值
  7. 滑动二级列表触发组件滚动事件后,获取到列表可视区域第一个item对应的索引值,通过二级列表索引获取一级列表索引,调用scrollToIndex方法将一级列表滚动到指定索引值
  8. 监听curClass变量,onClassChange点击一级列表可视区域边界时,一级列表将选中类别向中间移动

样例代码

interface IRange {
  start: number;
  end: number;
}//可视区间的开始和结尾
@Entry
@Component
struct Index {
  @State itemList:string[]=[];				// 二级列表数据
  @State classList:string[]=[];				// 一级列表数据
  @State @Watch('onClassChange') curClass:number=-1//当前类别下标
  readonly eachClassCount:number[]=[6,4,4,6,5,6,4,4,6,5];//每一个类别item的数量
  private classScroller:Scroller=new Scroller();
  private itemScroller:Scroller=new Scroller();
  private records:number[]=[];  			//一个前缀和 二级列表分组count数量
  private classVisualRange:IRange={start:0,end:0};
  private isClickClassList:boolean=false; 	//上一次点击是否点击的是类别 true:滑动二级列表,一级列表不跟着一起变化 flase:滑动二级列表,一级列表跟着一起变化

  aboutToAppear(): void {
    /*
       造数据
     */
    for(let i=0;i<10;i++){
      this.classList[this.classList.length]=`${i+1}`
      for(let j=0;j<this.eachClassCount[i];j++){
        this.itemList[this.itemList.length]=`${i+1}类 第${j+1}`
      }
    }
    this.records[0]=0;
    for(let i=1;i<=10;i++){//最后多一个,方便二级item寻找一级class
      this.records[i]=this.records[i-1]+this.eachClassCount[i-1];
    }
  }
  
  itemFindClassIndex(itemIndex:number):number{
    let classIndex:number=0;
    for(let i=0;i<10;i++){
      if(this.records[i]<=itemIndex&&itemIndex<this.records[i+1]){
        classIndex=i;
        break;
      }
    }
    return classIndex;
  }
  classFindItemIndex(classIndex:number):number{
    return this.records[classIndex];
  }
  onClassChange(){
    const start=this.classVisualRange.start,end=this.classVisualRange.end;
    if(this.curClass===start||this.curClass===start+1){
      this.classScroller.scrollToIndex(Math.max(0,this.curClass-1),true)//向上一格作为可视区域第一个
    }
    else if(this.curClass===end||this.curClass===end-1){
      this.classScroller.scrollToIndex(Math.min(10,this.curClass+1),true)//向下一格作为可视区域第一个
    }
  }

  build() {
    Row() {
      /**
       * 一级列表
       */
      List({scroller:this.classScroller,space:10, initialIndex: 0}){
        ForEach(this.classList,(classItem:string,index:number)=>{
          ListItem(){
            Text(classItem).width('100%').height('15%').backgroundColor(this.curClass===index?Color.Green:Color.Pink)
              .onClick(()=>{
                let itemIndex=this.classFindItemIndex(index);
                this.curClass=index;
                this.itemScroller.scrollToIndex(itemIndex,true)
              })
          }
        })
      }.width('30%').height('100%')
      .margin({left:20,right:20}).scrollBar(BarState.Off)
      .onTouch(()=>{
        this.isClickClassList=true;
      })
      .onScrollIndex((start,end)=>{
        this.classVisualRange.start=start;
        this.classVisualRange.end=end;
      })

      /**
       * 二级列表
       */
      List({scroller:this.itemScroller,space:10}){
        ForEach(this.itemList,(item:string,index:number)=>{
          ListItem(){
            Text(item).width('100%').height('17%').backgroundColor('#999999')
              .onClick(()=>{
                let classIndex=this.itemFindClassIndex(index);
               this.curClass=classIndex;
                this.classScroller.scrollToIndex(classIndex,true)
              })
          }
        })
      }.width('70%').height('100%').margin({left:20,right:20}).scrollBar(BarState.Off)
      .onTouch(()=>{
        this.isClickClassList=false;
      })
        // 性能知识点:onScrollIndex事件在列表滚动时频繁执行,在回调中需要尽量减少耗时和冗余操作,例如减少不必要的日志打印
      .onScrollIndex((start,end)=>{//二级列表滑动,判断一级列表是否一起滑动
        if(!this.isClickClassList){
          let classIndex=this.itemFindClassIndex(start);
          this.curClass=classIndex;
          this.classScroller.scrollToIndex(classIndex,true)
          /**
           * scrollToIndex(value: number, smooth?: boolean, align?: ScrollAlign)
           * 性能知识点:开启smooth动效时,会对经过的所有item进行加载和布局计算,当大量加载item时会导致性能问题。
           */
        }
      })
    }
    .height('100%')
    .width('100%')
  }
}

扩展

  1. 把ForEach换成LazyEach,懒加载
  2. 当种类较多时,要实现“点击一级列表可视区域边界时,选中类别向中间移动”,改进本案例会出现的问题
  3. 当使用ListItemGroup时,每一个ListItemGroup占List的一个位置,不计ListItemGroup内的ListItem数量