鸿蒙新特性 | 图片怎么显示——Image 组件那些坑

一、我们想解决什么问题

想象一下,你打开一个新闻 App,首页是一堆缩略图。有的图秒出,有的图转圈圈半天,有的干脆显示一个裂开的图标。你是什么感受?

这其实就是图片加载体验的核心矛盾:网络是不可靠的,图片有大有小,用户期望的是"快、稳、好看"

在 HarmonyOS 的 ArkUI 框架里,Image 组件是专门负责图片显示的。它本身不复杂,但真正用好它,需要解决以下几个实际问题:

1. 图片从哪来?

本地资源、网络地址、Base64 编码、Raw File......来源不同,加载方式也不同。HarmonyOS 的 Image 组件支持这些主流数据源,但每种都需要正确的初始化方式。

2. 加载失败了怎么办?

网络超时、图片地址404、图片格式不支持------这些情况太常见了。产品经理一般会说:"不能显示空白,要有个占位图或者提示。"但这个占位图怎么做、怎么切换,又是个细节问题。

3. 加载过程怎么呈现给用户?

用户点进去页面,不希望看到一片空白然后图片突然蹦出来。更好的做法是:先显示一个骨架屏或者 loading 动画,图片下载完成后再淡入。这个过渡体验非常重要。

4. 大图怎么适配不同屏幕?

手机屏幕有大有小,图片比例也不固定。如果直接铺满容器,可能会变形;如果用固定高度,宽屏手机上可能很丑。ArkUI 提供了 objectFit 属性来处理这个,但具体用哪个值、要不要配合宽高比设一个合适的 aspectRatio,这里面的坑不少。

5. 性能怎么优化?

列表里有很多图片的时候,一次性全部加载会导致内存爆炸。HarmonyOS 提供了懒加载机制,配合 LazyForEach 使用才能让列表滚动流畅。但 Image 组件本身也有一些属性可以帮助我们做优化,比如 syncLoad 控制同步还是异步。

以上这些问题,本文会逐一拆解。不会一上来就贴代码,而是先说清楚为什么这么做,然后再看代码怎么写。


二、数据模型设计

正式写代码之前,我们先想清楚数据结构。图片加载这个功能涉及的状态和配置其实不少,如果一开始不梳理清楚,后面的代码会越写越乱。

我们用一个简单的 TypeScript interface 来定义图片组件的核心状态:

typescript 复制代码
// ImageCard.ets
// 图片卡片的状态模型

export interface ImageState {
  // 图片地址,本地路径或网络URL
  src: string;
  // 加载状态:pending | loading | success | fail
  status: 'pending' | 'loading' | 'success' | 'fail';
  // 加载进度(0-100)
  progress: number;
  // 错误信息
  errorMsg: string;
}

export interface ImageConfig {
  // 是否启用占位图
  showPlaceholder: boolean;
  // 是否启用加载动画
  showLoading: boolean;
  // 图片适应模式
  objectFit: ImageFit;
  // 是否允许预览/放大
  previewEnabled: boolean;
}

为什么要单独设计 ImageState 这个状态模型?想象一个场景:你在写一个商品列表,每个商品卡片里有一张图。加载中、加载成功、加载失败------这三种状态在同一张图片上会切换。如果没有一个状态模型,你可能要在代码里到处写 if else 判断,状态一多就乱了。

把它单独抽成 interface 的好处是:状态和配置分离,职责清晰。一个对象管状态,一个对象管配置,后面改起来不互相影响。

另外,status 用了联合类型 'pending' | 'loading' | 'success' | 'fail',而不是用一个布尔值 isLoading 加一个 isError。原因是:图片的状态其实有四种,直接用联合类型表达更直观,后面写条件渲染的时候代码读起来也更顺畅。


三、核心设计决策

图片加载的方案其实有不少可选路径,这里把几个最关键的设计决策拉出来对比一下。

3.1 占位图的实现方案

方案 做法 优点 缺点
Stack 叠加层 用 Stack 叠两张图:底层占位图 + 上层真实图片,真实图片加载成功后覆盖 实现简单,状态切换自然 切换时可能有闪烁
条件渲染 if/else 用 if 分支:加载中渲染占位图,加载成功替换为真实图片 切换干净,无残影 每次切换都会重新构建组件
opacity 过渡 真实图片用 opacity 动画:加载完成后从 0 过渡到 1 过渡体验最顺滑 实现稍复杂,需要监听加载完成事件

我们的实战方案选用 Stack 叠加层 + opacity 过渡 的组合。这个方案在视觉体验和实现复杂度之间取得了比较好的平衡。具体实现上,真实图片加载成功后通过 animateTo 控制透明度从 0 变到 1,整个过渡大概 300 毫秒,用户感受就是图片"淡入"了。

3.2 网络图片的加载策略

问题:网络图片从发起请求到图片显示,有一段等待时间。这段时间怎么处理?

两种常见思路:

同步加载:图片下载完之前,组件不渲染任何东西。优点是状态简单;缺点是用户看到的是一片空白,体验差。

异步加载 + 状态反馈 :发起请求后立即显示 loading 状态,下载完成后渲染图片。我们在实战里选择了这种方式。配合 onCompleteonError 回调,状态管理会非常清晰。

3.3 大图适配策略

图片容器大小和图片本身大小的关系,是一个经典问题。ArkUI 的 objectFit 属性提供以下几种模式:

  • Cover:等比缩放填充,超出部分裁剪------适合头像、轮播图
  • Contain:等比缩放,让整张图完整显示在容器内------适合展示完整图片
  • Fill:拉伸铺满------容易变形,不推荐用于真实图片展示
  • Auto:自动选择------但实际上在某些场景下行为不够可控

我们的实战方案选 Cover,原因是大多数 UI 场景(商品图、头像、新闻封面)都需要图片填满容器且不变形。

3.4 为什么不用第三方图片库?

HarmonyOS 生态目前主流的图片库(如 ohdss/ImageKnife)确实提供了缓存、压缩、渐进加载等开箱即用的功能。但对于学习理解 Image 组件本身的工作原理来说,用原生组件手写一遍更有价值。等你理解了底层逻辑,再用这些库就会知道它在帮你做什么、优化什么。


四、完整代码实现

为了让你理解得更扎实,我们分三个文件来写:ImageCard 组件(图片卡片)、ImageViewer 页面(图片查看器)、Index 页面(入口列表)。代码块一共 4 个,每个控制在 50 行以内。

4.1 ImageCard 图片卡片组件

typescript 复制代码
// ImageCard.ets
import promptAction from '@ohos.promptAction';

// ImageCard:封装了加载状态、占位图、淡入动画的图片组件
@Component
export struct ImageCard {
  @State private var imgState: ImageState = {
    src: '',
    status: 'pending',
    progress: 0,
    errorMsg: ''
  };

  @Prop config: ImageConfig;
  @Prop src: string;

  // 监听 src 变化,重新加载图片
  aboutToAppear(): void {
    if (this.src) {
      this.loadImage(this.src);
    }
  }

  private loadImage(src: string): void {
    this.imgState = { src, status: 'loading', progress: 0, errorMsg: '' };
  }

  // 图片加载成功回调
  private onImageLoad(width: number, height: number): void {
    this.imgState.status = 'success';
    // 触发淡入动画
    animateTo({ duration: 300, curve: Curve.EaseOut }, () => {});
  }

  // 图片加载失败回调
  private onImageError(err: string): void {
    this.imgState.status = 'fail';
    this.imgState.errorMsg = err;
    promptAction.showToast({ message: '图片加载失败' });
  }

  build() {
    Stack() {
      // 底层:占位图或错误图
      if (this.imgState.status === 'fail') {
        this.buildErrorPlaceholder();
      } else if (this.imgState.status === 'loading' && this.config.showPlaceholder) {
        this.buildLoadingPlaceholder();
      }

      // 上层:真实图片(加载成功后才完全显示)
      Image(this.src)
        .width('100%')
        .height('100%')
        .objectFit(ImageFit.Cover)
        .opacity(this.imgState.status === 'success' ? 1 : 0)
        .autoResize(false)
        .syncLoad(false)
        .onComplete((info) => {
          this.onImageLoad(info.width, info.height);
        })
        .onError((err) => {
          this.onImageError('加载错误');
        })
    }
    .width('100%')
    .aspectRatio(16 / 9)
    .clip(true)
  }

  @Builder
  buildLoadingPlaceholder() {
    Column() {
      LoadingProgress()
        .width(40)
        .height(40)
        .color(Color.Grey)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F0F0F0')
    .justifyContent(FlexAlign.Center)
  }

  @Builder
  buildErrorPlaceholder() {
    Column() {
      Image($r('sys.media.ohos_ic_public_dialog_error'))
        .width(48)
        .height(48)
        .opacity(0.4)
      Text('图片加载失败')
        .fontSize(12)
        .fontColor('#999999')
        .margin({ top: 8 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
    .justifyContent(FlexAlign.Center)
  }
}

这段代码的核心思路:用 Stack 把占位图和真实图片叠在一起。真实图片初始 opacity 是 0(看不见),加载成功后设为 1,配合 animateTo 就有淡入效果。aspectRatio(16/9) 保证图片容器有一个固定比例,防止页面抖动。

4.2 图片查看器(支持手势缩放)

typescript 复制代码
// ImageViewer.ets
@Component
export struct ImageViewer {
  @Prop src: string;
  @State scaleValue: number = 1;
  @State offsetX: number = 0;
  @State offsetY: number = 0;
  @State isEnlarged: boolean = false;

  build() {
    Stack() {
      Image(this.src)
        .width('100%')
        .height('100%')
        .objectFit(ImageFit.Contain)
        .scale({ x: this.scaleValue, y: this.scaleValue })
        .translate({ x: this.offsetX, y: this.offsetY })
        .gesture(
          PinchGesture()
            .onActionUpdate((event) => {
              this.scaleValue = Math.max(1, Math.min(event.scale * this.scaleValue, 3));
              this.isEnlarged = this.scaleValue > 1;
            })
            .onActionEnd(() => {
              if (this.scaleValue < 1.1) {
                animateTo({ duration: 200 }, () => {
                  this.scaleValue = 1;
                  this.offsetX = 0;
                  this.offsetY = 0;
                  this.isEnlarged = false;
                });
              }
            })
        )
        .gesture(
          PanGesture()
            .onActionUpdate((event) => {
              if (this.isEnlarged) {
                this.offsetX += event.translationX;
                this.offsetY += event.translationY;
              }
            })
        )
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#000000')
  }
}

这个查看器的逻辑很直接:用 PinchGesture 控制缩放,范围限制在 1 到 3 倍;用 PanGesture 控制平移,只有在放大状态下才允许拖动。缩回比例小于 1.1 时自动归位,体验接近原生相册。

4.3 Index 入口页面

typescript 复制代码
// Index.ets
import { ImageCard } from './ImageCard';
import { ImageViewer } from './ImageViewer';

interface ArticleItem {
  id: number;
  title: string;
  coverUrl: string;
}

@Entry
@Component
struct Index {
  @State selectedImage: string = '';
  @State showViewer: boolean = false;

  private articles: ArticleItem[] = [
    { id: 1, title: 'HarmonyOS 分布式能力解析', coverUrl: 'https://picsum.photos/800/450?random=1' },
    { id: 2, title: 'ArkUI 声明式 UI 入门指南', coverUrl: 'https://picsum.photos/800/450?random=2' },
    { id: 3, title: '一次开发多端部署实战', coverUrl: 'https://picsum.photos/800/450?random=3' },
    { id: 4, title: '鸿蒙应用性能优化技巧', coverUrl: 'https://picsum.photos/800/450?random=4' },
  ];

  build() {
    Column() {
      Text('图片加载实战')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 20, bottom: 16 })

      List() {
        ForEach(this.articles, (item: ArticleItem) => {
          ListItem() {
            Column() {
              ImageCard({
                src: item.coverUrl,
                config: {
                  showPlaceholder: true,
                  showLoading: true,
                  objectFit: ImageFit.Cover,
                  previewEnabled: true
                }
              })
              .onClick(() => {
                this.selectedImage = item.coverUrl;
                this.showViewer = true;
              })

              Text(item.title)
                .fontSize(14)
                .margin({ top: 8, bottom: 12 })
            }
          }
        }, (item: ArticleItem) => item.id.toString())
      }
      .listDirection(Axis.Vertical)
      .padding({ left: 16, right: 16 })
    }
    .width('100%')
    .height('100%')
  }
}

Index 页面用 List + ForEach 渲染文章列表,配合 ImageCard 组件处理图片加载和占位逻辑。点击图片后跳转到 ImageViewer 进行全屏查看。注意这里列表数据是本地模拟的,真实项目中会通过网络请求获取。

4.4 网络请求封装(可选扩展)

如果你需要从接口获取图片列表,可以用一个简单的网络请求封装:

typescript 复制代码
// HttpUtil.ets
import http from '@ohos.net.http';

export async function fetchImageList(): Promise<string[]> {
  const httpRequest = http.createHttp();
  const response = await httpRequest.request(
    'https://api.example.com/images',
    { method: http.RequestMethod.GET }
  );
  const result = JSON.parse(response.result as string);
  return result.urls as string[];
}

这只是一个示意,实际项目中需要处理异常、loading 状态、分页等场景,建议配合 LazyForEach 做列表懒加载,避免一次性加载大量图片。


五、深度技术原理

理解了代码怎么写之后,我们来聊聊背后的一些设计思路和原理,这样你在遇到问题的时候能自己想明白为什么。

5.1 Image 组件的数据源解析

HarmonyOS 的 Image 组件支持以下几种数据源,每种的数据格式稍有不同:

  • 网络图片Image('https://example.com/photo.jpg'),直接传 URL 字符串即可。底层会自动发起网络请求。
  • 本地资源Image($r('app.media.photo')),引用 resources 目录下的资源文件。
  • Base64 图片Image('data:image/png;base64,iVBORw0KGgo...'),适合小图标或动态生成的图片。
  • Raw FileImage('rawfile://photo.jpg'),读取 entry/src/main/resources/rawfile 目录下的文件。

这里有一个容易踩的坑:网络图片首次加载会慢,因为要经过 DNS 解析、TCP 连接、HTTPS 握手等步骤。如果图片比较大,用户可能会看到长时间的白屏。建议在生产环境中给网络图片加超时限制,以及 fallback 到占位图。

5.2 渲染流程与生命周期

Image 组件在 ArkUI 的渲染流程中属于叶子节点组件(Leaf Component),它不像容器组件那样有子组件。但它的渲染时机和状态切换依然遵循 ArkUI 的渲染机制:

src 属性变化时,ArkUI 会触发组件更新。Image 组件内部的状态机大致是:

  1. pending → 发起加载请求(网络请求或文件读取)
  2. loading → 渲染中,调用方可以监听这个状态显示 loading 动画
  3. success → 图片解码完成,渲染到屏幕上
  4. fail → 加载失败,调用方可以监听这个状态显示错误图

onComplete 回调里拿到的 info 对象包含图片的原始宽高(widthheight)和组件宽高。利用这个信息,你可以在加载完成后计算一个更精确的 aspectRatio,避免页面抖动------这个技巧在做瀑布流或者自适应高度的图片列表时非常有用。

5.3 内存管理与图片缓存

HarmonyOS 的 Image 组件内置了内存缓存机制,但这个缓存是组件级别的,不是全局的。如果你创建了大量独立的 Image 组件,内存占用会随数量线性增长。

所以在大列表场景下,有几个优化手段:

懒加载 :用 LazyForEach 渲染列表,只渲染可见区域的图片,向下滚动时销毁滚出区域的组件,释放内存。

固定宽高或宽高比:提前告诉组件图片的尺寸,可以减少重排和重绘。不要让组件自己猜测尺寸。

控制分辨率:网络图片可以在服务端做多尺寸适配,移动端请求小图而非原图,节省流量和内存。

5.4 为什么 Stack 叠加层比条件渲染更好?

我们选用了 Stack 叠加层的方案来做占位图,这里解释一下原因。

条件渲染(if/else)的问题在于:两个组件是互斥的,切换时旧组件销毁、新组件创建。如果占位图是一个比较复杂的自定义组件,频繁切换会造成 GC 压力,甚至在低端设备上产生卡顿。

Stack 叠加层的做法是:两个组件始终存在,但通过 opacity 控制可见性。真实图片加载成功后直接改变自己的透明度,不需要销毁占位图。切换过程完全由 GPU 合成,效率更高。

当然这个方案也有前提:占位图和真实图片的容器尺寸必须完全一致 ,否则 opacity=0 时仍然会遮挡下面的交互区域。我们用 aspectRatio 固定了容器尺寸,确保这一点。

5.5 手势系统的协作原理

ImageViewer 里同时注册了 PinchGesture(双指缩放)和 PanGesture(单指滑动)。这两个手势在 ArkUI 里是可以同时识别的,框架会根据手势的起点自动分发。

缩放时 scale 变化会带动视觉大小变化;平移时 translate 在已经放大的状态下允许拖动查看图片的不同区域。这两个变换是独立的,可以叠加。代码里通过 isEnlarged 这个状态变量来控制:只有在放大状态下才允许平移,避免误触。


六、常见问题解答

Q1:网络图片加载失败了,怎么显示自定义错误图?

A:在 onError 回调里把状态设为 fail,然后在 build 方法里通过条件判断渲染错误占位图。参考本文 4.1 节的 buildErrorPlaceholder 方法。需要注意:如果图片地址是 404,onError 可能不会被触发(因为 HTTP 请求成功了,只是返回的内容不是图片),这时候可能需要在 onComplete 里检查图片尺寸是否为 0 来判断。

Q2:图片在加载过程中页面高度跳动了,怎么解决?

A:这是最常见的问题之一。原因是:加载前没有图片,容器高度为 0 或由占位图撑开;加载后真实图片渲染出来,高度可能不一致。解决方案是提前固定容器宽高比

typescript 复制代码
Stack() {
  // ...
}
.width('100%')
.aspectRatio(16 / 9) // 固定宽高比,高度由宽度决定,不会跳动

如果图片宽高比不确定(比如用户上传的头像可能是正方形也可能是横图),可以先获取图片尺寸动态计算:

typescript 复制代码
Image(this.src)
  .onComplete((info) => {
    // info.width 和 info.height 是原始尺寸
    // 可以计算出 aspectRatio 并动态更新
  })

Q3:大图片加载很慢,有什么优化方法?

A:几个方向可以一起做。首先,服务端做图片压缩 ,不要把原图传给客户端,移动端 800-1200px 的宽度就够了。其次,使用 syncLoad(false) (默认)做异步加载,避免阻塞 UI 线程。第三,对列表做懒加载,不要一次性创建所有 Image 组件。如果你的图片加载非常慢,可以考虑先显示一个低分辨率的缩略图(模糊图),加载完成后再替换为高清图------类似 iOS 的 LPROG(Low Progressive)方案。

Q4:图片旋转了或者方向不对,是什么问题?

A:有些手机拍的照片带有 EXIF 方向信息。ArkUI 的 Image 组件在解码图片时会自动读取 EXIF 方向并正确显示,但如果图片经过了处理或来源特殊,可能需要手动处理。解决方案是用图片处理相关的 API 预先处理图片方向,或者在服务端统一处理后返回正确朝向的图片。

Q5:怎么实现图片的渐进式加载(先模糊后清晰)?

A:HarmonyOS 原生 Image 组件不直接支持渐进式 JPEG(Progressive JPEG)。但可以换一个思路:先加载一张小图(缩略图)作为占位图,收到 onComplete 回调后再请求大图加载到另一个 Image 组件里。这个方案需要服务端支持多尺寸图片返回,优点是体验接近原生渐进式加载。

Q6:在列表中使用 Image 组件,有什么特别注意的?

A:核心注意事项是不要在 ForEach 的 item 里创建复杂的匿名组件 。每次列表更新都会重建匿名组件,导致图片重新加载。建议把 Image 相关逻辑封装成独立的 @Component 组件(就像本文的 ImageCard),这样组件的状态可以在 item 级别独立管理,不会被列表整体更新影响。另外,配合 LazyForEach 做懒加载,只渲染可见区域的图片,内存占用会大幅下降。


七、运行效果

以下是用 ASCII 字符画模拟的运行效果,帮助你在实际运行前有个直观感受:

点击某张图片后 → 进入全屏预览模式:

运行说明

  • 首次打开时,带网络图片的文章卡片会先显示 LoadingProgress 动画,约 1-2 秒后图片淡入显示
  • 第三张图模拟了一个加载失败的场景,显示错误图标和提示文字
  • 点击任意图片卡片可进入全屏查看模式,支持双指缩放和滑动平移
  • 缩小到接近 1x 时自动归位,体验接近原生相册

八、扩展方向

本文的方案覆盖了图片加载的基础场景,但实际项目中还有很多可以深入的方向:

1. 缓存策略:目前我们的方案每次加载网络图片都会重新下载。生产环境中可以接入图片缓存库(如 ImageKnife 的磁盘缓存),或者自己封装一个 LRU 缓存,避免重复加载。缓存策略直接影响列表滚动的流畅度和用户的流量消耗。

2. 预加载 :在列表场景下,可以提前加载"即将进入可见区域"的图片,实现"无缝滚动"。具体做法是在 LazyForEach 的 item 即将渲染时,提前 2-3 个位置发起图片加载请求。

3. 离线图片:如果你的 App 需要支持离线浏览,图片缓存策略就更加重要。可以把首次加载的图片写入本地文件,下一次打开时直接读本地,节省流量并提升加载速度。

4. 图片编辑集成:除了显示,HarmonyOS 也支持图片裁剪、旋转、滤镜等操作。可以基于 Image 组件扩展出图片编辑功能,配合 Canvas 和 PixelMap API 实现更丰富的图片处理能力。

5. 跨设备图片协同:HarmonyOS 的分布式能力允许图片在手机和平板之间无缝流转。比如在手机上选择图片,平板上直接显示。这个能力适合做相册类的多设备协同应用,值得深入探索。

6. WebP/HEIF 等新格式支持:HarmonyOS Image 组件支持 WebP 格式(Android 生态常见的压缩格式),但 HEIF/HEVC 图片的支持情况需要根据具体设备确认。服务端统一输出 WebP 可以兼顾压缩率和兼容性,是一个值得考虑的方案。

7. 长图与 GIF 处理 :长图(如信息图、长微博)在移动端很常见。如果不做处理,长图会撑满整个容器导致其他内容不可见。解决方案是根据宽高比判断:宽高比超过一定阈值(比如 1:3)时,限制图片的最大高度,底部显示"点击查看完整图片"的提示。GIF 图片在 HarmonyOS 里是作为普通图片序列播放的,可以通过 Image 的 autoPlayinterval 属性控制播放节奏。

8. 头像与九宫格场景 :除了单图卡片,另一个高频场景是头像和九宫格相册。头像图片一般用 Circle 或者带圆角的正方形,核心注意点是:头像来源不可控,用户可能上传了正方形、竖图或横图,objectFit 要选 Cover 才能保证头像始终是规整的圆形。九宫格则更复杂一些,需要根据图片数量动态调整布局------1张图全屏、4张图 2x2 网格、9张图 3x3 网格,这个布局逻辑可以用 Grid 组件配合动态行列配置来实现。

9. 安全与权限 :访问相册图片需要申请权限。如果你的 App 需要让用户从相册选择图片,需要在 module.json5 中声明 ohos.permission.READ_MEDIA 权限,并在运行时通过 abilityAccessCtrl 动态请求授权。权限被拒绝时要给用户明确的提示,并引导去设置页开启,避免出现无权限时一片空白的情况。

10. 测试建议:图片加载涉及大量异步和网络场景,自动化测试有一定挑战。建议至少覆盖这几类测试用例:正常网络下图片加载成功、弱网或无网下图片加载失败并显示错误态、网络恢复后重试、图片尺寸与容器尺寸不匹配时的显示效果。如果用 HarmonyOS 的测试框架,可以通过模拟网络响应或注入本地测试图片文件来构造各种场景。


相关推荐
世人万千丶18 小时前
鸿蒙Flutter Icon与IconButton
flutter·华为·harmonyos·鸿蒙
解局易否结局18 小时前
鸿蒙 PC 应用开发实战:文件系统与沙箱安全存储
安全·华为·harmonyos
解局易否结局19 小时前
鸿蒙原生开发实战|Native 加密与安全:NAPI + OpenSSL 构建全栈密文服务
安全·华为·harmonyos
条tiao条19 小时前
告别 router.pushUrl:鸿蒙 Navigation 页面跳转,从入门到传参与替换,一篇讲透
华为·harmonyos·鸿蒙·页面跳转·navigation
●VON19 小时前
鸿蒙 PC Markdown 编辑器质量工程:证据驱动的技术验证
华为·架构·编辑器·harmonyos·鸿蒙
用户09340777351419 小时前
HarmonyOS WPS Open SDK:关窗回传后如何稳定拿到业务 filePath
harmonyos
解局易否结局19 小时前
鸿蒙新特性实战:ArkUI 渲染性能优化——从 LazyForEach 到页面级按需加载
华为·性能优化·harmonyos
yy403319 小时前
【HarmonyOS学习笔记】2026-07-19 | 布局性能实验:百分比vs固定值vs预计算
前端·harmonyos
世人万千丶19 小时前
Flutter 鸿蒙Text组件详解
flutter·华为·harmonyos·鸿蒙·鸿蒙系统