37 相册读取与图片选择:PhotoViewPicker 实战
37 相册读取与图片选择:PhotoViewPicker 实战
前言

图:37 相册读取与图片选择:PhotoViewPicker 实战 运行效果截图(HarmonyOS NEXT)
除了直接拍照,从相册选择已有图片也是常见的图片获取方式。鸿蒙提供了 PhotoViewPicker——一个系统级的图片选择器,让用户可以从相册中选择一张或多张图片。
本文以"鹿鹿·笔迹心理分析"项目中 [CapturePage.ets](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/pages/CapturePage.ets) 的 pickFromAlbum() 方法为例,深入解析 PhotoViewPicker 的使用方法和相册选择流程。
鸿蒙官方·PhotoViewPicker 文档:developer.huawei.com
项目源码仓库:harmony-app GitHub

图:PhotoViewPicker 相册选择流程——创建选择器 → 配置选项 → 获取 URI
一、PhotoViewPicker 基础
1.1 模块导入
import { photoAccessHelper } from '@kit.MediaLibraryKit'
1.2 基本使用
private async pickFromAlbum() {
try {
// ① 创建选择器
const photoPicker = new photoAccessHelper.PhotoViewPicker()
// ② 配置选项
const pickerOptions: photoAccessHelper.PhotoSelectOptions = {
maxSelectNumber: 1 // 最多选择 1 张
}
// ③ 打开选择器
const result: photoAccessHelper.PhotoSelectResult =
await photoPicker.select(pickerOptions)
// ④ 处理结果
if (result && result.photoUris && result.photoUris.length > 0) {
this.previewPath = result.photoUris[0]
this.hasPreview = true
this.source = 'gallery'
}
} catch (error) {
hilog.error(0x0000, TAG, '相册选择失败: %s', JSON.stringify(error))
}
}
二、PhotoSelectOptions 配置
2.1 选项详解
interface PhotoSelectOptions {
maxSelectNumber: number // 最大选择数量
MIME?: string[] // 文件类型过滤
isEdit?: boolean // 是否允许编辑
}
2.2 实际配置
| 参数 | 本项目值 | 说明 |
|---|---|---|
maxSelectNumber |
1 |
一次分析一张笔迹图片 |
MIME |
未指定 | 默认所有图片类型 |
isEdit |
未指定 | 选择后不编辑 |
2.3 扩展配置示例
// 多选 + MIME 过滤
const pickerOptions: photoAccessHelper.PhotoSelectOptions = {
maxSelectNumber: 9,
MIME: ['image/jpeg', 'image/png'],
}
三、PhotoSelectResult 结果处理
3.1 返回结构
interface PhotoSelectResult {
photoUris: string[] // 所选图片的 URI 列表
}
3.2 URI 的使用
result.photoUris[0]
// 返回类似:content://media/external/images/media/12345
// 或:/data/storage/el2/base/files/...
// 这个 URI 可以直接用于:
// 1. image.createImageSource(uri) — 解码图像
// 2. Image(uri) — ArkUI 图片组件展示
// 3. fs.openSync(uri) — 文件操作
四、拍照/相册双模式切换
4.1 模式切换策略
CapturePage 支持两种来源——拍照和相册,通过 source 参数控制:
aboutToAppear() {
// 从路由参数读取来源
const params = this.getUIContext().getRouter().getParams() as Record<string, Object>
if (params && typeof params['source'] === 'string') {
this.source = params['source'] as string
}
if (this.source === 'gallery') {
this.pickFromAlbum() // → 相册模式
} else {
this.initCamera() // → 相机模式
}
}
4.2 触发来源
// 跳转到 CapturePage — 拍照模式
this.getUIContext().getRouter().pushUrl({
url: 'pages/CapturePage',
params: { source: 'camera' }
})
// 跳转到 CapturePage — 相册模式
this.getUIContext().getRouter().pushUrl({
url: 'pages/CapturePage',
params: { source: 'gallery' }
})
4.3 UI 中的相册入口
在拍照页面底部,用户也可以点击相册按钮随时切换到相册选择:
// 拍照模式底栏中的相册按钮
Text('🖼')
.fontSize(22)
.fontColor('rgba(255,255,255,0.8)')
.onClick(() => this.pickFromAlbum())
五、MediaLibraryKit 的其他能力
5.1 获取相册列表
async function getAlbums(): Promise<photoAccessHelper.Album[]> {
const helper = photoAccessHelper.getPhotoAccessHelper(getContext(this))
const albums = await helper.getAlbums(
photoAccessHelper.AlbumType.USER,
photoAccessHelper.AlbumSubtype.USER_GENERIC
)
return albums
}
5.2 保存图片到相册
import { photoAccessHelper } from '@kit.MediaLibraryKit'
async function saveToAlbum(uri: string): Promise<void> {
const helper = photoAccessHelper.getPhotoAccessHelper(getContext(this))
// 将文件添加到系统相册
await helper.addAsset(uri)
}
六、PhotoViewPicker vs 自定义相册
| 对比 | PhotoViewPicker(项目使用) | 自定义相册 |
|---|---|---|
| 实现复杂度 | 低(3 行代码) | 高(权限 + 查询 + UI) |
| UI 一致性 | 系统原生相册 | 自定义 UI |
| 多选支持 | ✅ 内置 | 需要自行实现 |
| 权限要求 | 自动处理 | 需要 MANAGE_MEDIA |
| 推荐场景 | 快速集成 | 需定制 UI 的场景 |
七、路径与 URI 的处理
7.1 路径统一
拍照和相册选择的图片路径格式不同,但后续处理方式一致:
// 拍照:文件系统路径
this.previewPath = `/data/storage/el2/base/cache/capture_${Date.now()}.jpg`
// 相册:content:// URI
this.previewPath = result.photoUris[0] // content://media/...
// 两种路径传入 AnalyzingPage 后的使用
const imagePath = AppStorage.get<string>('capture_image_path') ?? ''
await HandwritingDao.create({
image_path: imagePath, // 原样保存路径
// ...
})
7.2 路径格式统一化
private normalizePath(uri: string): string {
if (uri.startsWith('content://')) {
// content:// URI → 复制到应用缓存目录
// 这需要读取文件内容并写入临时目录
return uri // 项目当前直接使用 URI
}
return uri // 已经是文件路径
}
八、权限相关
8.1 相册读取权限
使用 PhotoViewPicker 不需要申请 ohos.permission.READ_MEDIA 权限——系统选择器自动处理了授权。但如果使用 photoAccessHelper 的其他 API(如遍历相册),则需要声明权限:
{
"requestPermissions": [
{
"name": "ohos.permission.READ_MEDIA",
"reason": "$string:media_permission_reason",
"usedScene": { "abilities": ["EntryAbility"], "when": "inuse" }
}
]
}
8.2 权限对比
| 场景 | 需要权限 | 权限声明 |
|---|---|---|
| PhotoViewPicker.select() | ❌ 不需要 | 系统选择器自带授权 |
| photoAccessHelper.getAlbums() | ✅ 需要 | ohos.permission.READ_MEDIA |
| photoAccessHelper.addAsset() | ✅ 需要 | ohos.permission.WRITE_MEDIA |
总结
本文从 CapturePage 的 pickFromAlbum() 方法出发,完整解析了鸿蒙 PhotoViewPicker 的使用:
- 基本使用:
new PhotoViewPicker()→select(options)→result.photoUris - 选项配置:
maxSelectNumber控制选择数量 - 双模式切换:通过
source参数在拍照/相册间切换 - URI 处理:拍照返回文件路径,相册返回 content:// URI
- 权限优势:PhotoViewPicker 不需要额外权限声明
下一篇文章将介绍 图片压缩与本地存储——在保存图片时的质量和大小优化。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
参考资源:
- PhotoViewPicker API
- [CapturPage 相册选择源码](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/pages/CapturePage.ets)
- photoAccessHelper 开发指南
- @kit.MediaLibraryKit 模块
- media 权限声明
- HarmonyOS 开发文档
九、PhotoViewPicker 完整功能扩展
9.1 多选图片场景
async function pickMultipleImages(): Promise<string[]> {
const photoPicker = new photoAccessHelper.PhotoViewPicker()
const options: photoAccessHelper.PhotoSelectOptions = {
maxSelectNumber: 9, // 最多选 9 张
MIME: ['image/jpeg', 'image/png', 'image/heif'] // 仅图片格式
}
const result = await photoPicker.select(options)
return result.photoUris ?? []
}
9.2 DocumentViewPicker:选择文档
除了图片,鸿蒙还提供了 DocumentViewPicker 用于选择文档文件:
import { picker } from '@kit.CoreFileKit'
async function pickDocument(): Promise<string> {
const documentPicker = new picker.DocumentViewPicker()
const options: picker.DocumentSelectOptions = {
maxSelectNumber: 1,
fileSuffixFilters: ['.pdf', '.txt', '.docx'] // 只显示指定后缀的文件
}
const result = await documentPicker.select(options)
return result[0] ?? ''
}
9.3 URI 转文件路径
相册 URI(content://)需要通过 fs.open 获取文件描述符才能读取:
import fs from '@ohos.file.fs'
async function readImageFromUri(uri: string): Promise<ArrayBuffer> {
// 通过 URI 打开文件
const file = fs.openSync(uri, fs.OpenMode.READ_ONLY)
// 获取文件大小
const stat = fs.statSync(file.fd)
// 读取文件内容
const buffer = new ArrayBuffer(stat.size)
fs.readSync(file.fd, buffer)
fs.closeSync(file)
return buffer
}
9.4 相册访问能力对比
| 功能 | PhotoViewPicker | photoAccessHelper | DocumentViewPicker |
|---|---|---|---|
| 选择图片 | ✅ | ❌(需权限查询) | ❌ |
| 选择文档 | ❌ | ❌ | ✅ |
| 遍历相册 | ❌ | ✅(需 READ_MEDIA) | ❌ |
| 保存到相册 | ❌ | ✅(需 WRITE_MEDIA) | ❌ |
| 权限要求 | 无需额外权限 | 需要 READ_MEDIA | 无需额外权限 |
| 推荐场景 | 用户主动选择 | 程序化批量访问 | 用户选择文档 |
9.5 相册操作完整代码模板
// 完整的相册选择 + 图片处理模板
@Component
struct ImagePickerDemo {
@State selectedImageUri: string = ''
@State isLoading: boolean = false
private async pickAndProcess(): Promise<void> {
this.isLoading = true
try {
// ① 选择图片
const picker = new photoAccessHelper.PhotoViewPicker()
const result = await picker.select({ maxSelectNumber: 1 })
if (!result.photoUris || result.photoUris.length === 0) return
const uri = result.photoUris[0]
// ② 解码 PixelMap
const source = image.createImageSource(uri)
const pixelMap = await source.createPixelMap({
desiredSize: { width: 1024, height: 1024 }
})
// ③ 压缩保存到缓存
const outputPath = `${getContext(this).cacheDir}/selected_${Date.now()}.jpg`
const packer = image.createImagePacker()
const buffer = await packer.packing(pixelMap, { format: 'image/jpeg', quality: 85 })
const file = fs.openSync(outputPath, fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY)
fs.writeSync(file.fd, buffer)
fs.closeSync(file)
// ④ 释放内存
pixelMap.release()
this.selectedImageUri = outputPath
} catch (err) {
hilog.error(0x0000, 'ImagePicker', '选图失败: %s', JSON.stringify(err))
} finally {
this.isLoading = false
}
}
build() {
Column() {
if (this.selectedImageUri) {
Image(this.selectedImageUri).width(200).height(200).objectFit(ImageFit.Cover)
}
Button('从相册选择')
.onClick(() => this.pickAndProcess())
}
}
}
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
更多推荐




所有评论(0)