《HarmonyOS ArkUI 组件进阶:复杂布局与交互效果实现》
头像 li159 2025-11-26 23:02:46    发布
14958 浏览 428 点赞 0 收藏


前言

ArkUI 作为 HarmonyOS 应用开发的核心 UI 框架,凭借声明式语法、组件化架构和高效渲染能力,成为构建复杂界面的关键工具。但在实际开发中,开发者常面临多维度布局适配交互逻辑复杂组件复用性低等问题。本教程聚焦 ArkUI 组件的进阶用法,从布局技巧、交互增强到自定义组件开发,结合电商商品详情页实战案例,系统解决复杂场景下的开发难题,同时提供性能优化方案,帮助开发者打造高效、流畅的鸿蒙应用界面。

第一章 基础布局组件升级用法:突破布局瓶颈

1.1 Flex/Grid 嵌套布局:应对多维度界面结构

1.1.1 Flex 布局高级嵌套:实现复杂分区界面

Flex 布局是 ArkUI 中最常用的线性布局方式,通过嵌套可实现 “整体 - 局部” 分层结构。例如电商首页的 “顶部导航 + 分类栏 + 商品列表 + 底部 Tab” 布局:

typescript

运行


@Entry
@Component
struct HomePage {
  build() {
    // 外层垂直Flex:整体页面结构
    Flex({ direction: FlexDirection.Column }) {
      // 顶部导航栏(水平Flex)
      Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
        Text("商城首页").fontSize(20).fontWeight(FontWeight.Bold);
        Button("我的").width(60).height(30);
      }.width('100%').height(50).backgroundColor('#f5f5f5');

      // 分类栏(水平Flex+滚动)
      Scroll({ direction: ScrollDirection.Horizontal }) {
        Flex({ justifyContent: FlexAlign.SpaceAround }) {
          ['推荐', '数码', '服装', '家居', '食品'].forEach((item) => {
            Text(item).width(60).textAlign(TextAlign.Center);
          });
        }.width('100%').padding(10);
      }.height(60);

      // 商品列表(垂直Flex+网格嵌套)
      Flex({ direction: FlexDirection.Column }) {
        Grid() {
          ForEach([1, 2, 3, 4, 5, 6], (index) => {
            GridItem() {
              // 商品卡片(垂直Flex)
              Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) {
                Image($r('app.media.product')).width(100).height(100);
                Text(`商品${index}`).marginTop(5);
                Text(`¥${99 + index}`).fontColor('#ff4400').marginTop(5);
              }.padding(10).backgroundColor('#fff').borderRadius(8);
            });
          });
        }
        .columnsTemplate('1fr 1fr 1fr') // 3列网格
        .columnsGap(10)
        .rowsGap(10)
        .width('100%')
        .padding(10);
      }.flexGrow(1); // 占满剩余空间

      // 底部Tab栏(水平Flex)
      Flex({ justifyContent: FlexAlign.SpaceAround }) {
        ['首页', '分类', '购物车', '我的'].forEach((item) => {
          Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) {
            Image($r(`app.media.icon_${item}`)).width(24).height(24);
            Text(item).fontSize(12).marginTop(2);
          });
        });
      }.width('100%').height(60).backgroundColor('#f5f5f5');
    }.width('100%').height('100%').backgroundColor('#f9f9f9');
  }
}



关键技巧
  • 外层 Flex 控制页面整体流向,内层通过 Flex/Grid 实现局部分区;
  • 使用flexGrow让中间内容区域自适应剩余空间;
  • 结合Scroll组件实现横向 / 纵向滚动,避免内容溢出。

1.1.2 Grid 布局进阶:动态网格与自适应行列

Grid 布局适用于二维网格场景(如商品列表、相册),通过动态配置行列属性实现自适应:

typescript

运行


@Component
struct AdaptiveGridPage {
  // 模拟不同屏幕宽度下的列数
  @State columnCount: number = 3;

  // 监听屏幕宽度变化
  onPageShow() {
    const windowWidth = getWindowWidth();
    this.columnCount = windowWidth > 720 ? 4 : windowWidth > 480 ? 3 : 2;
  }

  build() {
    Grid() {
      ForEach([...Array(8).keys()], (index) => {
        GridItem() {
          Text(`Item ${index + 1}`)
            .width('100%')
            .height(80)
            .textAlign(TextAlign.Center)
            .backgroundColor('#fff')
            .borderRadius(8);
        };
      });
    }
    .columnsTemplate(`repeat(${this.columnCount}, 1fr)`) // 动态列数
    .columnsGap(10)
    .rowsGap(10)
    .width('100%')
    .padding(10)
    .onPageShow(() => this.onPageShow());
  }
}

// 获取屏幕宽度工具函数
function getWindowWidth(): number {
  const windowClass = getContext().windowManager.getCurrentWindow();
  const windowSize = windowClass.getWindowProperties().windowRect;
  return windowSize.width;
}



核心优势
  • 通过repeat()函数动态生成列模板,适配不同屏幕尺寸;
  • 结合生命周期或尺寸监听函数,实现布局实时响应。

1.2 自适应屏幕适配技巧:多设备统一体验

1.2.1 尺寸单位选择:vp/fp 的灵活运用

  • vp(虚拟像素):根据屏幕密度自动缩放,适用于组件尺寸、间距;
  • fp(字体像素):跟随系统字体大小设置,保证文字可读性。
  • typescript
  • 运行
// 错误示范:使用固定px导致不同屏幕显示不一致
Text("固定尺寸").width(200).height(50);

// 正确示范:使用vp/fp适配
Text("自适应尺寸").width(200).height(50) // 单位默认vp
Text("自适应字体").fontSize(16) // 单位默认fp


1.2.2 媒体查询:针对不同设备类型定制布局

通过@Media装饰器实现设备差异化布局:

typescript

运行


@Entry
@Component
struct MediaQueryPage {
  @State isPad: boolean = false;

  build() {
    Column() {
      if (this.isPad) {
        // 平板布局:左右分栏
        Flex() {
          Text("侧边栏").width(200).height('100%').backgroundColor('#f5f5f5');
          Text("主内容").flexGrow(1).height('100%').backgroundColor('#fff');
        }.width('100%').height('100%');
      } else {
        // 手机布局:垂直堆叠
        Column() {
          Text("顶部栏").width('100%').height(50).backgroundColor('#f5f5f5');
          Text("主内容").flexGrow(1).width('100%').backgroundColor('#fff');
        }.width('100%').height('100%');
      }
    }
    .onPageShow(() => {
      this.isPad = getDeviceType() === 'tablet';
    });
  }
}

// 获取设备类型工具函数
function getDeviceType(): string {
  return getContext().config.deviceType; // 返回phone/tablet/watch等
}


第二章 交互组件高级应用:增强用户体验

2.1 List 组件进阶:下拉刷新 + 上拉加载

List 是 ArkUI 中实现滚动列表的核心组件,结合Refresh和LazyForEach可实现分页加载:

typescript

运行


@Entry
@Component
struct ProductListPage {
  @State data: number[] = [...Array(20).keys()]; // 初始数据
  @State isRefreshing: boolean = false;
  @State hasMore: boolean = true; // 是否有更多数据

  // 下拉刷新逻辑
  async onRefresh() {
    this.isRefreshing = true;
    // 模拟接口请求
    await new Promise(resolve => setTimeout(resolve, 1500));
    this.data = [...Array(20).keys()]; // 重置数据
    this.hasMore = true;
    this.isRefreshing = false;
  }

  // 上拉加载逻辑
  async loadMore() {
    if (!this.hasMore || this.isRefreshing) return;
    // 模拟接口请求
    await new Promise(resolve => setTimeout(resolve, 1000));
    const newData = this.data.length + 10 > 50 ? [] : [...Array(10).keys()].map(i => this.data.length + i);
    if (newData.length === 0) {
      this.hasMore = false;
      return;
    }
    this.data = [...this.data, ...newData];
  }

  build() {
    Refresh({ refreshing: this.isRefreshing, onRefresh: this.onRefresh.bind(this) }) {
      List() {
        LazyForEach(new MyDataSource(this.data), (item) => {
          ListItem() {
            Text(`商品 ${item + 1}`)
              .width('100%')
              .height(80)
              .lineHeight(80)
              .textAlign(TextAlign.Center)
              .backgroundColor('#fff')
              .marginBottom(10);
          }
        });

        // 加载更多提示
        ListItem() {
          Text(this.hasMore ? "加载中..." : "没有更多数据了")
            .width('100%')
            .height(50)
            .textAlign(TextAlign.Center)
            .fontSize(14)
            .fontColor('#999');
        }
      }
      .onReachEnd(() => this.loadMore()) // 上拉触底触发
      .width('100%')
      .padding(10)
      .backgroundColor('#f9f9f9');
    }
    .width('100%')
    .height('100%');
  }
}

// 自定义数据源(优化性能)
class MyDataSource implements IDataSource {
  private data: number[];

  constructor(data: number[]) {
    this.data = data;
  }

  totalCount(): number {
    return this.data.length;
  }

  getData(index: number): number {
    return this.data[index];
  }

  registerDataChangeListener(_: DataChangeListener): void {}
  unregisterDataChangeListener(_: DataChangeListener): void {}
}



关键优化
  • 使用LazyForEach替代ForEach,实现列表项懒加载,减少内存占用;
  • 通过onReachEnd监听列表触底,触发加载更多逻辑;
  • 加入状态控制(isRefreshing/hasMore),避免重复请求。

2.2 弹窗组件自定义样式:从默认到个性化

ArkUI 提供AlertDialog/CustomDialog等弹窗组件,通过自定义实现差异化 UI:

2.2.1 自定义确认弹窗

typescript

运行


@Entry
@Component
struct CustomDialogPage {
  @State showDialog: boolean = false;

  build() {
    Column() {
      Button("打开自定义弹窗")
        .onClick(() => this.showDialog = true)
        .margin(20);

      // 自定义弹窗
      CustomDialog({
        controller: this.dialogController,
        builder: () => {
          Column() {
            Text("提示")
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
              .marginBottom(15);

            Text("确定要删除这条数据吗?")
              .fontSize(14)
              .marginBottom(20);

            Flex({ justifyContent: FlexAlign.End }) {
              Button("取消")
                .width(80)
                .height(35)
                .backgroundColor('#f5f5f5')
                .fontColor('#333')
                .onClick(() => this.dialogController.close());

              Button("确定")
                .width(80)
                .height(35)
                .backgroundColor('#ff4400')
                .marginLeft(10)
                .onClick(() => {
                  // 执行删除逻辑
                  this.dialogController.close();
                });
            }
          }
          .width(300)
          .padding(20)
          .backgroundColor('#fff')
          .borderRadius(12);
        },
        cancel: () => console.log("弹窗取消"),
        autoCancel: true // 点击空白处关闭
      })
      .visible(this.showDialog);
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center);
  }

  // 弹窗控制器
  dialogController: CustomDialogController = new CustomDialogController({
    builder: this.build.bind(this),
    autoCancel: true,
    alignment: DialogAlignment.Center,
    offset: { dx: 0, dy: 0 },
    customStyle: true // 启用自定义样式
  });
}


2.2.2 底部弹出菜单(ActionSheet)

typescript

运行


@Component
struct ActionSheetPage {
  @State showSheet: boolean = false;

  build() {
    Column() {
      Button("打开底部菜单")
        .onClick(() => this.showSheet = true)
        .margin(20);

      if (this.showSheet) {
        Stack() {
          // 遮罩层
          Column().width('100%').height('100%').backgroundColor('rgba(0,0,0,0.5)')
            .onClick(() => this.showSheet = false);

          // 菜单内容
          Column() {
            Text("操作选项")
              .width('100%')
              .height(50)
              .lineHeight(50)
              .textAlign(TextAlign.Center)
              .borderBottom({ width: 1, color: '#eee' });

            ['分享', '收藏', '举报'].forEach((item) => {
              Text(item)
                .width('100%')
                .height(50)
                .lineHeight(50)
                .textAlign(TextAlign.Center)
                .onClick(() => {
                  console.log(`选择了${item}`);
                  this.showSheet = false;
                });
            });

            Text("取消")
              .width('100%')
              .height(50)
              .lineHeight(50)
              .textAlign(TextAlign.Center)
              .backgroundColor('#f5f5f5')
              .marginTop(10)
              .onClick(() => this.showSheet = false);
          }
          .width('100%')
          .backgroundColor('#fff')
          .borderRadius({ topLeft: 12, topRight: 12 })
          .position({ bottom: 0 });
        }
        .width('100%')
        .height('100%');
      }
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center);
  }
}


第三章 自定义组件开发:封装复用与扩展

3.1 通用组件封装:以 “商品卡片” 为例

封装可复用的自定义组件,需考虑参数传递事件回调样式定制

typescript

运行


// 商品卡片组件(ProductCard.ets)
@Component
export struct ProductCard {
  // 组件参数(支持外部传入)
  @Prop image: Resource = $r('app.media.default_product');
  @Prop title: string = "默认商品";
  @Prop price: number = 0;
  @Prop tag?: string; // 可选标签(如“新品”“热销”)

  // 事件回调(点击事件)
  onClick: () => void = () => {};

  build() {
    Column() {
      // 商品图片(带标签)
      Stack() {
        Image(this.image)
          .width('100%')
          .height(180)
          .objectFit(ImageFit.Cover)
          .borderRadius({ topLeft: 8, topRight: 8 });

        if (this.tag) {
          Text(this.tag)
            .fontSize(12)
            .backgroundColor('#ff4400')
            .fontColor('#fff')
            .padding({ left: 5, right: 5, top: 2, bottom: 2 })
            .borderRadius(4)
            .position({ top: 10, left: 10 });
        }
      }

      // 商品信息
      Column() {
        Text(this.title)
          .fontSize(14)
          .fontWeight(FontWeight.Medium)
          .maxLines(2)
          .textOverflow({ overflow: TextOverflow.Ellipsis });

        Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
          Text(`¥${this.price.toFixed(2)}`)
            .fontSize(16)
            .fontColor('#ff4400')
            .fontWeight(FontWeight.Bold);

          Button("加入购物车")
            .width(80)
            .height(28)
            .fontSize(12)
            .backgroundColor('#ff4400')
            .fontColor('#fff');
        }.marginTop(8);
      }
      .padding(10)
      .width('100%')
      .backgroundColor('#fff')
      .borderRadius({ bottomLeft: 8, bottomRight: 8 });
    }
    .width('100%')
    .backgroundColor('#fff')
    .borderRadius(8)
    .shadow({ radius: 4, color: 'rgba(0,0,0,0.1)', offsetX: 0, offsetY: 2 })
    .onClick(this.onClick);
  }
}



使用自定义组件:





typescript


运行




@Entry
@Component
struct ProductList {
  build() {
    Grid() {
      ForEach([
        { image: $r('app.media.phone'), title: "鸿蒙智能手机", price: 2999, tag: "热销" },
        { image: $r('app.media.tablet'), title: "鸿蒙平板", price: 1999, tag: "新品" },
        { image: $r('app.media.watch'), title: "鸿蒙智能手表", price: 1299 }
      ], (item) => {
        GridItem() {
          ProductCard({
            image: item.image,
            title: item.title,
            price: item.price,
            tag: item.tag,
            onClick: () => console.log(`点击了${item.title}`)
          });
        };
      });
    }
    .columnsTemplate('1fr 1fr')
    .columnsGap(10)
    .rowsGap(10)
    .width('100%')
    .padding(10);
  }
}


3.2 组件通信:父子 / 兄弟组件数据传递

3.2.1 父子组件通信:@Prop/@Link/@Provide/@Consume

  • @Prop:父组件向子组件单向传递数据(子组件无法修改父组件数据);
  • @Link:父子组件双向数据绑定(子组件修改会同步到父组件);
  • @Provide/@Consume:跨层级组件通信(避免多层级 props 传递)。
  • 示例:@Link 双向绑定
  • typescript
  • 运行
// 子组件
@Component
struct CounterChild {
  @Link count: number;

  build() {
    Button(`子组件:${this.count}`)
      .onClick(() => this.count++);
  }
}

// 父组件
@Entry
@Component
struct CounterParent {
  @State count: number = 0;

  build() {
    Column() {
      Text(`父组件:${this.count}`).fontSize(20).margin(20);
      CounterChild({ count: $count }); // $表示双向绑定
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center);
  }
}


3.2.2 兄弟组件通信:全局事件总线

通过EventHub实现非父子组件通信:

typescript

运行


// 全局事件总线工具类
class EventBus {
  private static instance: EventBus;
  private eventHub: EventHub;

  private constructor() {
    this.eventHub = getContext().eventHub;
  }

  static getInstance(): EventBus {
    if (!EventBus.instance) {
      EventBus.instance = new EventBus();
    }
    return EventBus.instance;
  }

  // 发送事件
  emit(eventName: string, data?: any) {
    this.eventHub.emit(eventName, data);
  }

  // 订阅事件
  on(eventName: string, callback: (...args: any[]) => void) {
    this.eventHub.on(eventName, callback);
  }

  // 取消订阅
  off(eventName: string, callback?: (...args: any[]) => void) {
    this.eventHub.off(eventName, callback);
  }
}

// 组件A(发送事件)
@Component
struct ComponentA {
  build() {
    Button("发送消息")
      .onClick(() => {
        EventBus.getInstance().emit("message", { text: "Hello from ComponentA" });
      });
  }
}

// 组件B(接收事件)
@Component
struct ComponentB {
  @State message: string = "";

  aboutToAppear() {
    EventBus.getInstance().on("message", (data) => {
      this.message = data.text;
    });
  }

  aboutToDisappear() {
    EventBus.getInstance().off("message"); // 取消订阅,避免内存泄漏
  }

  build() {
    Text(`接收的消息:${this.message}`).fontSize(16);
  }
}


第四章 实战案例:电商商品详情页实现

4.1 页面整体结构:滑动布局与分区设计

商品详情页通常包含 “轮播图、商品信息、规格选择、详情内容” 等模块,通过Scroll+Stack实现复杂布局:

typescript

运行


@Entry
@Component
struct ProductDetailPage {
  @State currentTab: number = 0; // 当前选中的标签页
  @State selectedSpec: string = "默认规格"; // 选中的商品规格
  @State showSpecModal: boolean = false; // 是否显示规格选择弹窗

  build() {
    Stack() {
      // 主滚动内容
      Scroll() {
        Column() {
          // 1. 商品轮播图
          Swiper() {
            ForEach([1, 2, 3], (index) => {
              Image($r(`app.media.product_${index}`))
                .width('100%')
                .height(300)
                .objectFit(ImageFit.Cover);
            });
          }
          .height(300)
          .autoPlay(true)
          .indicator(true);

          // 2. 商品基本信息
          Column() {
            Text("鸿蒙智能手表 运动版")
              .fontSize(20)
              .fontWeight(FontWeight.Bold)
              .marginBottom(5);

            Text("¥1299")
              .fontSize(24)
              .fontColor('#ff4400')
              .fontWeight(FontWeight.Bold)
              .marginBottom(10);

            Text("【新品上市】支持鸿蒙4.0,超长续航,心率监测")
              .fontSize(14)
              .fontColor('#666')
              .marginBottom(15);

            // 规格选择栏
            Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
              Text("选择规格:")
                .fontSize(14);

              Text(this.selectedSpec)
                .fontSize(14)
                .fontColor('#ff4400')
                .onClick(() => this.showSpecModal = true);
            }
            .width('100%')
            .padding(10)
            .backgroundColor('#f9f9f9')
            .borderRadius(8)
            .marginBottom(15);
          }
          .width('100%')
          .padding(15)
          .backgroundColor('#fff');

          // 3. 标签页切换(商品详情/规格参数/用户评价)
          Column() {
            // 标签栏
            Flex() {
              ['商品详情', '规格参数', '用户评价'].forEach((item, index) => {
                Text(item)
                  .fontSize(16)
                  .fontWeight(this.currentTab === index ? FontWeight.Bold : FontWeight.Normal)
                  .fontColor(this.currentTab === index ? '#ff4400' : '#333')
                  .width('100%')
                  .textAlign(TextAlign.Center)
                  .padding(10)
                  .onClick(() => this.currentTab = index);

                if (index < 2) {
                  Divider().vertical().height(20).color('#eee');
                }
              });
            }
            .width('100%')
            .backgroundColor('#fff');

            // 标签内容
            Stack() {
              if (this.currentTab === 0) {
                // 商品详情
                Column() {
                  Image($r('app.media.detail_1')).width('100%');
                  Image($r('app.media.detail_2')).width('100%');
                  Image($r('app.media.detail_3')).width('100%');
                }
              } else if (this.currentTab === 1) {
                // 规格参数
                Column() {
                  ['品牌:鸿蒙', '型号:Watch Pro', '续航:7天', '防水:50米'].forEach((item) => {
                    Flex({ justifyContent: FlexAlign.SpaceBetween }) {
                      Text(item.split(':')[0]).fontColor('#666');
                      Text(item.split(':')[1]).fontColor('#333');
                    }
                    .width('100%')
                    .padding(10)
                    .borderBottom({ width: 1, color: '#eee' });
                  });
                }.padding(10);
              } else {
                // 用户评价
                Column() {
                  ForEach([
                    { name: "用户A", content: "续航超给力,推荐购买!", rating: 5 },
                    { name: "用户B", content: "操作流畅,鸿蒙生态体验好", rating: 4 }
                  ], (item) => {
                    Column() {
                      Flex({ justifyContent: FlexAlign.SpaceBetween }) {
                        Text(item.name).fontWeight(FontWeight.Bold);
                        Rating({ rating: item.rating, indicator: true }).width(80);
                      }.marginBottom(5);

                      Text(item.content).fontSize(14).fontColor('#666');
                    }
                    .width('100%')
                    .padding(10)
                    .borderBottom({ width: 1, color: '#eee' });
                  });
                }.padding(10);
              }
            }
            .width('100%')
            .backgroundColor('#fff')
            .padding(10);
          }
          .width('100%')
          .marginTop(10);
        }
        .width('100%');
      }
      .width('100%')
      .height('100%');

      // 底部操作栏(悬浮固定)
      Stack() {
        Flex({ justifyContent: FlexAlign.SpaceAround, alignItems: ItemAlign.Center }) {
          Button("加入购物车")
            .width('40%')
            .height(45)
            .backgroundColor('#fff')
            .fontColor('#ff4400')
            .border({ width: 1, color: '#ff4400' });

          Button("立即购买")
            .width('40%')
            .height(45)
            .backgroundColor('#ff4400')
            .fontColor('#fff');
        }
        .width('100%')
        .height(60)
        .backgroundColor('#fff')
        .position({ bottom: 0 });
      }

      // 规格选择弹窗
      if (this.showSpecModal) {
        Stack() {
          // 遮罩层
          Column().width('100%').height('100%').backgroundColor('rgba(0,0,0,0.5)')
            .onClick(() => this.showSpecModal = false);

          // 规格内容
          Column() {
            Text("选择商品规格")
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
              .marginBottom(20);

            // 规格选项
            Wrap({ spacing: 10 }) {
              ['黑色-46mm', '白色-46mm', '黑色-42mm', '白色-42mm'].forEach((spec) => {
                Text(spec)
                  .padding({ left: 15, right: 15, top: 8, bottom: 8 })
                  .backgroundColor(this.selectedSpec === spec ? '#ff4400' : '#f5f5f5')
                  .fontColor(this.selectedSpec === spec ? '#fff' : '#333')
                  .borderRadius(20)
                  .onClick(() => this.selectedSpec = spec);
              });
            }
            .marginBottom(30);

            Button("确定")
              .width('100%')
              .height(45)
              .backgroundColor('#ff4400')
              .fontColor('#fff')
              .onClick(() => this.showSpecModal = false);
          }
          .width('80%')
          .padding(20)
          .backgroundColor('#fff')
          .borderRadius(12)
          .position({ top: '30%' });
        }
        .width('100%')
        .height('100%');
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f9f9f9');
  }
}


4.2 交互增强:滑动动效与手势处理

4.2.1 轮播图缩放动效

通过Swiper的onChange事件结合动画,实现轮播图切换时的缩放效果:

typescript

运行


Swiper({ onChange: (index) => this.currentIndex = index }) {
  ForEach([1, 2, 3], (item, index) => {
    Image($r(`app.media.product_${item}`))
      .width('100%')
      .height(300)
      .objectFit(ImageFit.Cover)
      .scale({ x: this.currentIndex === index ? 1 : 0.95, y: this.currentIndex === index ? 1 : 0.95 })
      .animation({ duration: 300, curve: Curve.EaseInOut });
  });
}


4.2.2 下拉放大图片

监听滚动事件,实现下拉时轮播图放大效果:

typescript

运行


@State scrollOffset: number = 0; // 滚动偏移量

build() {
  Scroll({ onScroll: (offset) => this.scrollOffset = offset.y }) {
    // 轮播图(根据滚动偏移量放大)
    Image($r('app.media.product_1'))
      .width('100%')
      .height(300 + Math.max(0, -this.scrollOffset) / 2) // 下拉时高度增加
      .objectFit(ImageFit.Cover);
    // ...其他内容
  }
}


第五章 组件性能优化:避免卡顿与过度渲染

5.1 减少渲染开销:状态管理与条件渲染

5.1.1 合理使用 @State/@Link:避免不必要的更新

  • 仅将需要响应式更新的数据声明为@State/@Link
  • 复杂对象拆分多个状态变量,避免整体更新导致的全量渲染。
  • 反面示例
  • typescript
  • 运行
@State product: { name: string, price: number, stock: number } = {
  name: "鸿蒙手机",
  price: 2999,
  stock: 100
};

// 修改stock时,整个product对象更新,导致依赖product的组件全部重渲染
updateStock() {
  this.product = { ...this.product, stock: this.product.stock - 1 };
}



优化示例:





typescript


运行




@State productName: string = "鸿蒙手机";
@State productPrice: number = 2999;
@State productStock: number = 100;

// 仅更新stock状态,依赖stock的组件才会重渲染
updateStock() {
  this.productStock -= 1;
}


5.1.2 条件渲染优化:使用if替代visibility

visibility: Hidden会保留组件布局空间且仍会渲染,而if条件不满足时组件不会渲染:

typescript

运行


// 低效:组件仍会渲染,仅隐藏
Text("暂无数据").visibility(this.data.length === 0 ? Visibility.Visible : Visibility.Hidden);

// 高效:条件不满足时组件不渲染
if (this.data.length === 0) {
  Text("暂无数据");
}


5.2 列表性能优化:懒加载与数据复用

5.2.1 强制使用 LazyForEach

对于长列表(超过 20 项),必须使用LazyForEach替代ForEach,实现列表项按需创建和回收:

typescript

运行


List() {
  LazyForEach(new LargeDataSource(), (item) => {
    ListItem() {
      Text(`Item ${item}`).width('100%').height(80).textAlign(TextAlign.Center);
    }
  });
}


5.2.2 列表项高度固定

为 List 设置estimatedItemSize或固定列表项高度,减少布局计算开销:

typescript

运行


List() {
  // 列表项
}
.estimatedItemSize(80) // 预估列表项高度


5.3 图片优化:懒加载与格式选择

5.3.1 图片懒加载

结合IntersectionObserver实现图片懒加载(进入可视区域后加载):

typescript

运行


@Component
struct LazyImage {
  @Prop src: string;
  @State loaded: boolean = false;

  aboutToAppear() {
    // 创建交叉观察者
    const observer = getContext().intersectionObserver.createObserver({
      thresholds: [0.1] // 元素10%进入可视区域时触发
    });

    observer.observe(this.id, (result) => {
      if (result.intersectionRatio > 0) {
        this.loaded = true;
        observer.disconnect(); // 取消观察
      }
    });
  }

  build() {
    Image(this.loaded ? this.src : $r('app.media.placeholder')) // 占位图
      .width('100%')
      .height(200)
      .id(`lazy-image-${this.src}`);
  }
}


5.3.2 选择高效图片格式

优先使用 WebP 格式(体积比 PNG 小 30%-50%),并根据设备分辨率加载对应尺寸图片:

typescript

运行


Image($r('app.media.product.webp')) // WebP格式图片
  .width('100%')
  .height(200);


结语

ArkUI 组件的进阶应用核心在于布局分层设计交互逻辑解耦性能优化意识。通过 Flex/Grid 嵌套实现复杂界面结构,结合自定义组件提升复用性,再通过懒加载、状态管理优化性能,可高效构建流畅的鸿蒙应用界面。


©本站发布的所有内容,包括但不限于文字、图片、音频、视频、图表、标志、标识、广告、商标、商号、域名、软件、程序等,除特别标明外,均来源于网络或用户投稿,版权归原作者或原出处所有。我们致力于保护原作者版权,若涉及版权问题,请及时联系我们进行处理。
分类
HarmonyOS
地址:北京市朝阳区北三环东路三元桥曙光西里甲1号第三置业A座1508室 商务内容合作QQ:2291221 电话:13391790444或(010)62178877
版权所有:电脑商情信息服务集团 北京赢邦策略咨询有限责任公司
声明:本媒体部分图片、文章来源于网络,版权归原作者所有,我司致力于保护作者版权,如有侵权,请与我司联系删除
京ICP备:2022009079号-2
京公网安备:11010502051901号
ICP证:京B2-20230255