DEV Community

HarmonyOS
HarmonyOS

Posted on

How to Display 4 Elements in a 2x2 Layout per Swiper Page and Support Dynamic Pagination

Read the original article:How to Display 4 Elements in a 2x2 Layout per Swiper Page and Support Dynamic Pagination

Requirement Description

In a conference layout scenario, it is required to dynamically display participants in a 2x2 layout on each Swiper page. When the number of participants exceeds four, the layout should automatically paginate to the next Swiper page. Additionally, the total number of pages and the current page should be displayed, and page sliding should be supported.

Background Knowledge

  • The Swiper component in HarmonyOS supports page-based navigation.
  • Flex layout can be used to build a 2×2 grid.
  • LazyDataSource allows dynamic data updates with efficient rendering.
  • Swiper’s digit indicator provides current page and total page display.

Implementation Steps

To achieve this:

  1. Divide the data source into sub-arrays of four elements each.
  2. Use Swiper for page sliding.
  3. Display a 2x2 grid using Flex layout within each SwiperItem.
  4. Show the total number of pages and the current page using Swiper().indicator(Indicator.digit()).
  5. Provide dynamic updates when participants join or leave.

Code Snippet / Configuration

MeetingSwiper.ets

import { JSON } from '@kit.ArkTS'
import { LazyDataSource } from './LazyDataSource';

// Participant class
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(`The ${i} th`)
      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('Host')
                .fontSize(40)
                .width(80)
                .height(80)
                .borderRadius(40)
                .backgroundColor('#30B5C5')
            }
            .justifyContent(FlexAlign.Center)
            .width('100%')
            .height('100%')
          } else {
            Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
              ForEach(item, (param: ItemParam) => {
                Text(param.name)
                  .width('calc((100% - 40vp)/2)')
                  .height('calc((100% - 40vp)/2)')
                  .textAlign(TextAlign.Center)
                  .margin(10)
                  .backgroundColor('#30B5C5')
                  .onClick(() => {
                    this.list.splice(this.list.length - 1, 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%')
  }
}

Enter fullscreen mode Exit fullscreen mode

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 {
    return undefined;
  }

  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      this.listeners.push(listener);
    }
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener);
    if (pos >= 0) {
      this.listeners.splice(pos, 1);
    }
  }

  notifyDataReload(): void {
    this.listeners.forEach(listener => {
      listener.onDataReloaded();
    })
  }

  notifyDataAdd(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataAdd(index);
    })
  }

  notifyDataChange(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataChange(index);
    })
  }

  notifyDataDelete(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataDelete(index);
    })
  }

  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 }
    ])
  }
}
Enter fullscreen mode Exit fullscreen mode

ObservedArray.ets

@Observed
export class ObservedArray<T> extends Array<T> {
  constructor(args?: T[]) {
    if (args instanceof Array) {
      super(...args);
    } else {
      super();
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Test Results

  • Dynamic Pagination: The Swiper pages update automatically as participants join or leave.
  • Flexible Layout: Each Swiper page arranges participants in a 2x2 grid using Flex layout.
  • Page Indicator: Swiper’s digit indicator is used to show the current page and total number of pages.
  • Live Updates: Clicking an item removes it from the list and refreshes the Swiper.

Limitations or Considerations

  • If participant count is very large, performance may degrade (consider virtualized lists).
  • UI responsiveness depends on LazyDataSource efficiency.
  • Currently uses fixed 2x2 grid – not adaptive to screen orientation.
  • Needs manual refresh (resetDataArr) after data changes.

Related Documents or Links

https://developer.huawei.com/consumer/en/doc/harmonyos-references-V14/ts-container-swiper-V14?utm_source=chatgpt.com

Written by Ali Anil Toklu

Top comments (0)