目录

一、基本介绍

二、技术要点

三、图片选择与沙箱处理

3.1 创建FileUtil工具类

四、在页面中使用和预览图片

4.1 创建图片选择和预览组件

4.2 在页面中使用该组件

五、沙箱文件操作详解

5.1 沙箱目录说明

5.2 URI格式说明

六、实际应用示例

6.1 多图片选择与预览

6.2 图片网格预览组件

七、注意事项与优化建议

7.1 内存管理

7.2 图片性能优化

7.3 权限处理

八、总结


一、基本介绍

在鸿蒙NEXT应用开发中,图片的选择、处理和预览是常见需求。本教程将重点介绍如何在鸿蒙NEXT应用中实现图片的选择、沙箱处理和预览功能,不涉及后端上传流程。

二、技术要点

本教程涉及的主要技术点包括:

  • 使用@kit.CoreFileKit的picker选择用户图片
  • 使用fileIo进行文件操作和沙箱处理
  • 在鸿蒙应用中预览图片

三、图片选择与沙箱处理

3.1 创建FileUtil工具类

首先创建一个FileUtil.ets文件,用于处理图片选择和沙箱处理:

import { picker } from '@kit.CoreFileKit'
import { fileIo } from '@kit.CoreFileKit'
import { util } from '@kit.ArkTS'
import request from '@ohos.request'
import { promptAction } from '@kit.ArkUI'

class FileUtilModel {
  /**
   * 选择照片并复制到沙箱
   * @param context 上下文
   * @returns 返回沙箱内图片路径
   */
  selectPhoto(context: Context): Promise<string> {
    return new Promise<string>(async (resolve, reject) => {
      try {
        // 创建图片选择器
        const photo = new picker.PhotoViewPicker()
        
        // 调用选择方法
        const result = await photo.select({
          maxSelectNumber: 1,  // 最多选择1张图片
          MIMEType: picker.PhotoViewMIMETypes.IMAGE_TYPE  // 仅图片类型
        })
        
        if (result.photoUris.length) {
          // 获取选择的第一张图片URI
          const photoUri = result.photoUris[0]
          
          // 拷贝到沙箱目录
          const sandboxPath = await this.copyToSandbox(context, photoUri)
          resolve(sandboxPath)
        } else {
          reject(new Error("未选择图片"))
        }
      } catch (error) {
        reject(new Error(error.message || "选择图片失败"))
      }
    })
  }
  
  /**
   * 将图片复制到应用沙箱
   * @param context 上下文
   * @param sourceUri 源图片URI
   * @returns 沙箱内的图片路径
   */
  private copyToSandbox(context: Context, sourceUri: string): Promise<string> {
    return new Promise<string>((resolve, reject) => {
      try {
        // 打开源文件
        const sourceFile = fileIo.openSync(sourceUri, fileIo.OpenMode.READ_ONLY)
        
        // 生成目标文件名和路径
        const defaultDir = context.cacheDir  // 使用应用缓存目录
        const tempFileName = util.generateRandomUUID() + '.jpg'
        const targetPath = defaultDir + '/' + tempFileName
        
        // 复制文件
        fileIo.copyFileSync(sourceFile.fd, targetPath)
        
        // 关闭源文件,释放资源
        fileIo.closeSync(sourceFile.fd)
        
        // 构建沙箱内URI格式路径
        const sandboxUri = `internal://cache/${tempFileName}`
        
        promptAction.showToast({
          message: '图片已复制到沙箱',
          duration: 2000
        })
        
        resolve(sandboxUri)
      } catch (error) {
        reject(new Error("复制图片到沙箱失败: " + error.message))
      }
    })
  }
  
  /**
   * 获取图片的本地路径(用于预览)
   * @param uri 沙箱URI
   * @returns 可预览的文件路径
   */
  getLocalPath(uri: string): string {
    if (uri.startsWith('internal://cache/')) {
      // 从internal://cache/xxx.jpg 格式转换为 缓存目录路径
      const fileName = uri.replace('internal://cache/', '')
      return context.cacheDir + '/' + fileName
    }
    return uri
  }
}

// 创建单例
let FileUtil = new FileUtilModel()
export default FileUtil

四、在页面中使用和预览图片

4.1 创建图片选择和预览组件

import FileUtil from '../utils/FileUtil'
import { promptAction } from '@kit.ArkUI'

@Component
export struct ImagePickerComponent {
  @State imagePath: string = ''
  
  build() {
    Column() {
      // 图片预览区域
      if (this.imagePath) {
        Stack() {
          // 显示图片
          Image(this.imagePath)
            .width('100%')
            .height(200)
            .objectFit(ImageFit.Cover)
            .borderRadius(8)
          
          // 删除按钮
          Button({ type: ButtonType.Circle }) {
            Image($r('app.media.ic_delete'))
              .width(24)
              .height(24)
          }
          .width(36)
          .height(36)
          .position({ x: '90%', y: 10 })
          .backgroundColor('rgba(0,0,0,0.5)')
          .onClick(() => {
            this.imagePath = ''
          })
        }
        .width('100%')
        .margin({ bottom: 20 })
      } else {
        // 选择图片按钮
        Button('选择图片') {
          Image($r('app.media.ic_add_image'))
            .width(24)
            .height(24)
            .margin({ right: 8 })
          Text('添加图片')
            .fontSize(16)
        }
        .width('100%')
        .height(60)
        .margin({ bottom: 20 })
        .onClick(() => {
          this.selectImage()
        })
      }
    }
    .width('100%')
    .padding(16)
  }
  
  // 选择图片方法
  async selectImage() {
    try {
      // 调用图片选择器
      const sandboxUri = await FileUtil.selectPhoto(getContext(this))
      
      // 获取本地路径用于预览
      this.imagePath = sandboxUri
      
      promptAction.showToast({
        message: '图片选择成功',
        duration: 2000
      })
    } catch (error) {
      promptAction.showToast({
        message: '选择图片失败:' + error.message,
        duration: 2000
      })
    }
  }
}

4.2 在页面中使用该组件

@Entry
@Component
struct ImagePickerPage {
  build() {
    Column() {
      // 顶部标题
      Text('图片选择与预览')
        .fontSize(22)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 20, bottom: 30 })
      
      // 使用图片选择器组件
      ImagePickerComponent()
        .width('90%')
      
      // 说明文本
      Text('选择图片后会自动复制到应用沙箱并预览')
        .fontSize(14)
        .opacity(0.6)
        .margin({ top: 30 })
    }
    .width('100%')
    .height('100%')
    .alignItems(HorizontalAlign.Center)
    .backgroundColor('#F8F8F8')
  }
}

五、沙箱文件操作详解

5.1 沙箱目录说明

鸿蒙NEXT应用中常用的沙箱目录包括:

  • context.cacheDir:应用缓存目录,用于存储临时文件,系统可能在空间不足时清理
  • context.filesDir:应用文件目录,用于存储应用数据文件
  • context.databaseDir:应用数据库目录

5.2 URI格式说明

鸿蒙NEXT中的URI格式主要有:

  • internal://cache/xxx.jpg:应用缓存目录下的文件
  • internal://app/xxx.jpg:应用目录下的文件
  • file:///xxx/xxx.jpg:文件系统路径

在本教程中,我们使用internal://cache/格式来表示沙箱中的图片路径。

六、实际应用示例

6.1 多图片选择与预览

// 多图片选择功能
selectMultiplePhotos(context: Context): Promise<string[]> {
  return new Promise<string[]>(async (resolve, reject) => {
    try {
      // 创建图片选择器
      const photo = new picker.PhotoViewPicker()
      
      // 调用选择方法,允许选择多张图片
      const result = await photo.select({
        maxSelectNumber: 9,  // 最多选择9张图片
        MIMEType: picker.PhotoViewMIMETypes.IMAGE_TYPE
      })
      
      if (result.photoUris.length) {
        // 处理每张图片
        const sandboxUris: string[] = []
        
        // 使用Promise.all处理多个异步操作
        const copyPromises = result.photoUris.map(uri => this.copyToSandbox(context, uri))
        const results = await Promise.all(copyPromises)
        
        promptAction.showToast({
          message: `已选择${results.length}张图片`,
          duration: 2000
        })
        
        resolve(results)
      } else {
        reject(new Error("未选择图片"))
      }
    } catch (error) {
      reject(new Error(error.message || "选择图片失败"))
    }
  })
}

6.2 图片网格预览组件

@Component
export struct ImageGridComponent {
  @State imageList: string[] = []
  
  build() {
    Column() {
      // 图片网格显示
      if (this.imageList.length > 0) {
        Grid() {
          ForEach(this.imageList, (image, index) => {
            GridItem() {
              Stack() {
                Image(image)
                  .width('100%')
                  .height('100%')
                  .objectFit(ImageFit.Cover)
                  .borderRadius(4)
                
                Button({ type: ButtonType.Circle }) {
                  Image($r('app.media.ic_delete'))
                    .width(16)
                    .height(16)
                }
                .width(24)
                .height(24)
                .position({ x: '85%', y: 5 })
                .backgroundColor('rgba(0,0,0,0.5)')
                .onClick(() => {
                  this.removeImage(index)
                })
              }
            }
          })
          
          // 添加按钮,只在图片数量小于9时显示
          if (this.imageList.length < 9) {
            GridItem() {
              Column() {
                Image($r('app.media.ic_add'))
                  .width(36)
                  .height(36)
                Text('添加')
                  .fontSize(12)
                  .margin({ top: 4 })
              }
              .width('100%')
              .height('100%')
              .justifyContent(FlexAlign.Center)
              .border({ width: 1, style: BorderStyle.Dashed, color: '#CCCCCC' })
              .borderRadius(4)
              .onClick(() => {
                this.selectImages()
              })
            }
          }
        }
        .columnsTemplate('1fr 1fr 1fr')
        .rowsGap(8)
        .columnsGap(8)
        .width('100%')
      } else {
        // 未选择图片时显示添加按钮
        Button('选择图片') {
          Image($r('app.media.ic_add_image'))
            .width(24)
            .height(24)
            .margin({ right: 8 })
          Text('添加图片')
            .fontSize(16)
        }
        .width('100%')
        .height(60)
        .onClick(() => {
          this.selectImages()
        })
      }
    }
    .width('100%')
  }
  
  // 选择多张图片
  async selectImages() {
    try {
      const newImages = await FileUtil.selectMultiplePhotos(getContext(this))
      // 合并新选择的图片和已有图片,确保不超过9张
      this.imageList = [...this.imageList, ...newImages].slice(0, 9)
    } catch (error) {
      promptAction.showToast({
        message: '选择图片失败:' + error.message,
        duration: 2000
      })
    }
  }
  
  // 移除指定索引的图片
  removeImage(index: number) {
    this.imageList.splice(index, 1)
    // 强制更新数组
    this.imageList = [...this.imageList]
  }
}

七、注意事项与优化建议

7.1 内存管理

  • 图片文件操作完成后务必关闭文件描述符,避免内存泄漏
  • 不再需要的临时图片文件应该及时删除,避免占用过多存储空间

7.2 图片性能优化

  • 可以根据实际需要调整图片尺寸和质量,减小内存占用
  • 预览时可以先显示缩略图,提高加载速度

7.3 权限处理

  • 使用PhotoViewPicker不需要申请额外权限
  • 如果需要保存图片到公共目录,需要申请相应的存储权限

八、总结

本教程详细介绍了鸿蒙NEXT应用中图片的选择、沙箱处理和预览功能的实现方法。通过使用PhotoViewPicker选择图片,将其复制到应用沙箱目录,然后在应用中预览显示。这些基础功能是开发图片相关应用的重要基础,可以根据实际需求进行扩展和优化。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐