HarmonyOS开发:Render Profiler与渲染性能分析

举报
Jack20 发表于 2026/06/23 19:59:35 2026/06/23
【摘要】 HarmonyOS开发:Render Profiler与渲染性能分析📌 核心要点:Render Profiler将每一帧的渲染过程拆解为布局→绘制→合成三个阶段,帮你精确定位UI卡顿的元凶,让60fps不再是奢望。 一、背景与动机“这个列表滑动怎么一卡一卡的?”——如果你是HarmonyOS开发者,这句话你一定不陌生。UI卡顿是用户感知最强烈的性能问题。CPU占用高一点用户可能感觉不到,...

HarmonyOS开发:Render Profiler与渲染性能分析

📌 核心要点:Render Profiler将每一帧的渲染过程拆解为布局→绘制→合成三个阶段,帮你精确定位UI卡顿的元凶,让60fps不再是奢望。


一、背景与动机

“这个列表滑动怎么一卡一卡的?”——如果你是HarmonyOS开发者,这句话你一定不陌生。

UI卡顿是用户感知最强烈的性能问题。CPU占用高一点用户可能感觉不到,内存泄漏一时半会儿也不会暴露,但滑动列表时的每一帧卡顿,用户的眼睛都看得清清楚楚。

为什么UI会卡?一句话解释:某一帧的渲染时间超过了帧预算(16.67ms@60fps)

但知道"超了"没用,你得知道超在哪了——是布局计算太复杂?还是绘制内容太多?还是合成阶段出了问题?不同原因对应完全不同的优化方向:布局问题要简化组件树,绘制问题要减少重绘区域,合成问题要减少图层数量。

Render Profiler就是来帮你"拆帧"的。它把每一帧的渲染过程像慢动作回放一样展开,告诉你每一毫秒都花在了哪个阶段。有了这个信息,你就能精准施策,而不是盲目优化。


二、核心原理

2.1 渲染管线三阶段

HarmonyOS的渲染管线分为三个阶段,每个阶段都有可能成为性能瓶颈:

graph TD
    A[VSync信号到达]:::primary --> B[布局阶段 Layout]:::info
    B --> B1[测量 Measure]:::info
    B --> B2[排列 Arrange]:::info
    B1 --> C[绘制阶段 Draw]:::warning
    B2 --> C
    C --> C1[构建DisplayList]:::warning
    C --> C2[光栅化 Rasterize]:::warning
    C1 --> D[合成阶段 Composite]:::error
    C2 --> D
    D --> D1[图层合并]:::error
    D --> D2[上屏显示]:::error
    D2 --> E{帧耗时 < 16.67ms?}:::warning
    E -->|| F[流畅渲染 ✅]:::primary
    E -->|| G[掉帧 Jank ❌]:::error

    classDef primary fill:#4CAF50,stroke:#388E3C,color:#fff
    classDef warning fill:#FF9800,stroke:#F57C00,color:#fff
    classDef error fill:#F44336,stroke:#D32F2F,color:#fff
    classDef info fill:#2196F3,stroke:#1976D2,color:#fff

三阶段详解

阶段 做什么 常见瓶颈 优化方向
Layout(布局) 计算每个组件的位置和大小 嵌套层级深、频繁重新布局 减少嵌套、使用LazyForEach、避免布局抖动
Draw(绘制) 将组件绘制到画布上 绘制区域大、频繁重绘、阴影/模糊效果 减少重绘范围、避免过度使用特效
Composite(合成) 将多个图层合并输出到屏幕 图层过多、Overdraw严重 减少图层、裁剪不可见区域

2.2 帧率与帧预算

目标帧率 帧预算 适用场景
60fps 16.67ms/帧 普通应用、列表滑动
90fps 11.11ms/帧 高刷新率设备
120fps 8.33ms/帧 游戏、动画密集型应用

关键概念

  • Jank(卡顿帧):耗时超过帧预算的帧,用户可感知
  • Big Jank(严重卡顿):耗时超过3倍帧预算(>50ms@60fps),用户非常不适
  • Frozen Frame(冻结帧):耗时超过700ms的帧,用户感觉应用"卡死了"

2.3 Overdraw(过度绘制)

Overdraw是指同一个像素被绘制了多次。比如一个半透明背景覆盖在不透明背景上,底层背景的绘制就是浪费。

Overdraw的严重程度用颜色表示:

  • 无色:没有Overdraw,理想状态
  • 蓝色:1次Overdraw,可接受
  • 绿色:2次Overdraw,需要关注
  • 粉色:3次Overdraw,需要优化
  • 红色:4次及以上Overdraw,必须优化

三、代码实战

3.1 基础用法:帧渲染耗时分析

import { hiTraceMeter } from '@kit.PerformanceAnalysisKit';

@Entry
@Component
struct RenderProfilerDemo {
  @State dataList: string[] = [];
  @State scrollIndex: number = 0;

  aboutToAppear(): void {
    // 初始化1000条数据
    this.dataList = Array.from({ length: 1000 }, (_, i) =>
      `列表项 ${i} - ${Math.random().toString(36).substring(2, 10)}`
    );
  }

  build() {
    Column() {
      Text('Render Profiler 帧耗时分析')
        .fontSize(22)
        .fontWeight(FontWeight.Bold)
        .padding(16)

      // ❌ 问题列表:ForEach加载全部数据
      List({ space: 4 }) {
        ForEach(this.dataList, (item: string, index: number) => {
          ListItem() {
            this.ListItemContent(item, index)
          }
        }, (item: string, index: number) => `${index}`)
      }
      .width('100%')
      .layoutWeight(1)
      .cachedCount(5) // 缓存5个ListItem
    }
    .width('100%')
    .height('100%')
  }

  @Builder
  ListItemContent(item: string, index: number) {
    // ❌ 问题:复杂的列表项布局,嵌套层级深
    Column() {
      Row() {
        Column() {
          Row() {
            Text(item)
              .fontSize(16)
              .fontWeight(FontWeight.Medium)
              .layoutWeight(1)
            Text(`#${index}`)
              .fontSize(12)
              .fontColor('#999999')
          }
          .width('100%')

          Text('这是一段描述文字,用于测试布局性能')
            .fontSize(13)
            .fontColor('#666666')
            .margin({ top: 4 })
        }
        .layoutWeight(1)

        // ❌ 问题:阴影效果增加绘制开销
        Text('详情')
          .fontSize(14)
          .fontColor('#FFFFFF')
          .backgroundColor('#2196F3')
          .borderRadius(16)
          .padding({ left: 12, right: 12, top: 6, bottom: 6 })
          .shadow({ radius: 4, color: '#332196F3', offsetY: 2 })
      }
      .width('100%')
    }
    .width('100%')
    .padding(12)
    .backgroundColor(index % 2 === 0 ? '#FFFFFF' : '#F8F8F8')
    .borderRadius(8)
    .margin({ left: 16, right: 16 })
  }
}

Render Profiler分析步骤

  1. 打开Profiler面板,选择Render Profiler
  2. 点击"Record"开始采集
  3. 在App中快速滑动列表
  4. 停止采集后,查看帧耗时时间线

你会看到时间线上有很多红色标记——这些就是掉帧(Jank)。点击某个红色帧,查看三阶段耗时分布,通常你会发现Layout阶段占比最高——因为ForEach会一次性创建所有列表项的布局。

3.2 进阶用法:布局/绘制/合成阶段耗时分析与Overdraw检测

import { hiTraceMeter } from '@kit.PerformanceAnalysisKit';

// 复杂卡片数据
interface CardData {
  id: number;
  title: string;
  subtitle: string;
  tag: string;
  imageUrl: string;
  likes: number;
}

@Entry
@Component
struct RenderAnalysisDemo {
  @State cards: CardData[] = [];
  @State isOptimized: boolean = false;

  aboutToAppear(): void {
    this.cards = Array.from({ length: 200 }, (_, i) => ({
      id: i,
      title: `卡片标题 ${i}`,
      subtitle: `这是第${i}张卡片的描述信息`,
      tag: ['推荐', '热门', '新品', '限时'][i % 4],
      imageUrl: `https://picsum.photos/200/200?random=${i}`,
      likes: Math.floor(Math.random() * 1000)
    }));
  }

  build() {
    Column() {
      // 切换按钮
      Row() {
        Button(this.isOptimized ? '优化版 ✅' : '问题版 ❌')
          .backgroundColor(this.isOptimized ? '#4CAF50' : '#F44336')
          .onClick(() => {
            this.isOptimized = !this.isOptimized;
          })

        Text('滑动列表观察Render Profiler')
          .fontSize(12)
          .fontColor('#999999')
          .margin({ left: 12 })
      }
      .width('100%')
      .padding(16)
      .justifyContent(FlexAlign.Center)

      if (this.isOptimized) {
        // ✅ 优化版:LazyForEach + 简化布局
        this.OptimizedList()
      } else {
        // ❌ 问题版:ForEach + 复杂布局 + Overdraw
        this.ProblemList()
      }
    }
    .width('100%')
    .height('100%')
  }

  // ❌ 问题版列表
  @Builder
  ProblemList() {
    List({ space: 12 }) {
      ForEach(this.cards, (card: CardData) => {
        ListItem() {
          // ❌ 问题1:多层嵌套导致布局计算复杂
          Column() {
            // ❌ 问题2:半透明背景覆盖在不透明背景上 → Overdraw
            Column() {
              Row() {
                Column() {
                  Text(card.title)
                    .fontSize(18)
                    .fontWeight(FontWeight.Bold)
                  Text(card.subtitle)
                    .fontSize(13)
                    .fontColor('#666666')
                    .maxLines(2)
                    .textOverflow({ overflow: TextOverflow.Ellipsis })
                }
                .layoutWeight(1)

                // ❌ 问题3:图片无裁剪,绘制区域大
                Image(card.imageUrl)
                  .width(80)
                  .height(80)
                  .borderRadius(8)
              }

              // ❌ 问题4:每个卡片都有阴影和模糊 → 绘制开销大
              Row() {
                Text(card.tag)
                  .fontSize(12)
                  .fontColor('#FFFFFF')
                  .backgroundColor('#FF5722')
                  .borderRadius(10)
                  .padding({ left: 8, right: 8, top: 2, bottom: 2 })

                Text(`${card.likes}`)
                  .fontSize(12)
                  .fontColor('#999999')
                  .margin({ left: 8 })
              }
              .margin({ top: 8 })
            }
            .padding(12)
            // ❌ 问题5:半透明背景 → Overdraw
            .backgroundColor('#0A000000')
          }
          .padding(12)
          .backgroundColor(Color.White)
          .borderRadius(12)
          // ❌ 问题6:卡片阴影 → 绘制开销
          .shadow({ radius: 8, color: '#1A000000', offsetY: 4 })
          .margin({ left: 16, right: 16 })
        }
      }, (card: CardData) => card.id.toString())
    }
    .width('100%')
    .layoutWeight(1)
  }

  // ✅ 优化版列表
  @Builder
  OptimizedList() {
    List({ space: 8 }) {
      // ✅ 优化1:使用LazyForEach,按需加载
      LazyForEach(new CardDataSource(this.cards), (card: CardData) => {
        ListItem() {
          // ✅ 优化2:扁平化布局,减少嵌套
          Column() {
            Text(card.title)
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
              .maxLines(1)
              .textOverflow({ overflow: TextOverflow.Ellipsis })

            Text(card.subtitle)
              .fontSize(13)
              .fontColor('#666666')
              .maxLines(1)
              .textOverflow({ overflow: TextOverflow.Ellipsis })
              .margin({ top: 4 })

            // ✅ 优化3:简化标签和点赞信息
            Row() {
              Text(card.tag)
                .fontSize(11)
                .fontColor('#FF5722')

              Text(`${card.likes}`)
                .fontSize(11)
                .fontColor('#999999')
                .margin({ left: 12 })
            }
            .margin({ top: 6 })
          }
          .width('100%')
          .padding(12)
          .backgroundColor(Color.White)
          .borderRadius(8)
          // ✅ 优化4:去掉阴影,减少绘制开销
          // ✅ 优化5:去掉半透明背景,消除Overdraw
        }
      }, (card: CardData) => card.id.toString())
    }
    .width('100%')
    .layoutWeight(1)
    .cachedCount(3)
  }
}

// LazyForEach需要的数据源
class CardDataSource implements IDataSource {
  private cards: CardData[] = [];
  private listeners: DataChangeListener[] = [];

  constructor(cards: CardData[]) {
    this.cards = cards;
  }

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

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

  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);
    }
  }
}

对比分析

在Render Profiler中分别测试两个版本:

指标 问题版 优化版 改善
平均帧耗时 ~25ms ~10ms 60%↓
Jank帧数/100帧 ~30 ~5 83%↓
Layout占比 55% 30% 25%↓
Draw占比 35% 60% 比例升但绝对值降
Overdraw级别 粉色/红色 蓝色/无色 显著改善

3.3 完整示例:渲染性能优化实战

import { hiTraceMeter } from '@kit.PerformanceAnalysisKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG = 'RenderOptimization';
const DOMAIN = 0xFF00;

@Entry
@Component
struct RenderOptimizationPage {
  @State items: number[] = [];
  @State animationValue: number = 0;
  @State isAnimating: boolean = false;
  private animationTimer: number = -1;

  aboutToAppear(): void {
    this.items = Array.from({ length: 500 }, (_, i) => i);
  }

  aboutToDisappear(): void {
    // 清理定时器
    if (this.animationTimer !== -1) {
      clearInterval(this.animationTimer);
    }
  }

  build() {
    Scroll() {
      Column({ space: 16 }) {
        Text('渲染性能优化实战')
          .fontSize(22)
          .fontWeight(FontWeight.Bold)

        // 动画测试区
        Row() {
          Button(this.isAnimating ? '停止动画' : '启动动画')
            .onClick(() => this.toggleAnimation())
        }

        // 动画元素
        Row() {
          Circle()
            .width(60)
            .height(60)
            .fill('#2196F3')
            .offset({ x: this.animationValue })
        }
        .width('100%')
        .height(80)
        .justifyContent(FlexAlign.Center)

        // ✅ 优化1:使用LazyForEach的长列表
        Text('长列表(LazyForEach)')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .width('100%')
          .margin({ top: 8 })

        List({ space: 4 }) {
          LazyForEach(new NumberDataSource(this.items), (item: number) => {
            ListItem() {
              Row() {
                Text(`项目 ${item}`)
                  .fontSize(14)
                  .layoutWeight(1)

                // ✅ 优化2:避免在列表项中使用复杂特效
                Text(`${item * 2}`)
                  .fontSize(14)
                  .fontColor('#2196F3')
              }
              .width('100%')
              .padding({ left: 16, right: 16, top: 10, bottom: 10 })
              .backgroundColor(item % 2 === 0 ? '#FFFFFF' : '#F5F5F5')
            }
          }, (item: number) => item.toString())
        }
        .width('100%')
        .height(400)
        .cachedCount(5)

        // ✅ 优化3:条件渲染代替透明度控制
        Text('条件渲染示例')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .width('100%')

        this.ConditionalRenderSection()
      }
      .padding(16)
    }
    .width('100%')
    .height('100%')
  }

  // ✅ 优化:条件渲染 vs 透明度控制
  @Builder
  ConditionalRenderSection() {
    Column() {
      // ❌ 错误方式:用opacity=0隐藏,元素仍然参与布局和绘制
      // Text('隐藏内容').opacity(0)

      // ✅ 正确方式:条件渲染,不参与布局和绘制
      if (this.isAnimating) {
        Text('动画运行中...')
          .fontSize(14)
          .fontColor('#4CAF50')
          .padding(8)
          .backgroundColor('#E8F5E9')
          .borderRadius(4)
      } else {
        Text('动画已停止')
          .fontSize(14)
          .fontColor('#F44336')
          .padding(8)
          .backgroundColor('#FFEBEE')
          .borderRadius(4)
      }
    }
    .width('100%')
  }

  // 动画控制
  private toggleAnimation(): void {
    this.isAnimating = !this.isAnimating;

    if (this.isAnimating) {
      hiTraceMeter.startTrace('animation', 1);
      let value = 0;
      let direction = 1;
      // ❌ 问题:用setInterval做动画,不与VSync同步
      this.animationTimer = setInterval(() => {
        value += direction * 3;
        if (value > 200 || value < -200) {
          direction = -direction;
        }
        this.animationValue = value;
      }, 16) as unknown as number;
    } else {
      if (this.animationTimer !== -1) {
        clearInterval(this.animationTimer);
        this.animationTimer = -1;
      }
      hiTraceMeter.finishTrace('animation', 1);
    }
  }
}

// 数据源
class NumberDataSource implements IDataSource {
  private items: number[] = [];
  private listeners: DataChangeListener[] = [];

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

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

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

  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);
    }
  }
}

Render Profiler中的关键观察

  1. 列表滑动:观察帧耗时时间线,LazyForEach版本在滑动时帧耗时基本在8~12ms,而ForEach版本会出现20~40ms的Jank帧
  2. 动画运行setInterval驱动的动画会导致帧率不稳定——因为setInterval的回调时机和VSync信号不同步,有些帧会被跳过
  3. 条件渲染:使用if条件渲染时,隐藏的元素不参与布局和绘制;而使用opacity(0)时,元素仍然被完整布局和绘制,只是不可见

四、踩坑与注意事项

坑点1:ForEach vs LazyForEach的性能差异被低估

很多开发者觉得"数据量不大,用ForEach就行了"。但问题不只是数据量——ForEach会在列表创建时一次性构建所有ListItem的组件树,即使大部分ListItem不在屏幕上。这意味着:

  • 初始加载时间长(白屏时间)
  • 滑动时内存占用高(所有ListItem都在内存中)
  • 数据更新时重新渲染范围大

建议:只要列表项超过20个,就用LazyForEach。它的按需加载机制能显著减少初始渲染时间和内存占用。

坑点2:@State变量粒度过粗导致过度重绘

@State变量的修改会触发组件的重新渲染。如果你的@State是一个大对象,修改其中任何一个字段都会导致整个组件树重新渲染。

// ❌ 问题:修改likes会导致整个卡片重新渲染
@State card: CardData = { id: 1, title: '标题', likes: 0 };

// 点赞时
this.card.likes++; // 整个card对象被标记为dirty,关联的所有UI都重新渲染

// ✅ 优化:将频繁变化的字段拆分为独立的@State
@State cardTitle: string = '标题';
@State cardLikes: number = 0;

// 点赞时
this.cardLikes++; // 只有显示likes的Text组件重新渲染

坑点3:布局抖动(Layout Thrashing)

布局抖动是指在同一个帧内多次触发布局计算。比如在循环中先读取布局属性(触发布局计算),再修改布局属性(使布局失效),再读取(再次触发布局计算)……这种"读-写-读-写"的模式会导致同一帧内布局被计算多次。

在HarmonyOS中的表现:在onAreaChange回调中修改影响布局的属性,可能导致布局抖动。

解决方案:批量修改布局属性,避免在回调中逐个修改。或者使用animateTo将多次布局修改合并到一次动画帧中。

坑点4:阴影和模糊是渲染性能杀手

shadow()blur()效果在视觉上很好看,但它们是渲染管线中最昂贵的操作之一。每个带阴影的组件都需要额外的绘制通道,模糊效果更是需要对周围像素进行多次采样。

建议

  • 列表项中不要使用阴影和模糊
  • 如果必须使用阴影,用9-patch图片代替实时阴影
  • 模糊效果尽量用静态背景代替实时模糊

坑点5:图片解码阻塞渲染

大图片的解码是在主线程上进行的(如果没有使用异步解码),一张4K图片的解码可能需要几十毫秒,直接导致当前帧超时。

解决方案

  • 使用适当尺寸的图片,不要加载超过显示尺寸的图片
  • 使用Image组件的autoResize属性(如果可用)自动缩放
  • 预加载图片到缓存中,避免在滑动时解码

坑点6:Render Profiler在模拟器上数据不准确

模拟器使用软件渲染,和真机的GPU渲染有本质区别。模拟器上的帧耗时数据完全不能代表真机表现。

铁律:渲染性能测试必须在真机上进行,模拟器只适合功能验证。

坑点7:cachedCount设置不当

cachedCount是List组件的一个重要属性,它控制屏幕外预渲染的ListItem数量。设置太小会导致滑动时出现空白区域,设置太大会增加内存和CPU开销。

建议

  • 简单列表项:cachedCount(2)即可
  • 复杂列表项(含图片):cachedCount(3~5)
  • 超大列表项(含视频/大图):cachedCount(1),配合图片预加载

五、HarmonyOS 6适配说明

API差异

API HarmonyOS 5.0 HarmonyOS 6.0 迁移建议
帧耗时分析 仅帧级别总耗时 拆分Layout/Draw/Composite子阶段 利用子阶段数据精准定位瓶颈
Overdraw检测 需要开发者选项开启 Profiler内置Overdraw热力图 直接在Profiler中查看Overdraw
渲染帧回放 不支持 新增帧回放功能,可逐帧查看渲染结果 用帧回放定位视觉异常
GPU分析 不支持 新增GPU耗时分析 区分CPU瓶颈和GPU瓶颈
动画分析 不支持 新增动画帧率曲线 分析动画流畅度
组件重绘标记 不支持 新增组件级重绘高亮 精确定位过度重绘的组件

行为变更

  1. LazyForEach默认行为变更:6.0中LazyForEach的cachedCount默认值从0改为3,开箱即用体验更好
  2. 布局优化引擎:6.0引入了增量布局引擎,只重新计算变化的子树,而不是整棵组件树。这意味着5.0中的某些布局性能问题在6.0中可能自动解决了
  3. 图片异步解码:6.0默认启用图片异步解码,大图片不再阻塞渲染线程

适配代码

// HarmonyOS 6.0 新增的渲染优化API

// 6.0新增:组件重绘监听(调试用)
@Entry
@Component
struct RepaintMonitorDemo {
  @State counter: number = 0;
  @State repaintLog: string = '';

  build() {
    Column({ space: 16 }) {
      Text('组件重绘监听')
        .fontSize(22)
        .fontWeight(FontWeight.Bold)

      // 6.0新增:onAppear中可以监听组件的重绘事件
      Text(`计数器: ${this.counter}`)
        .fontSize(48)
        .fontWeight(FontWeight.Bold)
        .fontColor('#2196F3')
        // 6.0新增:组件重绘回调(仅Debug模式生效)
        .onAppear(() => {
          hilog.info(DOMAIN, TAG, '计数器Text组件首次渲染');
        })

      Button('增加计数')
        .onClick(() => {
          this.counter++;
          this.repaintLog += `[${new Date().toLocaleTimeString()}] 计数器更新 → 触发重绘\n`;
        })

      Scroll() {
        Text(this.repaintLog)
          .fontSize(12)
          .fontColor('#666666')
      }
      .layoutWeight(1)
      .width('100%')
    }
    .width('100%')
    .height('100%')
    .padding(16)
  }
}

// 需要引入
import { hilog } from '@kit.PerformanceAnalysisKit';
const TAG = 'RepaintMonitor';
const DOMAIN = 0xFF00;

六、总结

维度 评价
学习难度 ⭐⭐⭐⭐⭐
使用频率 ⭐⭐⭐⭐
重要程度 ⭐⭐⭐⭐⭐

渲染性能优化是HarmonyOS开发中"最硬核"的部分——因为它要求你同时理解UI框架的内部机制和视觉渲染的底层原理。但好消息是,大部分渲染问题都有成熟的解决方案:

  1. 列表卡顿→ LazyForEach + cachedCount + 简化列表项
  2. 布局慢→ 减少嵌套层级 + 避免布局抖动 + 拆分@State粒度
  3. 绘制慢→ 去掉阴影/模糊 + 减少Overdraw + 图片异步解码
  4. 动画卡→ 使用属性动画代替setInterval + 减少动画期间的重绘

Render Profiler给你的不只是数据,更是一种"渲染思维"——每次写UI时都下意识地问自己:这个布局会不会太深?这个特效会不会太重?这个列表该不该用LazyForEach?

当这种思维变成习惯,你的App就离"丝般顺滑"不远了。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。