鸿蒙分享(六):文件视频图片选择+保存到相册

代码仓库:https://gitee.com/linguanzhong/share_harmonyos 鸿蒙api:12

引用的harmony-utils地址:OpenHarmony三方库中心仓

1.拷贝文件到缓存目录

import { FileUtil, ObjectUtil } from '@pura/harmony-utils'
import { common } from '@kit.AbilityKit'
import { BusinessError } from '@kit.BasicServicesKit'
import { BaseDialog } from './dialog/BaseDialog'
import fs from '@ohos.file.fs'


//拷贝文件到缓存目录
static copyFileToCache(mkdir: string, originPath: string, success: (cachePath: string, originalPath?: string) => void,
  fail: (err: BusinessError) => void,
  isShowLoading: boolean = true) {
  if (isShowLoading) {
    BaseDialog.showLoading()
  }
  fs.open(originPath, fs.OpenMode.READ_ONLY).then((file: fs.File) => {
    let path = FileUtil.getCacheDirPath(mkdir) + '/' + file.name
    let aaa = fs.accessSync(path)
    if (aaa) {
      if (isShowLoading) {
        BaseDialog.hintLoading()
      }
      success(path, originPath)
      fs.closeSync(file);
    } else {
      fs.copyFile(file.fd, path).then(() => {
        if (isShowLoading) {
          BaseDialog.hintLoading()
        }
        success(path, originPath)
        fs.closeSync(file);
      }).catch((err: BusinessError) => {
        if (isShowLoading) {
          BaseDialog.hintLoading()
        }
        fail(err)
      })
    }
  }).catch((err: BusinessError) => {
    if (isShowLoading) {
      BaseDialog.hintLoading()
    }
    fail(err)
  })
}

文件选择

//文件选择
static setFilePicker(context: Context,
  documentSelectOptions: picker.DocumentSelectOptions,
  successCallBack: (uris: Array<string>) => void,
  errorCallBack: (err: BusinessError) => void) {
  const documentViewPicker = new picker.DocumentViewPicker(context);
  documentViewPicker.select(documentSelectOptions).then((documentSelectResult: Array<string>) => {
    successCallBack(documentSelectResult)
  }).catch((err: BusinessError) => {
    errorCallBack(err)
  })
}

使用:

BaseText({
  text: "文件选择",
  fontColor: $r('app.color.color_DD2942')
}).margin({
  top: 10
}).onClick(() => {
  let options = new picker.DocumentSelectOptions()
  options.maxSelectNumber = 1
  options.authMode = true
  BaseUtil.setFilePicker(getContext(), options, (uris) => {
    BaseLog.i("uris:" + uris.toString())
    if (uris.length > 0) {
      uris.forEach(item => {
        BaseUtil.copyFileToCache("file", item, (path) => {
          BaseLog.i("path:" + path) //  /data/storage/el2/base/haps/entry/cache/file/欢迎使用华为文件管理.pdf
        }, (error) => {
          BaseLog.i("error11:" + JSON.stringify(error))
        })
      })
    }
  }, (error) => {
    BaseDialog.showToast("调用文件选择失败:" + error.message)
  })
})

图片或视频选择

申请权限:

"requestPermissions": [
 {
        "name": "ohos.permission.READ_IMAGEVIDEO",
        "reason": "$string:permission_READ_IMAGEVIDEO",
        "usedScene": {
          "abilities": [
          ]
        }
      },
      {
        "name": "ohos.permission.WRITE_IMAGEVIDEO",
        "reason": "$string:permission_READ_IMAGEVIDEO",
        "usedScene": {
          "abilities": [
          ]
        }
      }
]

申请权限之后登录签名

//type:0图片 1:视频
getPermission(type: number) {
  let ps: Permissions[] = ['ohos.permission.READ_IMAGEVIDEO'];
  PermissionUtil.requestPermissions(ps).then((result) => {
    if (result) {
      this.photoSelect(type)
    } else {
      BaseDialog.showToast("权限申请失败")
    }
  })
}

 //type:0图片 1:视频
  photoSelect(type: number) {
    let options: photoAccessHelper.PhotoSelectOptions = {
      isEditSupported: true,
      isOriginalSupported: true,
      maxSelectNumber: type == 0 ? 1 : 1,
      isSearchSupported: true,
      MIMEType: type == 0 ? photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE : photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE
    }
    PhotoHelper.select(options).then((uris: string[]) => {
      if (uris.length > 0) {
        uris.forEach(item => {
          BaseUtil.copyFileToCache("image", item, (path) => {
            if (type == 0) {
              if (FileUtil.lstatSync(path).size >= 5 * 1024 * 1024) {
                BaseDialog.showToast("只能上传大小为5M以下的图片~")
              } else {
              }
            } else {
              if (FileUtil.lstatSync(path).size >= 20 * 1024 * 1024) {
                BaseDialog.showToast("只能上传大小为20M以下的视频~")
              } else {
              }
            }
          }, (error) => {
          })
        })

      }
    }).catch((err: BusinessError) => {
      BaseDialog.showToast("调用相册失败:" + err.message)
    })
  }

使用:

BaseText({
  text: "视频选择",
  fontColor: $r('app.color.color_DD2942')
}).margin({
  top: 10
}).onClick(() => {
  this.getPermission(0)
})

保存到相册

  //保存到相册
  static async save(filePath: string, fileName: string, success: (path: string) => void, fail: (err: BusinessError) => void) {
    BaseDialog.showLoading()
    let photoHelper = photoAccessHelper.getPhotoAccessHelper(getContext());
    let uri = fileUri.getUriFromPath(filePath)
    let assetChangeRequest: photoAccessHelper.MediaAssetChangeRequest =
      photoAccessHelper.MediaAssetChangeRequest.createImageAssetRequest(getContext(), uri);
    photoHelper.applyChanges(assetChangeRequest).then((s) => {
      BaseDialog.hintLoading()
      success(filePath)
      BaseDialog.hintLoading()
    }).catch((err: BusinessError) => {
      fail(err)
      BaseDialog.hintLoading()
    })
  }

    //拷贝到图库
  static saveToAlbum(downLoadUrl: string, success: (path: string) => void, fail: (err: BusinessError) => void) {
    let e: BusinessError = {
      code: 0,
      name: '',
      message: ''
    }
    PermissionUtil.requestPermissions('ohos.permission.WRITE_IMAGEVIDEO').then((result) => {
      if (result) {
        let filePath = ""
        let fileName = ""
        let aa = downLoadUrl.split("/")
        if (aa.length != 0) {
          fileName = aa[aa.length-1]
          //有些图片链接不是.jpg结尾
          if (fileName.indexOf(".") == -1) {
            fileName += ".jpg"
          }
          BaseLog.i("fileName:" + fileName)
          filePath = BaseUtil.getImagePathByFileName(fileName)
        }
        BaseLog.i("filePath:" + filePath)
        if (BaseUtil.isEmpty(filePath)) {
          filePath = BaseUtil.getImagePath() + '/' + fileName
          let baseRequestSet = new BaseRequestSet()
          baseRequestSet.url = downLoadUrl
          baseRequestSet.filePath = filePath
          baseRequestSet.isLoading = true
          new BaseHttp().downloadFile(baseRequestSet,
            (msg) => {
              BaseUtil.save(msg ?? "", fileName, success, fail)
            },
            (error) => {
              e.message = error.message
              e.code = Number(error.code)
              fail(e)
            },
            () => {
            },
            (progress) => {
              BaseLog.i("progress:" + progress.progress)
            }
          )
        } else {
          BaseUtil.save(filePath, fileName, success, fail)
        }
      } else {
        e.message = "权限申请失败"
        fail(e)
      }
    })
  }

使用:

BaseText({
  text: "下载保存到图库",
  fontColor: $r('app.color.color_DD2942')
}).margin({
  top: 10
}).onClick(() => {
  BaseUtil.saveToAlbum("https://pics4.baidu.com/feed/314e251f95cad1c8dd6c162f5b8c5b07c83d5127.jpeg", (success) => {
    BaseDialog.showToast("保存成功:" + success)
  }, (error) => {
    BaseDialog.showToast(error.message)
  })
})
<think>嗯,用户想在鸿蒙系统中实现将animation.to生成的动态图片保存相册的方法。首先,我需要回忆一下相关的开发文档和API。根据提供的引用,特别是引用[3]提到了使用image.createPixelMapSync()和writePixelsSync来拼接长截图,这可能涉及到图像处理的部分。但用户需要的是动态图片,比如GIF或视频,而不仅仅是静态截图。 接下来,我需要确定鸿蒙系统是否支持将动画导出为动态图片格式。通常,这可能需要逐帧捕获动画,然后将这些帧编码成GIF或视频文件。引用[2]提到了animateTo函数,这可能用于控制动画,但保存动画可能需要捕获每一帧的图像数据。 另外,引用[3]中的方法是将图像片段写入PixelMap来生成长截图。或许类似的方法可以用于捕获动画的每一帧,然后将这些帧合并成动态图片。但需要注意,鸿蒙的API是否支持这样的操作,特别是动态图片的编码和保存。 用户的问题可能涉及到以下步骤:1. 逐帧捕获动画;2. 将帧数据保存为图像文件;3. 合并图像文件为动态格式;4. 使用相册保存API。需要检查是否有相关的图像处理库或API支持这些步骤,例如image模块是否有编码GIF的功能,或者是否需要第三方库。 此外,引用[4]提到使用Promise处理异步操作,这可能在进行文件保存时用到,确保异步操作的正确执行顺序。例如,在每帧捕获后异步保存,最后合并所有帧。 还需要考虑权限问题,应用是否有写入相册的权限,这需要在配置文件中声明,并动态请求用户授权。引用[3]中的SaveButton控件可能是一个安全控件,用于处理保存操作,但需要确认其是否支持动态图片格式。 可能遇到的问题包括:帧率控制、内存管理(大量帧可能导致内存不足)、动态图片编码的性能问题。需要建议用户进行性能优化,比如分批次处理帧或使用后台线程。 综上所述,实现步骤可能包括使用动画监听逐帧捕获,利用image模块处理每帧图像,使用编码库生成动态文件,最后调用相册保存接口,并处理权限和异步操作。</think>在鸿蒙系统开发中,将$animation.to$生成的动态效果保存为动态图片需要结合**动画帧捕获**和**图像编码技术**。以下是具体实现步骤及代码示例: --- ### 一、核心实现原理 1. **逐帧捕获**:通过动画插值监听器获取每一帧画面 2. **图像编码**:将帧序列编码为GIF/APNG等动态格式 3. **文件存储**:调用媒体库接口保存相册 --- ### 二、实现步骤 #### 1. 配置权限 在`module.json5`中添加相册写入权限: ```json "requestPermissions": [ { "name": "ohos.permission.WRITE_IMAGEVIDEO" } ] ``` #### 2. 创建动画控制器 ```typescript @State scale: number = 1; private anim = this.animation.create({ duration: 1000, curve: curves.springMotion() }); ``` #### 3. 逐帧捕获实现 ```typescript // 创建帧容器 private frameArray: image.PixelMap[] = []; // 设置动画监听 this.anim.onStart(() => { this.anim.onFrame((value: number) => { // 捕获当前帧画面 const captureOptions: image.ImageCaptureOptions = { quality: 100, format: image.ImageFormat.JPEG }; const framePixelMap = image.createPixelMapFromSurface(captureOptions); this.frameArray.push(framePixelMap); // 保存帧数据[^3] }); }); ``` #### 4. 动态图像编码 ```typescript async function encodeGIF() { const gifEncoder = new GifEncoder(800, 600); const output = new ByteArrayOutputStream(); gifEncoder.start(output); gifEncoder.setDelay(100); // 设置帧间隔 // 遍历所有帧 this.frameArray.forEach((pixelMap) => { const frameBitmap = await pixelMap.getBitmapInfo(); gifEncoder.addFrame(frameBitmap); // 添加单帧 }); gifEncoder.finish(); return output.toByteArray(); } ``` #### 5. 保存相册 ```typescript async function saveToGallery() { const gifData = await this.encodeGIF(); // 创建媒体资源 const mediaRequest: mediaLibrary.MediaAssetChangeRequest = await mediaLibrary.createMediaAssetChangeRequest(context); // 设置文件属性 mediaRequest.path = "Download/Animation/"; mediaRequest.displayName = `anim_${Date.now()}.gif`; // 写入文件内容 const file = await mediaRequest.getFileHandle(); await file.write(gifData); // 异步写入[^4] // 提交保存请求 await mediaLibrary.applyChanges(mediaRequest); } ``` --- ### 三、关键优化点 1. **内存管理**:建议使用`LruCache`缓存最近5秒的帧数据,防止内存溢出 2. **异步处理**:通过`Promise`链式调用确保操作顺序 ```typescript animateTo({}, async () => { await this.captureFrames(); await this.encodeAnimation(); await this.saveToGallery(); }); ``` 3. **分辨率适配**:根据设备屏幕密度自动调整捕获分辨率 ```typescript const display = display.getDefaultDisplaySync(); const captureWidth = display.width * display.densityDPI; ``` --- ### 四、注意事项 1. 动态图片生成需在**UI线程之外**执行 2. 建议添加进度提示组件 3. 不同设备对动态格式支持存在差异,需做兼容检测 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值