鸿蒙5 ArkUI媒体功能开发:相机拍照与相册管理实战

暗雨OL
发布于 2025-6-30 02:25
浏览
0收藏

鸿蒙5的ArkUI框架提供了强大的媒体功能支持,包括相机调用、照片拍摄和相册管理。本文将全面介绍如何在鸿蒙5应用中实现相机拍照和相册管理功能,包含详细的代码示例和最佳实践。

相机功能开发基础

  1. 权限配置
    首先需要在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}`);
}

}
}
相册管理功能实现

  1. 相册浏览与图片选择
    @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: '删除失败' })
}

}
}
相机高级功能实现

  1. 人脸检测与美化
    @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 })
}

}
}
相册高级功能

  1. 照片分类与搜索
    @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,构建高性能的相机和相册应用。

分类
标签
收藏
回复
举报
回复
    相关推荐