
鸿蒙5 ArkUI媒体功能开发:相机拍照与相册管理实战
鸿蒙5的ArkUI框架提供了强大的媒体功能支持,包括相机调用、照片拍摄和相册管理。本文将全面介绍如何在鸿蒙5应用中实现相机拍照和相册管理功能,包含详细的代码示例和最佳实践。
相机功能开发基础
- 权限配置
首先需要在module.json5中声明相机和存储权限:
{
“module”: {
“requestPermissions”: [
{
“name”: “ohos.permission.CAMERA”,
“reason”: “需要访问相机进行拍照”
},
{
“name”: “ohos.permission.READ_IMAGEVIDEO”,
“reason”: “需要读取相册中的照片”
},
{
“name”: “ohos.permission.WRITE_IMAGEVIDEO”,
“reason”: “需要保存照片到相册”
}
]
}
}
2. 相机基础API
鸿蒙5提供了CameraKit API来访问相机功能:
import camera from ‘@ohos.multimedia.camera’;
import image from ‘@ohos.multimedia.image’;
import photoAccessHelper from ‘@ohos.file.photoAccessHelper’;
@Entry
@Component
struct CameraPage {
private cameraManager: camera.CameraManager = camera.getCameraManager();
private cameraInput: camera.CameraInput | null = null;
private previewOutput: camera.PreviewOutput | null = null;
private photoOutput: camera.PhotoOutput | null = null;
@State previewSurfaceId: string = ‘’;
@State isBackCamera: boolean = true;
@State flashMode: camera.FlashMode = camera.FlashMode.FLASH_MODE_OFF;
@State capturedImage: string = ‘’;
aboutToAppear() {
this.initCamera();
}
aboutToDisappear() {
this.releaseCamera();
}
private async initCamera() {
try {
// 获取相机列表
const cameras = this.cameraManager.getSupportedCameras();
if (cameras.length === 0) {
console.error(‘没有可用的相机’);
return;
}
// 创建相机输入
const cameraId = this.isBackCamera ? cameras[0].cameraId : cameras[1]?.cameraId || cameras[0].cameraId;
this.cameraInput = this.cameraManager.createCameraInput(cameraId);
// 创建预览输出
const display = getContext(this).display;
const previewProfile = this.cameraManager.getSupportedOutputCapability(
cameraId,
camera.Profile.PROFILE_PREVIEW
).previewProfiles[0];
this.previewOutput = this.cameraManager.createPreviewOutput(
previewProfile,
display
);
// 创建拍照输出
const photoProfile = this.cameraManager.getSupportedOutputCapability(
cameraId,
camera.Profile.PROFILE_PHOTO
).photoProfiles[0];
this.photoOutput = this.cameraManager.createPhotoOutput(photoProfile);
// 打开相机
await this.cameraInput.open();
// 开始预览
this.previewSurfaceId = this.previewOutput.getSurfaceId();
await this.previewOutput.start();
// 配置相机会话
const session = this.cameraManager.createCaptureSession();
session.beginConfig();
session.addInput(this.cameraInput);
session.addOutput(this.previewOutput);
session.addOutput(this.photoOutput);
await session.commitConfig();
await session.start();
} catch (err) {
console.error(`相机初始化失败: ${err.code}, ${err.message}`);
}
}
private releaseCamera() {
if (this.previewOutput) {
this.previewOutput.release();
}
if (this.photoOutput) {
this.photoOutput.release();
}
if (this.cameraInput) {
this.cameraInput.close();
}
}
build() {
Column() {
// 相机预览
XComponent({
id: ‘cameraPreview’,
type: ‘surface’,
controller: this.previewSurfaceId
})
.width(‘100%’)
.height(‘70%’)
// 控制面板
Row({ space: 20 }) {
Button('切换相机')
.onClick(() => {
this.isBackCamera = !this.isBackCamera;
this.releaseCamera();
this.initCamera();
})
Button(this.flashMode === camera.FlashMode.FLASH_MODE_OFF ? '开启闪光灯' : '关闭闪光灯')
.onClick(() => {
this.flashMode = this.flashMode === camera.FlashMode.FLASH_MODE_OFF
? camera.FlashMode.FLASH_MODE_ON
: camera.FlashMode.FLASH_MODE_OFF;
this.photoOutput?.setFlashMode(this.flashMode);
})
Button('拍照')
.onClick(() => this.capturePhoto())
}
.width('100%')
.justifyContent(FlexAlign.Center)
.margin({ top: 20 })
// 显示拍摄的照片
if (this.capturedImage) {
Image(this.capturedImage)
.width('90%')
.height(200)
.margin({ top: 20 })
}
}
.width('100%')
.height('100%')
.backgroundColor('#000000')
}
private async capturePhoto() {
if (!this.photoOutput) return;
try {
const photo = await this.photoOutput.capture();
const imageBuffer = await photo.main.getComponent(image.ComponentType.JPEG);
const imageData = await imageBuffer.getBytes();
// 保存照片到相册
const photoHelper = photoAccessHelper.getPhotoAccessHelper(getContext(this));
const uri = await photoHelper.createAsset(
photoAccessHelper.PhotoType.IMAGE,
'jpg',
`photo_${new Date().getTime()}.jpg`
);
await photoHelper.writeImage(uri, imageData);
this.capturedImage = uri;
// 释放照片资源
photo.release();
} catch (err) {
console.error(`拍照失败: ${err.code}, ${err.message}`);
}
}
}
相册管理功能实现
- 相册浏览与图片选择
@Entry
@Component
struct AlbumPage {
@State photoList: Array<photoAccessHelper.PhotoAsset> = [];
@State selectedImage: string = ‘’;
private photoHelper: photoAccessHelper.PhotoAccessHelper =
photoAccessHelper.getPhotoAccessHelper(getContext(this));
aboutToAppear() {
this.loadPhotos();
}
private async loadPhotos() {
try {
const fetchOptions: photoAccessHelper.FetchOptions = {
fetchColumns: [
photoAccessHelper.PhotoKeys.URI,
photoAccessHelper.PhotoKeys.DISPLAY_NAME,
photoAccessHelper.PhotoKeys.DATE_ADDED
],
orderBy: ${photoAccessHelper.PhotoKeys.DATE_ADDED} DESC
,
};
this.photoList = await this.photoHelper.getAssets(fetchOptions);
} catch (err) {
console.error(`加载照片失败: ${err.code}, ${err.message}`);
}
}
build() {
Column() {
if (this.selectedImage) {
Image(this.selectedImage)
.width(‘100%’)
.height(300)
.objectFit(ImageFit.Contain)
.margin({ bottom: 20 })
}
Grid() {
ForEach(this.photoList, (item: photoAccessHelper.PhotoAsset) => {
GridItem() {
Image(item.uri)
.width('100%')
.aspectRatio(1)
.objectFit(ImageFit.Cover)
.onClick(() => {
this.selectedImage = item.uri;
})
}
})
}
.columnsTemplate('1fr 1fr 1fr')
.columnsGap(5)
.rowsGap(5)
.height('70%')
}
.width('100%')
.height('100%')
.padding(10)
}
}
2. 图片编辑与删除功能
@Component
struct PhotoDetailPage {
@Prop imageUri: string
@Link photoList: Array<photoAccessHelper.PhotoAsset>
private photoHelper: photoAccessHelper.PhotoAccessHelper =
photoAccessHelper.getPhotoAccessHelper(getContext(this));
@State isEditing: boolean = false
@State editText: string = ‘’
@State filters: Array<{name: string, value: string}> = [
{name: ‘无’, value: ‘none’},
{name: ‘黑白’, value: ‘grayscale’},
{name: ‘复古’, value: ‘sepia’},
{name: ‘高对比’, value: ‘high-contrast’}
]
@State selectedFilter: string = ‘none’
build() {
Column() {
Image(this.imageUri)
.width(‘100%’)
.height(400)
.objectFit(ImageFit.Contain)
.filter(this.selectedFilter === ‘none’ ? ‘’ :
this.selectedFilter === ‘grayscale’ ? ‘grayscale(100%)’ :
this.selectedFilter === ‘sepia’ ? ‘sepia(100%)’ :
‘contrast(200%)’)
if (this.isEditing) {
TextInput({ placeholder: '添加描述...', text: this.editText })
.onChange((value: string) => {
this.editText = value
})
.width('90%')
.margin({ top: 20 })
Button('保存')
.onClick(() => this.saveEdit())
.width('60%')
.margin({ top: 10 })
}
Row({ space: 15 }) {
Button('编辑')
.onClick(() => {
this.isEditing = true
this.editText = ''
})
Button('删除')
.onClick(() => this.deletePhoto())
Picker({ range: this.filters.map(f => f.name) })
.onChange((index: number) => {
this.selectedFilter = this.filters[index].value
})
.width(120)
}
.margin({ top: 20 })
}
.width('100%')
.height('100%')
.padding(10)
}
private async saveEdit() {
try {
const asset = await this.photoHelper.getAsset(this.imageUri)
await this.photoHelper.modifyAsset(asset.uri, {
[photoAccessHelper.PhotoKeys.DESCRIPTION]: this.editText
})
this.isEditing = false
promptAction.showToast({ message: ‘修改已保存’ })
} catch (err) {
console.error(保存失败: ${err.code}, ${err.message}
)
promptAction.showToast({ message: ‘保存失败’ })
}
}
private async deletePhoto() {
try {
const asset = await this.photoHelper.getAsset(this.imageUri)
await this.photoHelper.deleteAsset(asset.uri)
// 更新相册列表
this.photoList = this.photoList.filter(item => item.uri !== this.imageUri)
promptAction.showToast({ message: '照片已删除' })
} catch (err) {
console.error(`删除失败: ${err.code}, ${err.message}`)
promptAction.showToast({ message: '删除失败' })
}
}
}
相机高级功能实现
- 人脸检测与美化
@Entry
@Component
struct AdvancedCameraPage {
// …基础相机代码同上
@State faceRectangles: Array<{x: number, y: number, width: number, height: number}> = []
@State beautyLevel: number = 0
private faceDetector: image.FaceDetector | null = null
aboutToAppear() {
this.initCamera()
this.initFaceDetector()
}
private async initFaceDetector() {
this.faceDetector = await image.createFaceDetector()
// 设置人脸检测回调
this.previewOutput?.on('faceDetection', (faces) => {
this.faceRectangles = faces.map(face => ({
x: face.boundingBox.x,
y: face.boundingBox.y,
width: face.boundingBox.width,
height: face.boundingBox.height
}))
})
}
build() {
Column() {
// 相机预览层
Stack() {
XComponent({
id: ‘cameraPreview’,
type: ‘surface’,
controller: this.previewSurfaceId
})
.width(‘100%’)
.height(‘70%’)
// 绘制人脸检测框
ForEach(this.faceRectangles, (rect) => {
Rect()
.width(rect.width)
.height(rect.height)
.position({ x: rect.x, y: rect.y })
.strokeWidth(2)
.strokeColor(Color.Red)
})
}
// 美颜控制
Slider({
value: this.beautyLevel,
min: 0,
max: 100,
step: 1,
style: SliderStyle.OutSet
})
.onChange((value: number) => {
this.beautyLevel = value
this.applyBeautyEffect()
})
.width('80%')
.margin({ top: 20 })
Text(`美颜级别: ${this.beautyLevel}`)
}
}
private applyBeautyEffect() {
if (!this.faceDetector) return
// 设置美颜参数
this.faceDetector.setBeautyOptions({
smoothLevel: this.beautyLevel / 100,
whiteningLevel: this.beautyLevel / 150,
shrinkFaceLevel: this.beautyLevel / 200,
enlargeEyeLevel: this.beautyLevel / 200
})
}
}
2. 专业模式相机
@Entry
@Component
struct ProCameraPage {
// …基础相机代码
@State exposureCompensation: number = 0
@State iso: number = 100
@State shutterSpeed: number = 1/30
@State whiteBalance: camera.WhiteBalanceMode = camera.WhiteBalanceMode.WB_MODE_AUTO
build() {
Column() {
// 相机预览…
// 专业控制面板
Column({ space: 10 }) {
Row() {
Text('曝光补偿:')
Slider({
value: this.exposureCompensation,
min: -2,
max: 2,
step: 0.1
})
.onChange((value: number) => {
this.exposureCompensation = value
this.cameraInput?.setExposureCompensation(value)
})
.width('70%')
}
Row() {
Text('ISO:')
Slider({
value: this.iso,
min: 100,
max: 3200,
step: 50
})
.onChange((value: number) => {
this.iso = value
this.cameraInput?.setIso(value)
})
.width('70%')
}
Row() {
Text('快门速度:')
Slider({
value: this.shutterSpeed,
min: 1/8000,
max: 30,
step: 0.1
})
.onChange((value: number) => {
this.shutterSpeed = value
this.cameraInput?.setShutterSpeed(value)
})
.width('70%')
}
Picker({ range: ['自动', '日光', '阴天', '荧光灯', '白炽灯'] })
.onChange((index: number) => {
const modes = [
camera.WhiteBalanceMode.WB_MODE_AUTO,
camera.WhiteBalanceMode.WB_MODE_SUNNY,
camera.WhiteBalanceMode.WB_MODE_CLOUDY,
camera.WhiteBalanceMode.WB_MODE_FLUORESCENT,
camera.WhiteBalanceMode.WB_MODE_INCANDESCENT
]
this.whiteBalance = modes[index]
this.cameraInput?.setWhiteBalance(this.whiteBalance)
})
.width(150)
}
.margin({ top: 20 })
}
}
}
相册高级功能
- 照片分类与搜索
@Entry
@Component
struct AlbumCategoryPage {
@State categories: Array<{name: string, count: number}> = []
@State currentCategory: string = ‘全部’
@State photoList: Array<photoAccessHelper.PhotoAsset> = []
private photoHelper: photoAccessHelper.PhotoAccessHelper =
photoAccessHelper.getPhotoAccessHelper(getContext(this));
aboutToAppear() {
this.loadCategories()
this.loadPhotos()
}
private async loadCategories() {
const albums = await this.photoHelper.getAlbums()
this.categories = [
{ name: ‘全部’, count: await this.getPhotoCount() },
…albums.map(album => ({
name: album.albumName,
count: album.count
}))
]
}
private async getPhotoCount(): Promise<number> {
const fetchOptions: photoAccessHelper.FetchOptions = {
fetchColumns: [photoAccessHelper.PhotoKeys.ID]
}
return (await this.photoHelper.getAssets(fetchOptions)).length
}
private async loadPhotos() {
const fetchOptions: photoAccessHelper.FetchOptions = {
fetchColumns: [
photoAccessHelper.PhotoKeys.URI,
photoAccessHelper.PhotoKeys.DISPLAY_NAME,
photoAccessHelper.PhotoKeys.DATE_ADDED
],
orderBy: ${photoAccessHelper.PhotoKeys.DATE_ADDED} DESC
,
}
if (this.currentCategory !== '全部') {
fetchOptions.selections = `${photoAccessHelper.PhotoKeys.ALBUM_NAME} = ?`
fetchOptions.selectionArgs = [this.currentCategory]
}
this.photoList = await this.photoHelper.getAssets(fetchOptions)
}
build() {
Column() {
// 分类标签
Scroll(.horizontal) {
Row({ space: 15 }) {
ForEach(this.categories, (category) => {
Column() {
Text(category.name)
.fontColor(this.currentCategory === category.name ? ‘#007DFF’ : ‘#666666’)
Text(${category.count}
)
.fontSize(12)
.fontColor(‘#999999’)
}
.onClick(() => {
this.currentCategory = category.name
this.loadPhotos()
})
})
}
.padding(10)
}
// 搜索框
Search({ placeholder: '搜索照片...' })
.onChange((value: string) => this.searchPhotos(value))
.width('90%')
.margin(10)
// 照片网格
Grid() {
ForEach(this.photoList, (item) => {
GridItem() {
Image(item.uri)
.width('100%')
.aspectRatio(1)
.objectFit(ImageFit.Cover)
}
})
}
.columnsTemplate('1fr 1fr 1fr')
.columnsGap(5)
.rowsGap(5)
.height('70%')
}
}
private async searchPhotos(keyword: string) {
const fetchOptions: photoAccessHelper.FetchOptions = {
fetchColumns: [
photoAccessHelper.PhotoKeys.URI,
photoAccessHelper.PhotoKeys.DISPLAY_NAME,
photoAccessHelper.PhotoKeys.DESCRIPTION
],
selections: ${photoAccessHelper.PhotoKeys.DISPLAY_NAME} LIKE ? OR ${photoAccessHelper.PhotoKeys.DESCRIPTION} LIKE ?
,
selectionArgs: [%${keyword}%
, %${keyword}%
]
}
this.photoList = await this.photoHelper.getAssets(fetchOptions)
}
}
2. 照片批量操作
@Entry
@Component
struct BatchOperationPage {
@State photoList: Array<photoAccessHelper.PhotoAsset> = []
@State selectedPhotos: Array<string> = []
@State isSelectMode: boolean = false
private photoHelper: photoAccessHelper.PhotoAccessHelper =
photoAccessHelper.getPhotoAccessHelper(getContext(this));
aboutToAppear() {
this.loadPhotos()
}
private async loadPhotos() {
const fetchOptions: photoAccessHelper.FetchOptions = {
fetchColumns: [
photoAccessHelper.PhotoKeys.URI,
photoAccessHelper.PhotoKeys.DISPLAY_NAME
],
orderBy: ${photoAccessHelper.PhotoKeys.DATE_ADDED} DESC
}
this.photoList = await this.photoHelper.getAssets(fetchOptions)
}
build() {
Column() {
// 操作工具栏
Row({ space: 20 }) {
Button(this.isSelectMode ? ‘取消’ : ‘选择’)
.onClick(() => {
this.isSelectMode = !this.isSelectMode
if (!this.isSelectMode) {
this.selectedPhotos = []
}
})
if (this.isSelectMode) {
Button(`删除(${this.selectedPhotos.length})`)
.onClick(() => this.deleteSelected())
Button(`分享(${this.selectedPhotos.length})`)
.onClick(() => this.shareSelected())
Button(`添加到相册(${this.selectedPhotos.length})`)
.onClick(() => this.addToAlbum())
}
}
.padding(10)
// 照片网格
Grid() {
ForEach(this.photoList, (item) => {
GridItem() {
Stack() {
Image(item.uri)
.width('100%')
.aspectRatio(1)
.objectFit(ImageFit.Cover)
if (this.isSelectMode && this.selectedPhotos.includes(item.uri)) {
Image($r('app.media.ic_check'))
.width(24)
.height(24)
.position({ right: 5, top: 5 })
}
}
.onClick(() => {
if (this.isSelectMode) {
if (this.selectedPhotos.includes(item.uri)) {
this.selectedPhotos = this.selectedPhotos.filter(uri => uri !== item.uri)
} else {
this.selectedPhotos.push(item.uri)
}
} else {
// 进入详情页...
}
})
}
})
}
.columnsTemplate('1fr 1fr 1fr')
.columnsGap(5)
.rowsGap(5)
.height('80%')
}
}
private async deleteSelected() {
try {
await this.photoHelper.deleteAssets(this.selectedPhotos)
this.photoList = this.photoList.filter(item => !this.selectedPhotos.includes(item.uri))
this.selectedPhotos = []
promptAction.showToast({ message: ‘删除成功’ })
} catch (err) {
console.error(删除失败: ${err.code}, ${err.message}
)
promptAction.showToast({ message: ‘删除失败’ })
}
}
private async shareSelected() {
try {
await share.share({
uris: this.selectedPhotos,
type: ‘image/*’
})
} catch (err) {
console.error(分享失败: ${err.code}, ${err.message}
)
promptAction.showToast({ message: ‘分享失败’ })
}
}
private async addToAlbum() {
const albums = await this.photoHelper.getAlbums()
const albumNames = albums.map(album => album.albumName)
ActionSheet.show({
title: '选择相册',
buttons: albumNames.map(name => ({ text: name }))
}).then((index: number) => {
if (index >= 0) {
this.moveToAlbum(albums[index].albumUri)
}
})
}
private async moveToAlbum(albumUri: string) {
try {
await this.photoHelper.addAssetsToAlbum(albumUri, this.selectedPhotos)
promptAction.showToast({ message: ‘添加成功’ })
this.selectedPhotos = []
this.isSelectMode = false
} catch (err) {
console.error(添加失败: ${err.code}, ${err.message}
)
promptAction.showToast({ message: ‘添加失败’ })
}
}
}
总结与最佳实践
鸿蒙5的媒体功能开发要点总结:
相机开发最佳实践:
合理管理相机生命周期(初始化、释放资源)
处理不同设备的兼容性问题
提供多种拍摄模式(普通、专业、美颜等)
优化预览流畅度和拍照响应速度
相册管理最佳实践:
实现高效的图片加载和缓存机制
支持多种分类和搜索方式
提供批量操作功能
优化大图浏览体验
性能优化建议:
// 使用缩略图提高列表性能
Image(item.uri)
.imageEffect({
thumbnail: {
width: 200,
height: 200,
interpolation: image.InterpolationMode.LOW
}
})
用户体验建议:
提供即时预览反馈
支持手势操作(缩放、滑动等)
添加过渡动画
处理权限拒绝场景
通过本文的代码示例和最佳实践,开发者可以充分利用鸿蒙5的ArkCompiler优化和媒体API,构建高性能的相机和相册应用。
