在HarmonyOS 的应用开发中,灵活的布局设计是提升用户体验的关键。ArkUI提供的Grid容器组件和GridItem子组件,为开发者提供了强大的网格布局能力。本文将通过实际案例,展示如何使用Grid与其他容器组件嵌套,实现复杂的混合布局。

场景一:Grid与List相互嵌套使用

方案描述

在本场景中,我们将通过Grid与List的嵌套使用,实现一个第三方服务的目录页面。该页面通过横向List布局展示服务目录,并通过滚动联动,实现与下方功能列表的二级联动效果。

核心代码

首先,我们定义一个grids类,用于存储Grid布局的数据:

enum ScrollPosition { start, center, end }

class grids {
  gridname: string = '';
  gridList: string[] = [];
  constructor(gridname: string, gridList: string[]) {
    this.gridname = gridname;
    this.gridList = gridList;
  }
}

接下来,我们创建一个TextDemo组件,用于展示Grid与List的嵌套布局:

@Entry
@Component
struct TextDemo {
  @State listPosition: number = ScrollPosition.start;
  @State scrollPosition: number = ScrollPosition.start;
  @State showTitle: boolean = false;
  @State currentYOffset: number = 0;
  private scrollerForScroll: Scroller = new Scroller();
  private scrollerForList: Scroller = new Scroller();
  private scrollerForTitle: Scroller = new Scroller();
  @State currentIndex: number = 0;
  @State list01: string[] = ['第三方服务', '查询', '财富', '转账', '贷款', '跨境金融'];
  @State gridList1: grids[] = [];

  aboutToAppear() {
    // 初始化数据
    this.gridList1.push(new grids('精选', ['理财', '基金', '外汇购入', '朝朝盈', '多宝理财', '银行卡转入', '薪福专区', '网点预约']));
  }

  @Builder myBuilder() {
    Row() {
      List({ space: 30, scroller: this.scrollerForTitle }) {
        ForEach(this.list01, (item: string, index) => {
          ListItem() {
            Column() {
              Text(item)
                .fontSize(15)
                .fontWeight(this.currentIndex == index ? FontWeight.Bold : FontWeight.Regular)
            }
            .height(35)
            .onClick(() => {
              this.scrollerForList.scrollToIndex(index);
              this.scrollerForScroll.scrollEdge(Edge.Bottom);
              this.currentIndex = index;
            })
          }
        })
      }
      .listDirection(Axis.Horizontal)
      .scrollBar(BarState.Off)
    }
    .backgroundColor(Color.White)
  }

  build() {
    Column() {
      Image($r('app.media.img'))
        .width('100%')
        .height('40')
      Column() {
        Stack({ alignContent: Alignment.Top }) {
          Scroll(this.scrollerForScroll) {
            Column() {
              GridDemo01({ gridList: new grids('精选', ['理财', '基金', '外汇购入', '朝朝盈', '多宝理财', '银行卡转入', '薪福专区', '网点预约']) })
              .backgroundColor(Color.White)
              this.myBuilder();
              Divider()
                .color('#d9d4d4')
                .strokeWidth(5)
              List({ space: 10, scroller: this.scrollerForList }) {
                ForEach(this.gridList1, (item: grids, index) => {
                  GridDemo01({ gridList: item })
                    .onVisibleAreaChange([0.8], (isVisible) => {
                      if (isVisible) {
                        this.currentIndex = index;
                        this.scrollerForTitle.scrollToIndex(this.currentIndex);
                      }
                    })
                })
              }
              .padding({ left: 10, right: 10 })
              .width("100%")
              .edgeEffect(EdgeEffect.None)
              .scrollBar(BarState.Off)
              .onScrollFrameBegin((offset: number, state: ScrollState) => {
                if (this.scrollPosition == ScrollPosition.end && (this.listPosition != ScrollPosition.start || offset > 0)) {
                  return { offsetRemain: offset };
                } else {
                  this.scrollerForScroll.scrollBy(0, offset);
                  return { offsetRemain: 0 };
                }
              })
              .width("100%")
              .height("calc(100% - 40vp)")
              .backgroundColor('#F1F3F5')
            }
          }
          .scrollBar(BarState.Off)
          .width("100%")
          .height("100%")
          .onScroll((xOffset: number, yOffset: number) => {
            this.currentYOffset = this.scrollerForScroll.currentOffset().yOffset;
            if (!((this.scrollPosition == ScrollPosition.start && yOffset < 0) || (this.scrollPosition == ScrollPosition.end && yOffset > 0))) {
              this.scrollPosition = ScrollPosition.center
            }
          })
          .onScrollEdge((side: Edge) => {
            if (side == Edge.Top) {
              this.scrollPosition = ScrollPosition.start
            } else if (side == Edge.Bottom) {
              this.scrollPosition = ScrollPosition.end
            }
          })
          .onScrollFrameBegin(offset => {
            if (this.scrollPosition == ScrollPosition.end) {
              return { offsetRemain: 0 };
            } else {
              return { offsetRemain: offset };
            }
          })
        }
        .width('100%')
        .height('100%')
        .backgroundColor(0xDCDCDC)
      }
    }
  }
}

场景二:Grid与Swiper相互嵌套使用

方案描述

在本场景中,我们将通过Grid与Swiper的嵌套使用,实现一个分页效果的布局。每一页中包含的功能菜单数量存储至分页数组gridColList1。通过多层遍历存放Grid容器组件的自定义组件来达到分页效果。

核心代码

首先,我们创建一个GridDemo组件,用于展示Grid与Swiper的嵌套布局:

import { display } from '@kit.ArkUI';

@Entry
@Component
struct GridDemo {
  @State message: string = 'Hello World';
  @State numberList: number[] = [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];
  private swiperController: SwiperController = new SwiperController();
  @State gridColList: number = 0;
  @State gridColList1: number[] = [10, 30];
  @State gridcol: number = 10;
  @State swiperDistance: number = 150;
  @State displayWidth: number = 0;

  onChage() {
    console.log('this.gridColList1:', this.gridColList1);
  }

  aboutToAppear(): void {
    const displayData: display.Display = display.getDefaultDisplaySync();
    this.displayWidth = px2vp(displayData.width);
    console.log('displayWidth:', this.displayWidth)
  }

  build() {
    Row() {
      Column() {
        Image($r('app.media.text02'))
          .width("100%")
          .height('40')
        Swiper(this.swiperController) {
          ForEach(this.gridColList1, (item: number, index) => {
            Child({ numberList: this.numberList.slice(index * this.gridColList1[index - 1], item) })
          })
        }
        .onChange(() => {
          console.log('swiperDistance=', this.swiperDistance)
          console.log('displayWidth=', this.displayWidth)
        })
        .onGestureSwipe((index: number, extraInfo: SwiperAnimationEvent) => {
          animateTo({ 
            duration: 700, 
            curve: Curve.Smooth, 
            playMode: PlayMode.Normal, 
            onFinish: () => { 
            } 
          }, () => { 
            if (extraInfo.currentOffset < 0) { 
              this.swiperDistance = 250 
            } else if (extraInfo.currentOffset > 0) { 
              this.swiperDistance = 150 
            } 
          })
        })
        .onAnimationStart((_: number, targetIndex: number) => { 
          animateTo({ 
            duration: 700, 
            curve: Curve.Smooth, 
            playMode: PlayMode.Normal, 
            onFinish: () => { 
            } 
          }, () => { 
            if (targetIndex === 0) { 
              this.swiperDistance = 150; 
            } else if (targetIndex === 1) { 
              this.swiperDistance = 250; 
            } 
          })
        })
        .height(this.swiperDistance)