OpenHarmony应用复杂列表用法

Ryaaaaaaan 2023-03-13 16:46:53

前言

ETS(基于TS扩展的声明式开发范式)提供了强大的组件化能力,通过组合不同的基础组件可以呈现各种不同样式和功能的UI页面,下文会介绍如何通过List、Text等基本组件实现比较复杂的列表和网格布局。

目标

  • 单项展开/收起列表
  • 滑动列表
  • 下拉刷新列表
  • 瀑布流网格

开发环境

示例开发环境:

IDE: DevEco Studio 3.0 Release(build:3.0.0.993)
SDK: Api Version9
开发模型:Stage

实现

打开DevEco Studio,创建SDK版本为API9、模型为Stage的项目。

工程目录结构如下:

img

创建主页面

在ets/pages/目录下创建index.ets文件,为了方便进行测试,在该文件中使用Button组件搭建一个简单的入口页面。

index.ets完整代码:

import router from '@ohos.router';

@Entry
@Component
struct Index {
  build() {
    Column({ space: 50 }) {
      Button("展开列表")
        .width("100%")
        .fontSize(50)
        .height("10%")
        .onClick(() => {
          router.push({ url: "pages/ExpandList" })
        })
      Button("滑动列表")
        .width("100%")
        .fontSize(50)
        .height("10%")
        .onClick(() => {
          router.push({ url: "pages/SwiperList" })
        })
      Button("刷新列表")
        .width("100%")
        .fontSize(50)
        .height("10%")
        .onClick(() => {
          router.push({ url: "pages/RefreshList" })
        })
      Button("瀑布流")
        .width("100%")
        .fontSize(50)
        .height("10%")
        .onClick(() => {
          router.push({ url: "pages/WaterFall" })
        })
    }
    .backgroundColor("#ffddd0aa")
    .height('100%')
  }
}

效果如下:

img

列表单项展开收起实现

这里以显示各省份下的地级市为例。

定义数据模型

在ets/model/do目录下创建Province.ets文件,在其中定义Province和City类分别封装省份和地级市信息。

Province.ets:

export class Province{
  id:number;
  name:string;
  cities:City[];
}

export class City{
  id:number;
  name:string;
}

在model/data目录下创建ProvinceDataModel.ets文件,定义示例中需要的测试数据。

ProvinceDataModel.ets:

import {Province} from '../do/province'


const provinces: Province[] = [
  {
    id: 0,
    name: "湖北省",
    cities: [
      { id: 0, name: "武汉" },
      { id: 1, name: "孝感" }, 
      { id: 2, name: "黄石" }, 
      { id: 3, name: "十堰" },
      { id: 4, name: "襄阳"}, 
      { id: 5, name: "宜昌" }, 
      { id: 6, name: "荆门" }, 
      { id: 7, name: "随州" }
    ]
  },
  {
    id: 1,
    name: "四川省",
    cities: [
        { id: 0, name: "成都" },
        { id: 1, name: "雅安" }, 
        { id: 2, name: "乐山" }, 
        { id: 3, name: "绵阳" }, 
        { id: 4, name: "资阳" }
    ]
  },
  {
    id: 2,
    name: "湖南省",
    cities: [
        { id: 0, name: "长沙" }, 
        { id: 1, name: "株洲" }, 
        { id: 2, name: "湘潭" }, 
        { id: 3, name: "岳阳" }, 
        { id: 4, name: "常德"}, 
        { id: 5, name: "张家界"}, 
        { id: 6, name: "衡阳" }
    ]
  },
  {
    id: 3,
    name: "河北省",
    cities: [
        { id: 0, name: "石家庄" }, 
        { id: 1, name: "唐山" }, 
        { id: 2, name: "秦皇岛"}, 
        { id: 3, name: "邯郸" }, 
        { id: 4, name: "保定" }, 
        { id: 5, name: "承德" }
    ]
  },
  {
    id: 4,
    name: "山东省",
    cities: [
        { id: 0, name: "济南" }, 
        { id: 1, name: "青岛" }, 
        { id: 2, name: "淄博" }, 
        { id: 3, name: "烟台" }, 
        { id: 4, name: "济宁" }, 
        { id: 5, name: "泰安" }
    ]
  }

]

export function initProvinceInfo(): Province[] {
  return provinces;
}

创建展开列表页面

在ets/pages/目录下创建ExpandList.ets文件,在该文件中定义ExpandList组件实现列表展开收起功能。

  • 引入测试数据
import { Province } from '../model/do/province'
import {initProvinceInfo} from '../model/data/provincedatamodel';
...
provinces: Province[]= initProvinceInfo();
  • 定义标题栏 使用Row、Image、Text组件做一个标题栏,给Image组件添加点击事件,实现点击后能够返回index页面
@Entry
@Component
struct ExpandList {
   ...
   ...
  build() {
    Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween }) {
      Row() {
        Image($r("app.media.back"))
          .height(50)
          .width(50)
          .margin({ left: 20, right: 30 })
          .onClick(() => {
            router.back({ url: "pages/index" })
          })
        Text("省份信息")
          .fontSize(40)
          .width("100%")
          .height(50)
      }
      .width("100%")
      .height(50)
      .backgroundColor(Color.Gray)
       ...
       ...
    }
     ...
  }
}

效果如下:

img

  • 定义ExpandItem组件 使用Text、Flex、Image、List等组件组合成ExpandItem子组件以显示省份及对应省份下的地级市信息。在其List组件中用ForEach 循环遍历province下的所有city数据,同时给其Flex组件添加click事件,在事件中通过改变boolean类型的状态变量的值以触发组件重新渲染来控制该List的显示以及箭头方向的改变。
@Component
struct ExpandItem {
  /**
   * 列表项展开收起的一个开关
   */
  @State
  expandStatus: boolean = false;
  province: Province;

  build() {
    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
      Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
        Text(this.province.name)
          .width(80)
          .fontSize(25)
          .height("10%")
        if (!this.expandStatus) {
          Image($r("app.media.down_arrow"))
            .height(30)
            .width(30)
            .margin({ right: 20 })
        } else {
          Image($r("app.media.up_arrow"))
            .height(30)
            .width(30)
            .margin({ right: 20 })
        }
      }
      .onClick(() => {
        //通过改变expandStatus触发当前组件重新渲染,来控制List的收起和展开
        this.expandStatus = !this.expandStatus;
      })
      .backgroundColor("#DDDDDD")

      /**
       * 当expandStatus为true时,才绘制List组件
       */
      if (this.expandStatus) {
        List() {
          ForEach(this.province.cities, (city) => {
            ListItem() {
              Text(city.name)
                .fontSize(20)
                .width("100%")
                .height("10%")
            }
            .backgroundColor("#f86d05")
          }, (city) => city.id)
        }
        .divider({ strokeWidth: 2 })
      }
    }
  }
}
  • 组合ExpandItem组件生成列表
...
List({ space: 5, initialIndex: 0 }) {
        ForEach(this.provinces, (province) => {
          ListItem() {
            ExpandItem({province:province})
          }
          .opacity(0.8)
        }, (province) => province.id)
      }
...

效果如下:

img

列表单项滑动实现

这里以展示联系人列表为例。

定义数据模型

在model/do目录下创建Person.ets文件,在其中定义Person类封装联系人相关属性。

Pserson.ets:

export class Person{
   id:number;
   name:string;
}

在同级目录下创建PersonDataModel.ets文件,定义6个联系人作为测试数据。

PersonDataModel.ets:

import {Person} from './Person'

const personArray:Person[]=[
  {id:0,name:"张三"},
  {id:1,name:"李四"},
  {id:2,name:"王五"},
  {id:3,name:"赵六"},
  {id:4,name:"钱七"},
  {id:5,name:"王八"},
]

export  function initPersonInfo():Person[]{
   return personArray;
}

构建滑动列表页面

在pages目录下创建SwiperList.ets文件,在该文件中定义SwiperList组件实现列表每项可滑动。

  • 获取测试数据 在SwiperList.ets 文件中导入 PersonDataModel.ets文件中的initPersonInfo方法并执行,将执行的结果放在Appstorage中,以便在其他自定义组件中使用测试数据。
import {initPersonInfo} from '../common/PersonDataModel'
import {Person} from '../common/Person'
import router from '@ohos.router';

AppStorage.SetOrCreate("PersonList",initPersonInfo());
  • 定义标题栏 在SwiperList.ets中使用Row、Image、Text组件做一个标题栏,给Image组件添加点击事件,使得点击后能够回到index页面
  ...
  ...
  build() {
    Flex({ direction: FlexDirection.Column }) {
      Row() {
        Image($r("app.media.back"))
          .height(50)
          .width(50)
          .margin({ left: 20, right: 30 })
          .onClick(() => {
            router.back({ url: "pages/index" })
          })
        Text("联系人列表")
          .fontSize(40)
          .width("100%")
          .height(50)
      }
      .width("100%")
      .height(50)
      .backgroundColor(Color.Gray)
      ...
      ...
    }
    .width("100%")
    .height("100%")
  }
}

效果如下:

img

  • 定义SwipItem组件 使用Text、Flex、Scroll等组件组合成SwipItem组件以显示单条联系人信息。
@Component
struct SwiperItem{

  person:Person;
  scroller:Scroller=new Scroller();
  @StorageLink("PersonList")
  personList:Person[]=AppStorage.Link("PersonList");

  /*
   *置顶当前选择的联系人
   */
  setTopped(){
    let selectedIndex = this.PersonList.indexOf(this.person);
    console.info(JSON.stringify(selectedIndex));
    let tmp = this.PersonList[0];
    this.PersonList[0] = this.person;
    this.PersonList[selectedIndex] = tmp;
    this.scroller.scrollTo({ xOffset: 0, yOffset: 0 });
  }

  /**
   * 删除当前选择联系人
   *
   */
  deleteSelectedItem(){
    let selectedIndex = this.PersonList.indexOf(this.person);
    console.info(JSON.stringify(selectedIndex));
    this.PersonList.splice(selectedIndex, 1);
  }

  build(){
    Scroll(this.scroller){
      Flex({justifyContent:FlexAlign.SpaceBetween}){
        Text(this.person.name)
          .fontSize(50)
          .width("100%")
          .height("10%")
          .backgroundColor("#dddddd")
          .onClick(()=>{
            this.scroller.scrollTo({xOffset:0,yOffset:0,animation:{duration:500,curve:Curve.Ease}})
          })

        Text("置顶")
          .fontSize(50)
          .width(100)
          .height("10%")
          .backgroundColor(Color.Brown)
          .onClick(()=>{
              this.setTopped();
          })

        Text("删除")
          .width(100)
          .fontSize(50)
          .height("10%")
          .backgroundColor(Color.Blue)
        .onClick(()=>{
           this.deleteSelectedItem();
        })
      }
    }
    .width("100%")
    .scrollable(ScrollDirection.Horizontal)
    .onScroll((xOffset,yOffset)=>{
       console.info("x,y->"+JSON.stringify(xOffset+":"+yOffset));
       console.info("current scroller:"+JSON.stringify(this.scroller.currentOffset()))
    })

    .onScrollEnd(()=>{
      //滑动结束时回调,根据滑动的距离确定滚动条最终的位置。scroller.currentOffset()可以拿到滚动条相对初始位置的偏移量
      let xOffset=this.scroller.currentOffset().xOffset;
      console.info('now scroller xOffset '+JSON.stringify(xOffset))
      if(xOffset>0&&xOffset<100){
        this.scroller.scrollTo({xOffset:0,yOffset:0});
      }
      if(xOffset>100&&xOffset<200){
        this.scroller.scrollTo({xOffset:200,yOffset:0});
      }
    })
  }
}
  • 组合SwiperItem组件 在SwiperList组件的List中循环遍历personList,组合SwiperItem组件生成滑动列表
  ...
  ...
      List(){
        ForEach(this.personList,(person)=>{
          ListItem(){
             SwiperItem({person:person})
          }
        },person=>person.id.toString())
      }
      .width("100%")
      .height("100%")
      .divider({strokeWidth:2})
   ...
   ...

效果如下:

img

列表下拉刷新实现

这里以显示商品列表为例。

定义数据模型

在model/do目录下创建Good.ets文件,在文件中定义商品类,该类包括商品的唯一id、名称、数量以及规格四个属性。

Good.ets:

export class GoodItem{
   id:number;//商品的唯一id
   name:string;//名称
   count:number;//数量
   sku:string;//规格
}

在同级目录下创建GoodDataModel.ets文件,定义12个商品数据用于测试。

GoodDataModel.ets:

import {GoodItem} from './Good'


let goodArray:GoodItem[]=[
  {id:0,name:"香蕉",count:10,sku:"箱"},
  {id:1,name:"苹果",count:8,sku:"箱"},
  {id:2,name:"雪梨",count:2,sku:"箱"},
  {id:3,name:"火龙果",count:1,sku:"箱"},
  {id:4,name:"圣女果",count:4,sku:"箱"},
  {id:5,name:"六味地黄丸",count:13,sku:"盒"},
  {id:6,name:"板蓝根",count:14,sku:"包"},
  {id:7,name:"核桃",count:17,sku:"箱"},
  {id:8,name:"红枣",count:17,sku:"框"},
  {id:9,name:"大米",count:20,sku:"袋"},
  {id:10,name:"葡萄酒",count:20,sku:"箱"},
  {id:11,name:"茅台",count:32,sku:"瓶"}
];

export  function initGoodsInfo():GoodItem[]{
  return goodArray;
}

定义刷新状态

在model/data目录下创建RefreshStatus.ets文件,定义显示刷新状态的枚举类。
RefreshStatus.ets:

export enum RefreshStatus {
   INIT="下拉刷新",//初始状态
   READY="释放立即刷新",//准备刷新
   ON_REFRESH="正在刷新",//正在刷新
   REFRESH_SUCESS="刷新成功",//刷新成功
   REFRESH_FAIL="刷新失败",//刷新失败
   REFRESH_OVER="下拉刷新",//刷新结束

}

构建刷新列表页面

在ets/pages/目录下创建RefreshList.ets文件,在该文件中定义RefreshList组件实现列表下拉刷新。

  • 定义标题栏 使用Row、Image、Text组件做一个标题栏,给Image组件添加点击事件,使得点击后能够回到index页面。
 build() {
    Flex({ direction: FlexDirection.Column }) {
       /**
       * 标题栏
       */
      Row() {
        Image($r("app.media.back"))//返回图标
          .height(50)
          .width(50)
          .margin({ left: 20, right: 30 })
          .onClick(() => {
            router.back({ url: "pages/index" })
          })
        Text("物品列表")
          .fontSize(40)
          .width("100%")
          .height("100%")
      }
      .width("100%")
      .height(50)
      .backgroundColor(Color.Gray)
   ...
   ...
   }

效果如下:

img

  • 定义状态栏组件 使用Row、Image、Text组件显示列表下拉的刷新状态。
  Row() {
          if (!this.isOnFresh) {
            Image(this.yOffset > 0 ? $r("app.media.up_arrow1") : $r("app.media.down_arrow1"))
              .width(50)
              .height(50)
              .margin({ left: 50 })
          } else {
            Image($r("app.media.loading"))
              .width(50)
              .height(50)
              .aspectRatio(1)
              .margin({ left: 50 })
              .rotate({
                x: 0,
                y: 0,
                z: 1,
                angle: this.rotateAngle,
                centerX: 75,
                centerY: 25
              })
          }
          Text(this.refreshContext)
            .fontSize(40)
            .height(80)
            .margin({ right: 40 })
        }
        .width("100%")
        .backgroundColor("#dddddd")

效果如下:

img

  • 定义商品列表 在List组件中用ForEach循环遍历所有商品数据生成商品列表
List({ scroller: this.scroller }) {
          ForEach(this.goods, (good) => {
            ListItem() {
              Flex({ justifyContent: FlexAlign.SpaceBetween }) {
                Text(good.name)
                  .fontSize(40)
                  .height("8%")
                Text(`${good.count}${good.sku}`)
                  .fontSize(40)
                  .height("8%")
                  .margin({ right: 2 })
              }.width("100%")
            }
            .backgroundColor("#f86d05")
          }, (good) => good.id)
        }

效果如下:

img

  • 组合商品列表 将商品列表和状态栏组件组合放在一个Column组件中,共同构成一个下拉列表,手指向下滑动时,滑动的是两者组成的整体。

效果如下:

img

  • 绑定Touch事件 给List组件绑定Touch事件,在回调方法中根据Touch事件类型做出不同的处理。
...
...
@Entry
@Component
struct RefreshList {
  ...
  build() {
    Flex({ direction: FlexDirection.Column }) {
      ...
      Column() {
        ...
        List({ scroller: this.scroller }) {
          ...
        }
        .edgeEffect(EdgeEffect.None) //默认为Spring
        .width("100%")
        .divider({ strokeWidth: 2 })
        .onTouch((event: TouchEvent) => {
          console.info(JSON.stringify(event.touches[0]))
          switch (event.type) {
            case TouchType.Down:
              this.firstPress = event.touches[0].y; //记录手指首次按下时的位置
              console.info("first Prsss location:" + this.firstPress);
              break;
            case TouchType.Move:
              this.lastUp = event.touches[0].y; //记录手指沿着屏幕下滑每一时刻的位置
              this.yOffset = this.lastUp - this.firstPress; //计算手指下滑的距离,也就是列表下拉的距离,并更新UI,使得列表开始向下移动,即列表会随着手指的滑动而移动
              console.info(`yOffset value:${this.yOffset}`)
              if (this.yOffset >= this.criticalDistance) {
                this.refreshContext = RefreshStatus.READY; //当下拉距离超过临界值,将刷新状态更新为"释放立即刷新",触发页面重新渲染
              } else {
                this.refreshContext = RefreshStatus.INIT; //这里有两种情况:一是下拉距离尚未超过临界值,二是已超过临界值但是又向上滑回去使得列表最终的位置还是未达到临界位置,这两种情况都不能开始刷新
              }
              if (this.yOffset < 0) {
                this.yOffset = 0;
              }
              break;
            case TouchType.Up:
              this.lastUp = event.touches[0].y; //记录手指抬起时的位置
              console.info("last up location:" + this.lastUp);
              let dis = this.lastUp - this.firstPress; //计算手指抬起时的位置和首次按下时的位置之间的距离
              console.info(`move distance:${dis}`)
              if (dis < this.criticalDistance) {
                this.yOffset = 0; //手指抬起时的下拉距离尚未超过临界值,列表返回初始位置
              } else {
                this.isOnFresh = true; //将图标更新为正在刷新状态
                this.refreshContext = RefreshStatus.ON_REFRESH; //将刷新状态更改为正在刷新
                this.refreshList(); //开始刷新列表
              }
              break;
            default:
              break;
          }
        })
      }
      .position({ x: 0, y: 50 + this.yOffset })
      .width("100%")
      .height("100%")
    }
    .width("100%")
    .height("100%")
  }
}

刷新状态流程图如下:

img

最终效果如下:

img

瀑布流实现

定义数据模型

在model/do目录下创建Rectangle.ets文件,在该文件中定义瀑布流中需要显示的元素的数据结构。
Rectangle.ets:

 

export class Rectangle{
    id:number;//矩形元素唯一id
    width:number;//矩形宽度
    height:number;//矩形高度
    color:string;//矩形背景颜色
    offset:number;//矩形在纵轴方向上相对于初始位置的偏移量
}

在model/data目录下创建RectangleDataModel.ets文件,定义一定数量的Rectangle对象作为测试数据。

import {Rectangle} from '../do/recatangle'

const rects:Rectangle[]=[];
const colors= ["#f86d05", "#cce4e4e4", "#fa2a2d", "#ff4fe363", "#cce4e4e4", "#C8303118","#419fff", "#fb4447", "#3f97e9"]
for(let i=0;i<28;i++){
   let rect:Rectangle= new Rectangle();
   rect.id=i;
   rect.width=25;
   rect.height=50+Number(100*Math.random());//每个矩形元素的高度在50~150之间随机取值
   rect.color=colors[i%colors.length];
   rect.offset=0;
   rects.push(rect);
}

export function getRectanglesInfo():Rectangle[]{
     return rects;
}

计算偏移量

在service目录下创建OffsetCalculteService.ets文件,在该文件中定义OffsetCalculateService类用于计算每一个矩形元素在布局时纵轴方向上的偏移量。

OffsetCalculteService.ets:

import {Rectangle} from '../model/do/recatangle'

export class OffsetCalculateService{

  /**
   * 每一行元素个数
   */
  private perRowCount:number=4;

  setPerRowCount(perRowCount:number){
    this.perRowCount=perRowCount;
  }
  /**
   * 计算每一个矩形框在布局时Y轴方向的偏移量
   * @param candidateRectangles
   */
  public calculateOffset(candidateRectangles:Rectangle[]){

    let preRectHeights:number[]=this.initPreRectHeight(candidateRectangles,this.perRowCount);
    this.doCalculateOffset(candidateRectangles,preRectHeights);

  }

  private initPreRectHeight(candidateRectangles:Rectangle[],rowCount:number){
    let preRectHeights:number[]=[];
    for(let i=0;i<rowCount;i++){
      preRectHeights.push(candidateRectangles[i].height);
    }
    console.info("init arr: "+JSON.stringify(preRectHeights))
    return preRectHeights;
  }

 /**
  *  每个矩形元素的偏移量=矩形元素所在行前面的所有行的元素的最大高度之和-与当前元素在同一列的前面所有行元素的高度之和
  * @param candidateRectangles
  * @param preRectHeights
  */
  private  doCalculateOffset(candidateRectangles:Rectangle[],preRectHeights:number[]){
    let maxHeight=Math.max(...preRectHeights);//代表了当前行前面的每一行元素的最大高度之和,这里先赋值第一行元素的最大高度
    console.info("init max height:"+JSON.stringify(maxHeight));
    let len:number=preRectHeights.length;
    let tmp:number[]=[]; //临时数组,用来计算当前行所有元素的最大高度,Math.max(...tmp)
    for(let i=len;i<candidateRectangles.length;i++){
      let preHeight:number=preRectHeights[i%len];//代表与当前元素在同一列的前面所有行元素的高度之和
      let curHeight:number=candidateRectangles[i].height;//当前元素的高度
      console.info("pre height:"+JSON.stringify(preHeight)+",cur height:"+JSON.stringify(curHeight))
      candidateRectangles[i].offset=-( maxHeight-preHeight);//“-”号表示是向上的偏移量
      preRectHeights[i%len]=preHeight+curHeight;//把当前元素的高度和与当前元素在同一列的前面所有行元素的高度之和合并
      tmp.push(curHeight);
      if(i%len==(len-1)&&i>len){//每排完一行计算当前行最大高度,并和前面所有行的最大高度合并
        maxHeight+=Math.max(...tmp);
        tmp.splice(0,len);//
      }
      console.info("current max height:"+maxHeight)
    }
  }
}

偏移量计算方式如图:

img

构建瀑布流页面

在pages/目录下创建WaterFall.ets文件。在该文件中导入Rectangle类、OffsetCalculateService类和getRectanglesInfo方法

  • 定义VerticalLayout组件 在WaterFall.ets文件中定义VerticalLayout组件,实现瀑布流元素按列排布。在该自定义组件中使用Flex作为根容器,并将Flex布局方式设置为列布局,使用ForEach遍历导入的数据对象。
@Component
struct  VerticalLayout{
  rectArray: Rectangle[]= getRectanglesInfo();

  build() {
    Flex({ direction: FlexDirection.Column, wrap: FlexWrap.Wrap, alignContent: FlexAlign.Start }) {
      ForEach(this.rectArray, (rect) => {
        Row() {
          Text(`第${rect.id.toString()}项`)
            .fontSize(16)
            .align(Alignment.Center)
            .width("100%")
            .height(rect.height)
            .backgroundColor(rect.color)
            .border({ width: 1 })
            .margin(1)
        }
        .width(`${rect.width}%`)
        .height(rect.height)
      }, (rect) => rect.id.toString())
    }
    .width("100%")
    .height(750)
  }
...
}
  • 定义HorizontalLayout组件 在WaterFall.ets文件中定义HorizontalLayout组件,实现瀑布流元素按列排布。首先在该组件的aboutToAppear方法中创建OffsetCalculateService对象计算每个矩形元素的偏移量,然后在build方法中使用Flex作为根容器,并将Flex布局方式设置为行布局,使用ForEach遍历计算后的数据对象。
@Component
struct HorizontalLayout{
    
  rectArray: Rectangle[]= getRectanglesInfo();

  build() {
    Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap, alignContent: FlexAlign.Start }) {
      ForEach(this.rectArray, (rect) => {
        Column() {
          Text(`第${rect.id}项`)
            .fontSize(16)
            .align(Alignment.Center)
            .width("100%")
            .height(rect.height)
            .backgroundColor(rect.color)
            .border({ width: 1 })
        }
        .width(`${rect.width}%`)
        .offset({ x: 0, y: rect.offset })
      }, rect => rect.id)

    }
    .width("100%")
    .height(750)
  }

  aboutToAppear() {
    let calculator: OffsetCalculateService = new OffsetCalculateService();
    calculator.calculateOffset(this.rectArray);
    console.info(JSON.stringify(this.rectArray))
  }
}

  • 组合布局组件 在WaterFall组件中组合上述两个子组件,并通过一个状态变量和click事件实现布局方式的切换。
@Entry
@Component
struct WaterFall {
  @State
  isVerticalLayout: boolean = false; //瀑布流中元素排列方式,true代表纵向,false代表横向

  build() {
    Column() {
      Row() {
        Image($r("app.media.back"))
          .height(50)
          .width(50)
          .margin({ left: 20, right: 30 })
          .onClick(() => {
            router.back({ url: "pages/index" })
          })
        Text("瀑布流")
          .fontSize(40)
          .width("100%")
          .height(50)
      }
      .width("100%")
      .height(50)
      .backgroundColor(Color.Gray)

      Stack({ alignContent: Alignment.Bottom }) {
        if (this. isVerticalLayout) {
          VerticalLayout()
        } else {
          HorizontalLayout()
        }
        Row() {
          Text("切换排列方式")
            .margin({ left: 20, bottom: 20, right: 30 })
            .align(Alignment.Center)
            .fontSize(40)
            .width("100%")
            .height("100%")
            .onClick(() => {
              this.isVerticalLayout = !this.isVerticalLayout;
            })
        }
        .width("100%")
        .height("10%")
        .backgroundColor(Color.Brown)
      }

    }
    .width("100%")
    .height("100%")
  }
}


效果如下:

img

参考文献

[1] 使用ArkTS语言开发(Stage模型). https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/quick-start/start-with-ets-stage.md

[2]List组件介绍. https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-list.md

[3] Grid组件介绍. https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-grid.md

[4] Scroll组件介绍. https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-container-scroll.md

...全文
3646 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

506

社区成员

发帖
与我相关
我的任务
社区描述
OpenHarmony开发者社区
其他 企业社区
社区管理员
  • csdnsqst0025
  • shewaliujingli
  • BaoWei
加入社区
  • 近7日
  • 近30日
  • 至今

试试用AI创作助手写篇文章吧