前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >HarmonyOS 开发实践——基于PhotoViewPicker对图片进行操作

HarmonyOS 开发实践——基于PhotoViewPicker对图片进行操作

原创
作者头像
小帅聊鸿蒙
发布于 2024-11-06 06:10:56
发布于 2024-11-06 06:10:56
3180
举报
文章被收录于专栏:鸿蒙开发笔记鸿蒙开发笔记

场景描述

用户有时需要分享或保存图片、视频等用户文件,开发者可以通过系统预置的 文件选择器(FilePicker) ,实现该能力。通过Picker访问相关文件,将拉起对应的应用,引导用户完成界面操作,接口本身无需申请权限。 PhotoViewPicker :适用于图片或视频类型文件的选择与保存。优选使用 PhotoAccessHelper的PhotoViewPicker 来选择文件。当前PhotoViewPicker对接的选择资源来自于图库,保存位置为系统文件管理器的特定目录,因此使用save接口保存的图片或视频无法在图库中展示。如需在图库中展示,请使用 安全控件创建媒体资源 。

  • 场景一:从图库获取图片,并通过image组件显示
  • 场景二:对图库获取的图片进行操作
  • 场景三:保存图片

方案描述

场景一:从图库获取图片,并通过image组件显示

效果图

方案

  • 创建图库选择器实例,调用select()接口拉起图库界面进行文件选择。文件选择成功后,返回PhotoSelectResult结果集。
  • select返回的uri权限是只读权限,可以根据结果集中uri进行读取文件数据操作。
  • 根据返回uri创建pixelMap
  • 将pixelMap通过image组件送显

核心代码

代码语言:ts
AI代码解释
复制
Image(this.pixelMap).width(200).height(200) 
  Button('打开相册') 
          .onClick(() => { 
            //创建图库选择器对象实例 
            const photoViewPicker = new picker.PhotoViewPicker(); 
            //调用select()接口拉起图库界面进行文件选择,文件选择成功后,返回PhotoSelectResult结果集 
            photoViewPicker.select().then(async (photoSelectResult: picker.PhotoSelectResult) => { 
              //用一个全局变量存储返回的uri 
              selectUris = photoSelectResult.photoUris; 
              console.info('photoViewPicker.select to file succeed and uris are:' + selectUris); 
              //使用fs.openSync接口,通过uri打开这个文件得到fd 
              let file = fs.openSync(selectUris[0], fs.OpenMode.READ_ONLY); 
              console.info('file fd: ' + file.fd); 
              //根据文件fd创建imagSource 
              const imageSource: image.ImageSource = image.createImageSource(file.fd); 
              //完成后关闭fd 
              fs.closeSync(file); 
              let decodingOptions: image.DecodingOptions = { 
                editable: true, 
                desiredPixelFormat: 3, 
              } 
              //创建pixelMap 
              imageSource.createPixelMap(decodingOptions).then(async (pixelMap1: image.PixelMap) => { 
                this.pixelMap = pixelMap1; 
              }); 
            }).catch((err: BusinessError) => { 
              console.error(`Invoke photoViewPicker.select failed, code is ${err.code}, message is ${err.message}`); 
            }) 
          })

场景二:对图库获取的图片进行操作

效果图

方案

  • 调用pixelMap的 rotate方法实现对图面的旋转
  • 通过imagePacker的api实现图片编码压缩

核心代码

代码语言:ts
AI代码解释
复制
  Button("图片操作") 
          .margin({ top: 20 }) 
          .onClick(async (event: ClickEvent) => { 
            if (this.pixelMap) { 
              //旋转90度 
              await this.pixelMap.rotate(90); 
              //创建图像编码ImagePacker对象。 
              const imagePackerApi = image.createImagePacker(); 
              //设置编码输出流和编码参数。 
              // format为图像的编码格式;quality为图像质量,范围从0-100,100为最佳质量。 
              let packOpts: image.PackingOption = { format: "image/jpeg", quality: 100 }; 
              //通过PixelMap进行编码 
              imagePackerApi.packing(this.pixelMap, packOpts).then(async (data: ArrayBuffer) => { 
                // data 为打包获取到的文件流,写入文件保存即可得到一张图片 
                arrayBuffer = data; 
              }).catch((error: BusinessError) => { 
                console.error('Failed to pack the image. And the error is: ' + error); 
              }) 
 
            } 
          })

场景三:保存图片

方案一:通过photoViewPicker将图片保存到系统文件管理器管理特定目录

当前所有picker的save接口都是用户可感知的,具体行为是拉起FilePicker, 将文件保存在系统文件管理器管理的特定目录,与图库管理的资源隔离,无法在图库中看到。

效果图

调用 save() 接口拉起FilePicker界面进行文件保存。用户选择目标文件夹,用户选择与文件类型相对应的文件夹,即可完成文件保存操作。保存成功后,并用一个全局变量存储返回的uri。

使用 fs.openSync 接口,通过选择和保存uri打开这两个文件得到fd,这里需要注意接口权限参数分别是fs.OpenMode.READ_ONLY和fs.OpenMode.WRITE_ONLY。再调用 fs.copyFileSync 接口进行复制,修改完成后关闭两个文件。

核心代码

代码语言:ts
AI代码解释
复制
try { 
              const photoSaveOptions = new picker.PhotoSaveOptions(); // 创建文件管理器保存选项实例 
              photoSaveOptions.newFileNames = ["PhotoViewPicker01.png"]; // 保存文件名(可选),方括号里的文件名自定义,每次不能重复,设备里已有这个文件的话,名字就需要改个不一样的,不然接口会报错 
              const photoViewPicker = new picker.PhotoViewPicker(); 
              try { 
                //调用save()接口拉起FilePicker界面进行文件保存。用户选择目标文件夹,用户选择与文件类型相对应的文件夹,即可完成文件保存操作 
                let photoSaveResult = await photoViewPicker.save(photoSaveOptions); 
                if (photoSaveResult != undefined) { 
                  //保存成功后,并用一个全局变量存储返回的uri 
                  saveUris = photoSaveResult; 
                  console.info('photoViewPicker.save to file succeed and uris are:' + photoSaveResult); 
                } 
              } catch (error) { 
                let err: BusinessError = error as BusinessError; 
                console.error(`[picker] Invoke photoViewPicker.save failed, code is ${err.code}, message is ${err.message}`); 
              } 
            } catch (error) { 
              let err: BusinessError = error as BusinessError; 
              console.info("[picker] photoViewPickerSave error = " + JSON.stringify(err)); 
            }
 try { 
              //使用fs.openSync接口,通过选择和保存uri打开这两个文件得到fd,这里需要注意接口权限参数分别是fs.OpenMode.READ_ONLY和fs.OpenMode.WRITE_ONLY 
              let photoSelect = fs.openSync(selectUris[0], fs.OpenMode.READ_ONLY); 
              let photoSave = fs.openSync(saveUris[0], fs.OpenMode.WRITE_ONLY); 
              //再调用fs.copyFileSync接口进行复制 
              fs.copyFileSync(photoSelect.fd, photoSave.fd); 
              //完成后关闭两个文件。 
              fs.close(photoSelect); 
              fs.close(photoSave); 
            } catch (error) { 
              let err: BusinessError = error as BusinessError; 
              console.info("[picker] Photo Save error = " + JSON.stringify(err)); 
            } 
 
          })

方案二:通过安全控件按钮保存图片到图库

保存控件是一种特殊的安全控件,它允许用户通过点击按钮临时获取存储权限,而无需通过权限弹框进行授权确认

集成保存控件后,当用户点击该控件时,应用会获得10秒内单次访问媒体库特权接口的授权。这适用于任何需要将文件保存到媒体库的应用场景,例如保存图片或视频等。

与需要触发系统应用并由用户选择具体保存路径的Picker不同,保存控件可以直接保存到媒体库路径,使得操作更为便捷。

使用场景:应用仅需要在前台期间,短暂使用保存图片的特性,不需要长时间使用。此时,可以直接使用安全控件中的保存控件,免去权限申请和权限请求等环节(创建媒体资源需要在应用中申请相册管理模块权限'ohos.permission.WRITE_IMAGEVIDEO'),获得临时授权,保存对应图片。

约束与限制

  • 应用在onClick()触发回调到调用媒体库特权接口的时间间隔不能大于10秒。
  • 用户点击一次控件,仅获取一次授权调用。
  • 为了保障用户的隐私不被恶意应用获取,应用需确保安全控件是可见的且用户能够识别的。开发者需要合理的配置控件的尺寸、颜色等属性,避免视觉混淆的情况,如果发生因控件的样式不合法导致授权失败的情况,请检查设备错误日志。

效果图

1)设置安全控件按钮属性。

2)创建安全控件按钮。

3)调用 PhotoAccessHelper.createAsset 接口创建图片资源。

4)根据资源uri创建file并写入图片数据。

核心代码

代码语言:ts
AI代码解释
复制
 @State saveButtonOptions: SaveButtonOptions = { 
    icon: SaveIconStyle.FULL_FILLED, //设置保存按钮的图标风格,不传入该参数表示没有图标,icon和text至少存在一个;这里设置为:保存按钮展示填充样式图标。 
    text: SaveDescription.SAVE_IMAGE, //设置保存按钮的文本描述, 不传入该参数表示没有文字描述,这里设置为:保存按钮的文字描述为“保存图片”。 
    buttonType: ButtonType.Capsule   //设置保存按钮的背景样式,   不传入该参数表示没有背景。这里设置为:胶囊型按钮(圆角默认为高度的一半)。 
  } // 设置安全控件按钮属性
SaveButton(this.saveButtonOptions)// 创建安全控件按钮 
  .padding(10) 
    //点击动作触发该回调。 
  .onClick(async (event, result: SaveButtonOnClickResult) => { 
    //SaveButtonOnClickResult枚举说明 
    //名称 枚举值  描述 
    // SUCCESS 0  保存按钮点击成功。 
    // TEMPORARY_AUTHORIZATION_FAILED  1  保存按钮点击后权限授权失败。 
 
    if (result == SaveButtonOnClickResult.SUCCESS) { 
      // result:存储权限的授权结果,授权时长为10秒,即触发点击后,可以在10秒之内不限制次数的调用特定媒体库接口,超出10秒的调用会鉴权失败。 
      try { 
        let context = getContext(); 
        //获取相册管理模块的实例,用于访问和修改相册中的媒体文件 
        let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context); 
        // onClick触发后10秒内通过createAsset接口创建图片文件,10秒后createAsset权限收回 
        let uri = await phAccessHelper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg'); // 创建媒体文件 
        console.info('createAsset successfully, uri: ' + uri); 
        let file = await fs.open(uri, fs.OpenMode.READ_WRITE || fs.OpenMode.CREATE); 
        //   // 使用uri打开文件,可以持续写入内容,写入过程不受时间限制 
        await fs.write(file.fd, arrayBuffer); 
        /// 关闭文件 
        await fs.close(file); 
        promptAction.showToast({ message: '已保存至相册!' }); 
      } catch (err) { 
        console.error('createAsset failed, message = ', err); 
      } 
    } else { 
      console.error('SaveButtonOnClickResult createAsset failed'); 
    } 
  })

方案三:动态申请acl权限,保存图片到图库

PhotoAccessHelper.createAsset接口 需要ohos.permission.WRITE_IMAGEVIDEO权限,ohos.permission.WRITE_IMAGEVIDEO权限 为系统等级,该权限当前可申请的场景与功能:应用需要克隆、备份或同步图片/视频类文件。   

权限等级 和 应用APL等级 是一一对应的。原则上,拥有低APL等级的应用默认无法申请更高等级的权限。访问控制列表ACL(Access Control List)提供了解决低等级应用访问高等级权限问题的特殊渠道。

允许ACL跨级别申请

当前仅支持部分权限通过应用市场(AGC)使用ACL的方式跨级别申请权限。在申请发布Profile时,同步提交申请ACL权限

系统权限均定义了“ACL使能”字段,如果应用需要使用跨级别权限时,需使用 ACL 方式来申请对应权限。在 应用权限列表 中标记“ACL使能:TRUE”的为支持ACL的权限。当该权限的ACL使能为TRUE,应用可以使用ACL方式跨级别申请该权限。

支持ACL权限

从DevEco Studio 4.0 Release版本起,针对HarmonyOS工程,DevEco Studio支持在调测阶段通过自动签名快速申请ACL权限。

效果图

  • 在module.json5文件中配置权限
  • requestPermissionsFromUser方法弹框向用户动态申请权限
  • 权限申请成功后通过createAsset接口创建图片文件

核心代码

代码语言:ts
AI代码解释
复制
"requestPermissions": [ 
{ 
  "name": "ohos.permission.WRITE_IMAGEVIDEO", 
"reason": "$string:app_name", 
"usedScene": { 
  "abilities": [ 
  "FormAbility" 
  ], 
  "when":"always" 
} 
} 
]
async requestPermissionsFn() { 
  Logger.info(TAG, `requestPermissionsFn entry`); 
  try { 
    //申请相册管理模块权限'ohos.permission.WRITE_IMAGEVIDEO' 
    this.atManager.requestPermissionsFromUser(this.appContext, [ 
      'ohos.permission.WRITE_IMAGEVIDEO' 
 
    ]).then(() => { 
      Logger.info(TAG, `request Permissions success!`); 
      //权限申请成功,保存到图库 
      this.SavePicture() 
 
    }) 
  } catch (err) { 
    Logger.info(TAG, `requestPermissionsFromUser call Failed! error: ${err.code}`); 
 
  } 
}
async SavePicture(): Promise<void> { 
  // 
  try { 
  let context = getContext(); 
  //获取相册管理模块的实例,用于访问和修改相册中的媒体文件 
  let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context); 
  //通过createAsset接口创建图片文件 
  let uri = await phAccessHelper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg'); // 创建媒体文件 
  console.info('createAsset successfully, uri: ' + uri); 
  let file = await fs.open(uri, fs.OpenMode.READ_WRITE || fs.OpenMode.CREATE); 
  // 使用uri打开文件,可以持续写入内容,写入过程不受时间限制 
  await fs.write(file.fd, arrayBuffer); 
  // 关闭文件 
  await fs.close(file); 
} catch (err) { 
  console.error('createAsset failed, message = ', err); 
}

写在最后

如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:

  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力;
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识;
  • 想要获取更多完整鸿蒙最新学习知识点,可关注B站:码牛课堂鸿蒙开发;

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Java50个关键字总结
abstract修饰类,这个类就是抽象类,抽象类中可以有非抽象变量和成员变量,也可以有普通方法、构造方法。但是不能实例化,只能被子类继承。 如果子类不是抽象类,则必须重写父类的抽象方法。
用户7886150
2020/12/13
6630
*Java中的关键字*
关键字是Java中的一些具有特定含义的单词,定义的变量名不能和关键字冲突。(下面按如下图所示的顺序进行学习)
一半是我
2020/04/17
7920
【java基础】java关键字总结及详解
Java关键字是电脑语言里事先定义的,有特别意义的标识符,有时又叫保留字,还有特别意义的变量。Java的关键字对Java的编译器有特殊的意义,他们用来表示一种数据类型,或者表示程序的结构等,关键字不能用作变量名、方法名、类名、包名和参数。
全栈程序员站长
2022/09/08
4840
盘点历届 Java 语言的关键字,一定有你不认识的
在 Java 编程语言中,关键字是具有特殊含义的保留字,它们用于表示语言中的特定功能和操作。
Java极客技术
2024/06/25
2230
盘点历届 Java 语言的关键字,一定有你不认识的
Java之Java关键字及其作用
private 关键字是访问控制修饰符,可以应用于类、方法或字段(在类中声明的变量)。 只能在声明 private(内部)类、方法或字段的类中引用这些类、方法或字段。在类的外部或者对于子类而言,它们是不可见的。 所有类成员的默认访问范围都是 package 访问,也就是说,除非存在特定的访问控制修饰符,否则,可以从同一个包中的任何类访问类成员。
全栈程序员站长
2022/06/30
9910
Java50个关键字总结
abstract修饰类,这个类就是抽象类,抽象类中可以有非抽象变量和成员变量,也可以有普通方法、构造方法。但是不能实例化,只能被子类继承。 如果子类不是抽象类,则必须重写父类的抽象方法。
用户7886150
2020/12/13
6260
java关键字_Java关键字
Java keywords are the reserved words that are used by the Java compiler. These keywords have special meaning to the Java compiler. The reserved keywords help us in writing code and help the compiler in understanding the code and create the bytecode.
用户7886150
2020/12/13
1.1K0
Java几个重要关键字的使用
跟类相关:package(包) class(类) abstract(抽象) extends(继承) implements(实现) interface(接口)
用户7886150
2020/12/13
3130
【JAVA-Day04】Java关键字和示例:深入了解常用关键字的用法
本文深入探讨了Java编程语言中的常用关键字及其示例用法。我们从基本的数据类型声明开始,逐步介绍了控制流、异常处理、多线程、类继承等多个关键字的实际应用。通过详细的示例代码,读者将能够更好地理解这些关键字的功能和用法,为Java编程提供了坚实的基础。无论是新手还是有经验的开发人员,都可以从本文中获得有关Java关键字的重要知识和实用技巧。
默 语
2024/11/20
1930
干货——详解Java中的关键字
在平时编码中,我们可能只注意了这些static,final,volatile等关键字的使用,忽略了他们的细节,更深层次的意义。
Janti
2018/08/01
4400
干货——详解Java中的关键字
Java中this关键字的作用和用法
这段代码中,创建了一个类Student,有成员变量name与成员方法SetName(String name),由于成员方法接收的形参名称与成员变量相同,都是name,所以,这里可以使用this关键字来调用本类中的成员变量。其作用可以简单的这么理解:this关键字就是调用本类中的成员变量。
全栈程序员站长
2022/09/12
6170
Java关键字和相关疑问总结
Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。
李玺
2021/11/22
5011
【Java基础教程】标识符与关键字
Java中的包名、类名、方法名、参数名、变量名等都需要用一个符号来标识 命名规则 ①可由大小写字母、数字、下划线、美元符号组成 ②必须以字母、下划线、美元符号开头 ③严格区分大小写字母 ④长度无限制 ⑤不能与关键字重名
hacker707
2022/11/27
6430
【Java基础教程】标识符与关键字
Java关键字final、static总结与对比
Java关键字final有“不可改变的”或者“终态的”含义,它可以修饰非抽象类、非抽象类成员方法和变量。你可能出于两种理解而需要阻止改变:设计或效率。
chenchenchen
2022/03/09
9570
Java关键字final、static总结与对比
Java this 关键字用法
构造方法是一个类的对象在通过new关键字创建时自动调用的,在程序中不能向调用其他方法一样通过方法名(也就是类名)来调用。但如果一个类有多个构造方法,可以在一个构造方法中通过this(paras…)来调用其他的构造方法。 使用this来调用其他构造方法有如下几个约束。 1) 只能在构造方法中通过this来调用其他构造方法,普通方法中不能使用。 2) 不能通过this递归调用构造方法,即不能在一个构造方法中通过this直接或间接调用该构造方法本身。 例如:
全栈程序员站长
2022/09/13
2380
2.7w字!2021 最新版!Java基础面试题/知识点总结!(上)
这篇《Java 基础知识总结》是 JavaGuide 上阅读量最高的一篇文章,由于我对其进行了重构完善并且修复了很多小问题,所以,在腾讯云社区再同步一下!
Guide哥
2021/04/25
4700
java之static关键字
static方法: 1、定义: 《java编程思想》中提到:static方法就是没有this的方法,在static方法内部不能调用非静态方法,反过来是可以的,而且可以在没有创建任何对象的情况下,仅仅通过类本身来调用static方法,这实际上正是static方法的用途 2:概念: static方法一般称作静态方法,由于静态方法不依赖于对象就可以访问,所以也就没有this,并且static修饰的方法不能调用非静态成员变量和非静态成员方法,但是非静态成员方法可以调用静态成员方法 static变量: 定
说故事的五公子
2019/09/11
4280
深入理解static关键字
static 是我们日常生活中经常用到的关键字,也是 Java 中非常重要的一个关键字,static 可以修饰变量、方法、做静态代码块、静态导包等,下面我们就来具体聊一聊这个关键字,我们先从基础开始,从基本用法入手,然后分析其原理、优化等。
程序员Leo
2023/08/02
3760
深入理解static关键字
Java基础(十):关键字static、代码块、关键字final
Java微观世界
2025/01/21
1510
Java基础(十):关键字static、代码块、关键字final
Java关键字final、static使用总结
一、final 根据程序上下文环境,Java关键字final有“这是无法改变的”或者“终态的”含义,它可以修饰非抽象类、非抽象类成员方法和变量。你可能出于两种理解而需要阻止改变:设计或效率。 final类不能被继承,没有子类,final类中的方法默认是final的。 final方法不能被子类的方法覆盖,但可以被继承。 final成员变量表示常量,只能被赋值一次,赋值后值不再改变。 final不能用于修饰构造方法。
用户1112962
2018/07/03
8430
相关推荐
Java50个关键字总结
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档