如何使用Swiper实现四宫格会议页面
===================

#### 问题现象

需要实现一个动态会议的布局场景：

1.  会议参与者人数动态增加和减少，具体人数不确定。
2.  每个页面排列四个人，采用2×2的布局方式。如果人数超过四个，会自动排在下一页。
3.  支持左右滑动切换页面。
4.  页面底部显示当前页数和总页数。

#### 效果预览

| 
会议页

 | 

与会人页面1

 | 

与会人页面2

 | 

运行效果图

 |
| :-- | :-- | :-- | :-- |
| 

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/9f/v3/IkOfaxLZTei2D7XLJpw11Q/zh-cn_image_0000002658915015.png?HW-CC-KV=V1&HW-CC-Date=20260709T134046Z&HW-CC-Expire=86400&HW-CC-Sign=CD11302FE102598A5FB13E65357522E7CDF324B7F0D6C9DC67F7A76C9F4398CB "点击放大")

 | 

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/82/v3/8NKLL1xuQQaA7Acwi-QQPw/zh-cn_image_0000002628395804.png?HW-CC-KV=V1&HW-CC-Date=20260709T134046Z&HW-CC-Expire=86400&HW-CC-Sign=E7830776002DC2380C240E69A06B37A2A0F3A79E3A4A0E415E2A0C276C831CA2 "点击放大")

 | 

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/60/v3/sgzJl4XqStSWGqlGQ5oSHQ/zh-cn_image_0000002658795069.png?HW-CC-KV=V1&HW-CC-Date=20260709T134046Z&HW-CC-Expire=86400&HW-CC-Sign=2C5092019C2CCAFF8E86A8DC588DAB52FFDB79D654B374C679CDEFEA1D847E3F "点击放大")

 | 

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/8/v3/u-qpvGXFTkCTzgXDvkf1PA/zh-cn_image_0000002628555698.gif?HW-CC-KV=V1&HW-CC-Date=20260709T134046Z&HW-CC-Expire=86400&HW-CC-Sign=FE87CE1C7E5F71754A42A2765AB5D215D47259C76DF8054BDFCA135987E11DAA "点击放大")

 |

#### 背景知识

*   滑块视图容器组件[Swiper](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-swiper)能够实现子组件的滑动轮播，并且可以结合[LazyForEach](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-rendering-control-lazyforeach)懒加载优化性能。Swiper组件允许用户通过滑动手势在多个页面之间进行切换。
*   LazyForEach用于在列表中延迟加载项，以提升应用的加载速度和性能。[Flex](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-flex)组件能够使容器内的子元素在指定方向上自动对齐和均匀分布，适用于动态布局场景，可以使用Flex布局实现四宫格效果。

#### 解决方案

主要功能实现在MeetingSwiper.ets中，LazyDataSource.ets为懒加载数据通用工具类，ObservedArray.ets是一个继承自Array的类，通过new操作符创建的ObservedArray的实例可以观察到属性变化。MeetingSwiper.ets中的功能实现：

1.  将原始数据列表拆分为每4个元素一组的子数组，每个子数组代表一个页面的数据。
2.  使用Swiper组件实现页面的滑动切换，每个页面展示一个子数组的数据。
3.  使用Flex布局在每个页面内实现2×2的四宫格展示。
4.  第一个页面用空数组占位，显示会议界面。
5.  使用Swiper().indicator(Indicator.digit())实现在页面底部显示当前页数和总页数。

完整示例参考如下：

*   MeetingSwiper.ets代码示例如下：
    
    import { JSON } from '@kit.ArkTS';
    import { LazyDataSource } from './LazyDataSource';
    
    _//_ _与会人_
    class ItemParam {
      name: string \= '';
    
      constructor(name: string) {
        this.name \= name;
      }
    }
    
    @Entry
    @Component
    struct MeetingSwiper {
      private swiperController: SwiperController \= new SwiperController();
      @State private dataArr: LazyDataSource<ItemParam\[\]\> = new LazyDataSource(); _//_ _处理后的数据_
      list: ItemParam\[\] \= \[\]; _// 初始数据_
    
      aboutToAppear(): void {
       _ // 初始化数据_
        for (let i \= 1; i <\= 6; i++) {
          let param \= new ItemParam(\`第${i}个\`);
          this.list.push(param);
        }
        this.resetDataArr();
      }
    
      resetDataArr() {
        _// 将数据按四个拆分_
        let listArr: ItemParam\[\]\[\] \= \[\];
        for (let i \= 0; i < this.list.length; i += 4) {
          listArr.push(this.list.slice(i, i \+ 4));
        }
        _// 清空数据_
        this.dataArr.clear();
     _   // 添加第一屏会议界面_
        this.dataArr.pushData(\[\]);
       _ // 添加与会人_
        this.dataArr.pushDataPositionArray(1, listArr);
      }
    
      build() {
        Column() {
          Swiper(this.swiperController) {
            LazyForEach(this.dataArr, (item: ItemParam\[\], index: number) \=\> {
              if (index \=== 0) {
                Column() {
                  Text('主持')
                    .fontSize(40)
                    .width(80)
                    .height(80)
                    .borderRadius(40)
                    .backgroundColor('#f1f3f5')
                }
                .justifyContent(FlexAlign.Center)
                .width('100%')
                .height('100%')
              } else {
                Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) { _//_ _子组件多行布局_
                  ForEach(item, (param: ItemParam,itemIndex:number) \=\> {
                    Text(param.name)
                      .width('calc((100% - 40vp)/2)')
                      .height('calc((95% - 40vp)/2)')
                      .textAlign(TextAlign.Center)
                      .margin(10)
                      .backgroundColor('#f1f3f5')
                      .borderRadius(20)
                      .onClick(() \=\> {
                        _// 点击按钮，删除对应与会人，更新数据_
                        if(itemIndex \=== 0 && (((index\-1)\*4\+ itemIndex) \=== this.list.length \- 1)) {
                         _ //当点击的是当前屏最后一个按钮时，先跳转前一页，然后删除并刷新数据_
                          this.swiperController.changeIndex(index\-1);
                          setTimeout(()\=\>{
                            this.list.splice((index\-1)\*4\+ itemIndex, 1);
                            this.resetDataArr();
                          },200);
                        }else {
                          this.list.splice((index\-1)\*4\+ itemIndex, 1);
                          this.resetDataArr();
                        }
                      })
                  }, (param: ItemParam) \=\> JSON.stringify(param))
                }
              }
            }, (item: ItemParam\[\]) \=\> JSON.stringify(item))
          }
          .indicator(Indicator.digit()) _//_ _设置数字导航点样式_
          .loop(false)
          .width('100%')
          .height('100%')
        }
        .width('100%')
        .height('100%')
      }
    }
    
*   LazyDataSource.ets代码示例如下：
    
    import { ObservedArray } from './ObservedArray';
    
    class BasicDataSource<T\> implements IDataSource {
      private listeners: DataChangeListener\[\] \= \[\];
    
      public totalCount(): number {
        return 0;
      }
    
      public getData(index: number): T | undefined {
        console.info('index:',index);
        return undefined;
      }
    
      _//_ _该方法为框架侧调用，为LazyForEach组件向其数据源处添加listener监听_
      registerDataChangeListener(listener: DataChangeListener): void {
        if (this.listeners.indexOf(listener) < 0) {
          this.listeners.push(listener);
        }
      }
    
      _//_ _该方法为框架侧调用，为LazyForEach组件向其数据源处添加listener监听_
      unregisterDataChangeListener(listener: DataChangeListener): void {
        const pos \= this.listeners.indexOf(listener);
        if (pos \>\= 0) {
          this.listeners.splice(pos, 1);
        }
      }
    
      _//_ _通知LazyForEach组件需要重载所有子组件_
      notifyDataReload(): void {
        this.listeners.forEach(listener \=\> {
          listener.onDataReloaded();
        });
      }
    
      _//_ _通知LazyForEach组件需要在index对应索引处添加子组件_
      notifyDataAdd(index: number): void {
        this.listeners.forEach(listener \=\> {
          listener.onDataAdd(index);
        });
      }
    
     _ // 通知LazyForEach组件在index对应索引处数据有变化，需要重建该子组件_
      notifyDataChange(index: number): void {
        this.listeners.forEach(listener \=\> {
          listener.onDataChange(index);
        });
      }
    
     _ // 通知LazyForEach组件需要在index对应索引处删除该子组件_
      notifyDataDelete(index: number): void {
        this.listeners.forEach(listener \=\> {
          listener.onDataDelete(index);
        });
      }
    
      _// 通知LazyForEach组件将from索引和to索引处的子组件进行交换_
      notifyDataMove(from: number, to: number): void {
        this.listeners.forEach(listener \=\> {
          listener.onDataMove(from, to);
        });
      }
    
      notifyDatasetChange(operations: DataOperation\[\]): void {
        this.listeners.forEach(listener \=\> {
          listener.onDatasetChange(operations);
        });
      }
    }
    
    @Observed
    export class LazyDataSource<T\> extends BasicDataSource<T\> {
      dataArray: T\[\] \= \[\];
    
      public totalCount(): number {
        return this.dataArray.length;
      }
    
      public getData(index: number): T {
        return this.dataArray\[index\];
      }
    
      public addData(index: number, data: T): void {
        this.dataArray.splice(index, 0, data);
        this.notifyDataAdd(index);
      }
    
      public pushData(data: T): void {
        this.dataArray.push(data);
        this.notifyDataAdd(this.dataArray.length \- 1);
      }
    
      public pushArrayData(newData: ObservedArray<T\>): void {
        this.clear();
        this.dataArray.push(...newData);
        this.notifyDataReload();
      }
    
      public pushDataPositionArray(index: number, newData: ObservedArray<T\>): void {
        this.dataArray.splice(index, 0, ...newData);
        this.notifyDataReload();
      }
    
      public appendArrayData(addData: ObservedArray<T\>): void {
        this.dataArray.push(...addData);
        this.notifyDataReload();
      }
    
      public deleteData(index: number): void {
        this.dataArray.splice(index, 1);
        this.notifyDataDelete(index);
      }
    
      public getDataList(): ObservedArray<T\> {
        return this.dataArray;
      }
    
      public clear(): void {
        this.dataArray.splice(0, this.dataArray?.length);
      }
    
      public isEmpty(): boolean {
        return this.dataArray.length \=== 0;
      }
    
      public prependAllData(data: Array<T\>): void {
        this.dataArray.unshift(...data);
        this.notifyDatasetChange(\[
          { type: DataOperationType.ADD, index: -data.length, count: data.length },
          { type: DataOperationType.RELOAD }
        \]);
      }
    }
    
*   ObservedArray.ets：
    
    @Observed
    export class ObservedArray<T\> extends Array<T\> {
      constructor(args?: T\[\]) {
        if (args instanceof Array) {
          super(...args);
        } else {
          super();
        }
      }
    }