1# 获取图库中的图片并显示在Image组件中 2 3## 场景说明 4拉起用户图库,选择图片并上传显示在应用界面中是比较常见的场景,比如上传用户头像、将图片上传社交平台、购物应用中上传图片评价等等。本文以上传用户头像为例介绍如何获取用户图库中的图片并显示在应用界面中。 5 6## 效果呈现 7本例最终效果图如下。 8 9效果说明:点击头像处的相机,拉起用户图库,选择要上传的图片,点击上传,头像随即更新为上传后的图片。 10 11 12 13 14## 环境要求 15本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发: 16 17- IDE: DevEco Studio 4.0 Release 18- SDK: Ohos_sdk_public 4.0.10.13 (API Version 10 Release) 19 20## 实现思路 21本例的包含的关键操作及其实现方案如下: 22- 拉起用户图库选择图片:使用图片类用户文件选择器PhotoViewPicker拉起用户图库并选择图片,与此同时获取到选中图片的uri。由于图库中图片属于系统资源,可被其他程序删除,修改,最终造成图库中uri路径失效,因此通常是将获取的图片资源推送至应用沙箱中指定路径或上传应用服务器中。本文采用选择图库图片后将图片拷贝至应用沙箱中指定路径中。 23- 获取图片在应用沙箱中的uri后,可通过Image组件对图片进行显示,Image组件支持加载PixelMap、ResourceStr和DrawableDescriptor类型的数据源,支持png、jpg、bmp、svg和gif类型的图片格式。 24- 对于用户头像应用场景下,部分应用可能需要支持对图片进行优化,编辑等(本例中不做详细介绍,具体操作请查阅官方文档中图片处理章节),因此本例中将获取的图片转换为PixelMap类型,用于Image组件展示。本例通过图片的uri获取到图片文件的fd(文件描述符),再使用createImageSource和createPixelMap方法将fd转换为pixelmap。 25 26 27## 开发步骤 28本例详细开发步骤如下,开发步骤中仅展示相关步骤代码,全量代码请参考完整代码章节的内容。 291. 使用PhotoViewPicker拉起用户图库选择图片,并获取到图片的uri,然后将被选择图片拷贝到应用沙箱中的指定位置,获取沙箱中的uri。 30 ```ts 31 // 定义一个函数用于点击头像时拉起用户图库 32 async pickProfile() { 33 const photoSelectOptions = new picker.PhotoSelectOptions(); 34 // 设置选择的文件类型为图片类型 35 photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE; 36 // 一次只能选择一张图片 37 photoSelectOptions.maxSelectNumber = 1; 38 const photoViewPicker = new picker.PhotoViewPicker(); 39 // 拉起图库,获取选中图片的uri,并将选择图片拷贝至应用沙箱指定位置 40 await photoViewPicker.select(photoSelectOptions) 41 .then((photoSelectResult: picker.PhotoSelectResult) => { 42 // 获取选中图片的uri 43 let imageUri = photoSelectResult.photoUris[0]; 44 console.info('photoViewPicker.select to file succeed and uris are:' + imageUri); 45 let context = getContext() 46 //获取应用通用文件路径 47 let filesDir = context.filesDir 48 let fileName = "userProfileImage" 49 //获取沙箱中文件路径 50 let path = filesDir + "/" + fileName + "." + imageUri.split(".")[1] 51 let file = fs.openSync(imageUri, fs.OpenMode.READ_ONLY) 52 let file2 = fs.openSync(path, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE) 53 //完成图片拷贝 54 fs.copyFileSync(file.fd, file2.fd) 55 fs.closeSync(file.fd) 56 fs.closeSync(file2.fd) 57 //获取图片沙箱路径对应的uri 58 this.uri = fileUri.getUriFromPath(path) 59 //获取图片对应的PixelMap 60 this.getPixelMap() 61 }) 62 .catch((err: BusinessError) => { 63 console.error('MinePage', `Invoke photoViewPicker.select failed, code is ${err.code}, message is ${err.message}`); 64 }) 65 } 66 ``` 67 68 > **注意:** 69 > 70 >由于photoViewPicker.select返回的uri权限是只读权限,因此需要注意以下三点: 71 > 72 >1. 在使用fs.openSync方法时,mode只能选择OpenMode.READ_ONLY,获取的文件进对象只有读权限。 73 >2. 使用fs.copyFileSync进行拷贝操作时,如果直接使用fs.copyFileSync(file.fd, path) 方式进行拷贝时,path处新图片权限与file权限一致,在本例中即为只有读权限。如果希望新图片有读写权限,建议采用本文方式进行处理。此外如果使用fs.copyFileSync接口有文件覆盖场景时,主要注意目标文件必须有写权限,不然会报13900012 Permission denied 错误。 74 >3. 开发过程中,如果拷贝完成后,无法确定拷贝是否成功,可通过开发工具中的Terminal执行 hdc shell 进入工程机查看具体路径下的文件及权限 75 76 77 782. 使用获取到的uri将图片解码为pixelmap以便显示在Image组件中。 79 ```ts 80 // 定义获取图片pixelmap的函数 81 getPixelMap(){ 82 // 通过uri打开图片文件,获取文件fd 83 let file = fs.openSync(this.uri, fs.OpenMode.READ_WRITE); 84 const imageSourceApi = image.createImageSource(file.fd); 85 // 将图片解码为pixelmap 86 imageSourceApi.createPixelMap().then(pixelmap => { 87 // 用自定义变量profileImage接收pixelmap 88 this.profileImage = pixelmap 89 console.log('Succeeded in creating pixelmap object through image decoding parameters.'); 90 }).catch((err) => { 91 console.log('Failed to create pixelmap object through image decoding parameters.'); 92 }) 93 } 94 ``` 95 963. 通过pixelmap将图片传入Image组件,完成头像上传更新。 97 ```ts 98 // 通过自定义变量profileImage将图片pixelmap传入Image组件 99 Image(this.profileImage) 100 .alt($r('app.media.ic_user_portrait')) 101 .width(108) 102 .height(108) 103 .objectFit(ImageFit.Cover) 104 .borderRadius(54) 105 .onClick(() => { 106 this.pickProfile() 107 }) 108 ``` 109 110## 完整代码 111更新头像的完整代码如下: 112```ts 113// PhotoPixelmapTransfer.ets 114import picker from '@ohos.file.picker'; 115import fs from '@ohos.file.fs'; 116import image from '@ohos.multimedia.image'; 117import { BusinessError } from '@ohos.base'; 118import fileUri from '@ohos.file.fileuri'; 119 120@Entry 121@Component 122export struct MinePage { 123 @State profileSrc: Resource = $r('app.media.ic_user_portrait') 124 @State profileImage: PixelMap = new Object() as PixelMap 125 @State uri: string = '' 126 @State flag: number = 0 127 128 // 选择头像图片 129 // 定义一个函数用于点击头像时拉起用户图库 130 async pickProfile1() { 131 const photoSelectOptions = new picker.PhotoSelectOptions(); 132 // 设置选择的文件类型为图片类型 133 photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE; 134 // 一次只能选择一张图片 135 photoSelectOptions.maxSelectNumber = 1; 136 let uri = ""; 137 const photoViewPicker = new picker.PhotoViewPicker(); 138 // 拉起图库,获取选中图片的uri 139 photoViewPicker.select(photoSelectOptions).then((photoSelectResult) => { 140 // 获取选中图片的uri 141 uri = photoSelectResult.photoUris[0]; 142 this.uri = uri 143 this.flag = 1 144 console.info('photoViewPicker.select to file succeed and uris are:' + uri) 145 }).catch((err: BusinessError) => { 146 console.error(`Invoke photoViewPicker.select failed, code is ${err.code}, message is ${err.message}`); 147 }) 148 this.getPixelMap() 149 } 150 151 // 定义一个函数用于点击头像时拉起用户图库 152 async pickProfile() { 153 const photoSelectOptions = new picker.PhotoSelectOptions(); 154 // 设置选择的文件类型为图片类型 155 photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE; 156 // 一次只能选择一张图片 157 photoSelectOptions.maxSelectNumber = 1; 158 const photoViewPicker = new picker.PhotoViewPicker(); 159 // 拉起图库,获取选中图片的uri,并将选择图片拷贝至应用沙箱指定位置 160 photoViewPicker.select(photoSelectOptions) 161 .then((photoSelectResult: picker.PhotoSelectResult) => { 162 // 获取选中图片的uri 163 let imageUri = photoSelectResult.photoUris[0]; 164 console.info('photoViewPicker.select to file succeed and uris are:' + imageUri); 165 let context = getContext() 166 //获取应用通用文件路径 167 let filesDir = context.filesDir 168 let fileName = "userProfileImage" 169 //获取沙箱中文件路径 170 let path = filesDir + "/" + fileName + "." + imageUri.split(".")[1] 171 let file = fs.openSync(imageUri, fs.OpenMode.READ_ONLY) 172 let file2 = fs.openSync(path, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE) 173 //完成图片拷贝 174 fs.copyFileSync(file.fd, file2.fd) 175 fs.closeSync(file.fd) 176 fs.closeSync(file2.fd) 177 //获取图片沙箱路径对应的uri 178 this.uri = fileUri.getUriFromPath(path) 179 //获取图片对应的PixelMap 180 this.getPixelMap() 181 }) 182 .catch((err: BusinessError) => { 183 console.error('MinePage', `Invoke photoViewPicker.select failed, code is ${err.code}, message is ${err.message}`); 184 }) 185 } 186 187 // 获取图片的pixelmap 188 getPixelMap(){ 189 // 通过uri打开图片文件,获取文件fd 190 let file = fs.openSync(this.uri, fs.OpenMode.READ_WRITE); 191 const imageSourceApi = image.createImageSource(file.fd); 192 // 将图片解码为pixelmap 193 imageSourceApi.createPixelMap().then(pixelmap => { 194 this.profileImage = pixelmap 195 console.log('Succeeded in creating pixelmap object through image decoding parameters.'); 196 }).catch((err:BusinessError) => { 197 console.log('Failed to create pixelmap object through image decoding parameters.'); 198 }) 199 } 200 201 build() { 202 Column() { 203 Image(this.profileImage) 204 .alt($r('app.media.ic_user_portrait')) 205 .width(108) 206 .height(108) 207 .objectFit(ImageFit.Cover) 208 .borderRadius(54) 209 .onClick(() => { 210 this.pickProfile() 211 }) 212 if (!this.uri) { 213 Text('点击上传头像') 214 .fontSize(24) 215 .margin({ top: 10 }) 216 } 217 } 218 .alignItems(HorizontalAlign.Center) 219 .justifyContent(FlexAlign.Center) 220 .width('100%') 221 .height('100%') 222 } 223} 224``` 225 226## 参考 227- [选择用户图片或视频类文件](../application-dev/file-management/select-user-file.md) 228- [将图片解码为pixelmap](../application-dev/media/image-decoding.md) 229- [@ohos.file.fs (文件管理)](../application-dev/reference/apis-core-file-kit/js-apis-file-fs.md) 230- [Image](../application-dev/reference/apis-arkui/arkui-ts/ts-basic-components-image.md) 231 232