鸿蒙应用开发:图片渐进式加载Canvas渲染案例分享

一、前言

你在浏览网页时,一定见过这种效果: 一张高清大图,不是瞬间显示,而是从上到下、一行一行慢慢"刷"出来。

这种效果叫渐进式图片加载,既能提升视觉体验,又能大幅降低内存占用。今天这篇文章,带你用实现一模一样的效果!

实现亮点:

  • 图片从上到下逐行渲染
  • 等比缩放,不拉伸、不变形
  • 图片自动屏幕居中显示
  • 内存占用极低,高清大图不OOM

二、效果展示

加载完成效果 动态渐进渲染效果

三、实现原理

  1. 不一次性加载整张图片
  2. 每次只加载一小条区域
  3. 通过 Canvas 把这一小条画到屏幕上
  4. 不断重复,直到整张图片绘制完成
  5. 自动计算居中位置,让图片显示在屏幕正中间

四、实现思路

  1. 使用 ImageSource 加载图片资源
  2. 按区域增量创建 PixelMap
  3. 计算等比缩放尺寸与屏幕居中偏移量
  4. Canvas 逐行绘制图片条带
  5. 绘制完成后释放资源,避免内存泄漏

五、完整代码

javascript 复制代码
import { image } from '@kit.ImageKit';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct Index {
  private canvasCtx: CanvasRenderingContext2D = new CanvasRenderingContext2D();
  private imageSource: image.ImageSource | null = null;

  private imageWidth = 0;
  private imageHeight = 0;
  private currentLine = 0;

  // 每次绘制的高度 这两行代码控制绘制速度
  private readonly stepHeight = 10;
  // 绘制间隔时间
  private readonly delayMs = 5;

  private displayWidth = 0;
  private displayHeight = 0;
  private canvasViewWidth = 0;
  private canvasViewHeight = 0;

  private timerId: number | null = null;

  build() {
    Column() {
      Canvas(this.canvasCtx)
        .width('100%')
        .height('100%')
        .onReady(() => {
          this.canvasViewWidth = this.canvasCtx.width ?? 0;
          this.canvasViewHeight = this.canvasCtx.height ?? 0;
          this.startLoadProgressiveImage();
        })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#000')
  }

  aboutToDisappear() {
    this.clearTimer();
  }

  clearTimer() {
    if (this.timerId !== null) {
      clearTimeout(this.timerId);
      this.timerId = null;
    }
  }

  // 等比缩放计算
  calcFitSize(srcW: number, srcH: number, maxW: number, maxH: number) {
    const ratio = Math.min(maxW / srcW, maxH / srcH);
    this.displayWidth = srcW * ratio;
    this.displayHeight = srcH * ratio;
  }

  // 加载图片
  private async getImageSource(path: string): Promise<image.ImageSource | null> {
    try {
      const host = this.getUIContext().getHostContext();
      if (!host) return null;
      const mgr = host.resourceManager;
      const buf = await mgr.getRawFileContent(path);
      return image.createImageSource(buf.buffer);
    } catch (e) {
      console.error("图片加载失败", (e as BusinessError).message);
      return null;
    }
  }

  // 启动渐进式加载
  async startLoadProgressiveImage() {
    const img = await this.getImageSource("mimi.jpeg");
    if (!img) return;

    this.imageSource = img;
    const info = await img.getImageInfo();
    this.imageWidth = info.size.width;
    this.imageHeight = info.size.height;
    this.currentLine = 0;

    this.calcFitSize(this.imageWidth, this.imageHeight, this.canvasViewWidth, this.canvasViewHeight);
    this.drawLineByLine();
  }

  // 逐行绘制核心
  async drawLineByLine() {
    if (!this.imageSource || !this.canvasCtx) return;

    if (this.currentLine >= this.imageHeight) {
      console.log("图片居中绘制完成");
      this.clearTimer();
      return;
    }

    const decodeH = Math.min(this.currentLine + this.stepHeight, this.imageHeight);
    const region: image.Region = {
      x: 0,
      y: this.currentLine,
      size: { width: this.imageWidth, height: decodeH - this.currentLine }
    };

    const pixelMap = await this.imageSource.createPixelMap({
      desiredRegion: region,
      desiredPixelFormat: image.PixelMapFormat.RGBA_8888
    });
    if (!pixelMap) return;

    // 水平 + 垂直居中
    const offsetX = (this.canvasViewWidth - this.displayWidth) / 2;
    const offsetY = (this.canvasViewHeight - this.displayHeight) / 2;

    const drawY = offsetY + this.currentLine * (this.displayHeight / this.imageHeight);
    const drawH = region.size.height * (this.displayHeight / this.imageHeight);

    // 绘制图片
    this.canvasCtx.drawImage(pixelMap, offsetX, drawY, this.displayWidth, drawH);
    pixelMap.release();

    this.currentLine += this.stepHeight;

    this.timerId = setTimeout(() => {
      this.drawLineByLine();
    }, this.delayMs);
  }
}

六、关键代码说明

  1. 全屏 Canvas

    让画布占满整个屏幕,方便图片居中显示。

  2. 等比缩放

    自动计算宽高比,保证图片不变形、不拉伸

  3. 增量区域解码

    每次只解码图片的一小条区域,而不是整张图,内存占用极低

  4. 双向居中算法

    自动计算水平、垂直偏移量,让图片无论什么尺寸,都显示在屏幕正中间

  5. 资源安全释放

    页面退出时清除定时器,释放像素资源,避免内存泄漏

七、使用方法

  1. 将一张高清图片放入 src/main/resources/rawfile
  2. 代码中图片名称改为你的图片名称
  3. 直接运行即可看到效果

我是散修

专注鸿蒙原生开发、持续分享鸿蒙技术,带你实现好玩有趣实用的功能!

相关推荐
达子6661 分钟前
第25章_HarmonyOs开发图解之 电话服务
华为·harmonyos
懿路向前1 小时前
【HarmonyOS学习笔记】2026-08-05 | 端插件卡片绑定与跨上下文判断
笔记·学习·ai编程·harmonyos
程序员黑豆5 小时前
鸿蒙应用开发:V1与V2版本数据持久化实战教程
前端·harmonyos
程序员黑豆8 小时前
鸿蒙开发 Navigation 路由教程:从入门到实战
前端·harmonyos
HarmonyOS_SDK15 小时前
借助AR Engine人脸识别与跟踪能力,直播不露脸也生动
harmonyos
程序员黑豆17 小时前
鸿蒙应用开发 @BuilderParam 使用教程:实现灵活的 UI 插槽
前端·harmonyos
HMS Core18 小时前
基于人体骨骼点识别与跟踪,实现低时延体感游戏
游戏·华为·harmonyos
凡泰AI18 小时前
APP同时覆盖了iOS、安卓和鸿蒙,如何选择混合开发架构才能减少重复建设,提高功能上线效率~
android·ios·harmonyos·mpaas·uni·小程序容器
达子66618 小时前
第23章_HarmonyOs开发图解之 WLAN
华为·harmonyos
程序员黑豆18 小时前
鸿蒙应用开发 @Builder 使用教程:从入门到精通
前端·harmonyos