吴旭华 发表于 2024-6-15 02:08:05

【鸿蒙应用ArkTS开辟系列】- 选择图片、文件和照相功能实现

前言

在使用App的时间,我们常常会在一些交际软件中聊天时发一些图片或者文件之类的多媒体文件,那在鸿蒙原生应用中,我们怎么开辟这样的功能呢? 本文会给各人对这个功能点举行讲解,我们采用的是拉起体系组件来举行图片、文件的选择,拉起体系相机举行照相的这样一种实现方式。
在文章开始之前,按照惯例,我们先展示本文Demo效果图:
https://img-blog.csdnimg.cn/direct/fc40d35ef8a048a285e6201bd21e7b36.gif
下面我们正式开始讲解。
创建多媒体Demo工程

我们使用Empty 模板创建一个Demo工程。
https://img-blog.csdnimg.cn/268f934893ce4cc585bfc8d9270da5f3.png
https://img-blog.csdnimg.cn/b5a5d5207df14043a644d30c1abe56dc.png
创建MediaBean 实体类

在src->main->ets 下面创建bean文件夹,在文件夹下创建MediaBean.ts文件
/**
* 多媒体数据类
*/
export class MediaBean {
/**
   * 文件名称
   */
public fileName: string;
/**
   * 文件大小
   */
public fileSize: number;
/**
   * 文件类型
   */
public fileType: string;
/**
   * 本地存储地址
   */
public localUrl: string;
}

创建MediaHelper工具类

在src->main->ets 下面创建helper文件夹,在文件夹下创建MediaHelper.ts文件
https://img-blog.csdnimg.cn/7783e250d385496e932340489e092646.png
https://img-blog.csdnimg.cn/37338a8e763d461b850bf5ff61beda4d.png
/**
* 多媒体辅助类
*/
export class MediaHelper {
private readonly TAG: string = 'MediaHelper';

private mContext: common.Context;

constructor(context: common.Context) {
    this.mContext = context;
}

/**
* 选择图片
   */
public selectPicture(): Promise<MediaBean> {

}

/**
* 选择文件
   */
public selectFile(): Promise<MediaBean> {
}

/**
* 拍照
   */
public async takePhoto(context: common.UIAbilityContext): Promise<MediaBean> {

}

/**
* 封装附件实体类
** @param uri 文件路径
   */
private async buildMediaBean(uri: string): Promise<MediaBean> {
}

/**
* 通过Uri查找所选文件信息,插入到MediaBean中
* @param mediaBean
* @param uri
   */
private async appendFileInfoToMediaBean(mediaBean: MediaBean, uri: string) {
}
}
MediaHelper 类定义了5个方法,


[*]selectPicture 提供选择图片功能
[*]selectFile 提供选择文件功能
[*]takePhoto 提供照相功能
[*]buildMediaBean 内部方法,提供MediaBean对象封装
[*]appendFileInfoToMediaBean 内部方法,提供追加查询所选文件的文件信息的功能
通过体系组件选择图片、文件或者照相之后,体系只是简单的返回一个文件的Uri,如果我们需要展示文件的名称、文件大小、文件类型,需要通过appendFileInfoToMediaBean 方法另外去获取。
下面我们针对这几个方法,增加具体的实现代码:

[*]selectPicture
/**
   * 选择图片
   */
public selectPicture(): Promise<MediaBean> {

    try {
      let photoSelectOptions = new picker.PhotoSelectOptions();
      photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
      photoSelectOptions.maxSelectNumber = 1;
      let photoPicker = new picker.PhotoViewPicker();
      return photoPicker.select(photoSelectOptions)
      .then((photoSelectResult) => {
          Log.info(this.TAG, 'PhotoViewPicker.select successfully, PhotoSelectResult uri: ' + JSON.stringify(photoSelectResult));

          if (photoSelectResult && photoSelectResult.photoUris && photoSelectResult.photoUris.length > 0) {
            let filePath = photoSelectResult.photoUris;
            Log.info(this.TAG, 'PhotoViewPicker.select successfully, PhotoSelectResult uri: ' + filePath);
            return filePath;
          }

      }).catch((err) => {
          Log.error(this.TAG, 'PhotoViewPicker.select failed with err: ' + err);
          return err;
      }).then(async (filePath) => {
          const mediaBean = await this.buildMediaBean(filePath);
          return mediaBean;
      });
    } catch (err) {
      Log.error(this.TAG, 'PhotoViewPicker failed with err: ' + err);
      return Promise.reject(err);
    }
}
选择图片的功能,我们通过体系组件 picker.PhotoViewPicker 来举行图片选择,通过配置PhotoSelectOptions,指定选择的MIMEType类型(这里PhotoViewMIMETypes.IMAGE_TYPE 图片类型) 、选择的图片最大数目 maxSelectNumber ,这里我们实现单选功能,数值设置为1即可。
使用photoPicker.select 拉起体系组件举行选择,然后在回调中获取图片的uri。

[*]selectFile
/**
   * 选择文件
   */
public selectFile(): Promise<MediaBean> {
    try {
      let documentSelectOptions = new picker.DocumentSelectOptions();
      let documentPicker = new picker.DocumentViewPicker();
      return documentPicker.select(documentSelectOptions)
      .then((documentSelectResult) => {
          Log.info(this.TAG, 'DocumentViewPicker.select successfully, DocumentSelectResult uri: ' + JSON.stringify(documentSelectResult));

          if (documentSelectResult && documentSelectResult.length > 0) {
            let filePath = documentSelectResult;
            Log.info(this.TAG, 'DocumentViewPicker.select successfully, DocumentSelectResult uri: ' + filePath);
            return filePath;
          }

      }).catch((err) => {
          Log.error(this.TAG, 'PhotoViewPicker.select failed with err: ' + err);
          return err;
      }).then(async (filePath) => {

          const mediaBean = await this.buildMediaBean(filePath);
          return mediaBean;

      });
    } catch (err) {
      Log.error(this.TAG, 'PhotoViewPicker failed with err: ' + err);
      return Promise.reject(err);
    }
}
选择文件的功能,我们通过体系组件 picker.DocumentViewPicker来举行文件选择,代码根本是跟图片选择是一样的,区别在于DocumentSelectOptions,现在api9并没有配置项提供,具体关注后续的api版本环境。

[*]takePhoto
/**
   * 拍照
   */
public async takePhoto(context: common.UIAbilityContext): Promise<MediaBean> {


    let want = {
      'uri': '',
      'action': wantConstant.Action.ACTION_IMAGE_CAPTURE,
      'parameters': {},
    };
    return context.startAbilityForResult(want)
      .then((result) => {
      Log.info(this.TAG, `startAbility call back , ${JSON.stringify(result)}`);
      if (result.resultCode === 0 && result.want && StringUtils.isNotNullOrEmpty(result.want.uri)) {
          //拍照成功
          Log.info(this.TAG, 'takePhoto successfully, takePhotoResult uri: ' + result.want.uri);
          return result.want.uri;
      }
      }).catch((error) => {
      Log.info(this.TAG, `startAbility error , ${JSON.stringify(error)}`);
      return error;
      }).then(async (uri: string) => {
      const mediaBean = await this.buildMediaBean(uri);
      return mediaBean;
      });
}
照相的功能,我们也是拉起相机来举行照相的,我们使用 startAbilityForResult 方法 + 配置拉起action (wantConstant.Action.ACTION_IMAGE_CAPTURE)的方式拉起体系相机,照相竣事后,在then中接收返回的数据,我们通过返回码result.resultCode 来判断是否举行了照相,如果状态值===0,说明举行了照相,我们再使用result.want.uri获取照相后的照片uri。

[*]buildMediaBean
/**
   * 封装多媒体实体类
   *
   * @param uri 文件路径
   */
private async buildMediaBean(uri: string): Promise<MediaBean> {

    if (StringUtils.isNullOrEmpty(uri)) {
      return null;
    }

    const mediaBean: MediaBean = new MediaBean();
    mediaBean.localUrl = uri;
    await this.appendFileInfoToMediaBean(mediaBean, uri);
    return mediaBean;
}
这个方法的作用紧张是封装一个多媒体实体类,并触发appendFileInfoToMediaBean 获取Uri对应文件的一些文件信息。代码很简单,信赖各人一目了然。

[*]appendFileInfoToMediaBean
/**
* 通过Uri查找所选文件信息,插入到MediaBean中
* @param mediaBean
* @param uri
   */
private async appendFileInfoToMediaBean(mediaBean: MediaBean, uri: string) {

    if (StringUtils.isNullOrEmpty(uri)) {
      return;
    }
    let fileList: Array<mediaLibrary.FileAsset> = [];

    const parts: string[] = uri.split('/');
    const id: string = parts.length > 0 ? parts : '-1';

    try {

      let media = mediaLibrary.getMediaLibrary(this.mContext);
      let mediaFetchOptions: mediaLibrary.MediaFetchOptions = {
      selections: mediaLibrary.FileKey.ID + '= ?',
      selectionArgs: ,
      uri: uri
      };

      let fetchFileResult = await media.getFileAssets(mediaFetchOptions);
      Log.info(this.TAG, `fileList getFileAssetsFromType fetchFileResult.count = ${fetchFileResult.getCount()}`);
      fileList = await fetchFileResult.getAllObject();
      fetchFileResult.close();
      await media.release();

    } catch (e) {
      Log.error(this.TAG, "query: file dataexception ");
    }

    if (fileList && fileList.length > 0) {

      let fileInfoObj = fileList;
      Log.info(this.TAG, `file id = ${JSON.stringify(fileInfoObj.id)} , uri = ${JSON.stringify(fileInfoObj.uri)}`);
      Log.info(this.TAG, `file fileList displayName = ${fileInfoObj.displayName} ,size = ${fileInfoObj.size} ,mimeType = ${fileInfoObj.mimeType}`);

      mediaBean.fileName = fileInfoObj.displayName;
      mediaBean.fileSize = fileInfoObj.size;
      mediaBean.fileType = fileInfoObj.mimeType;

    }
}
这个方法的作用紧张是通过uri查询文件的详细信息,包括文件名称、文件大小、文件类型。


[*]通过Uri获取文件ID。
[*]使用mediaLibrary.getMediaLibrary获取media对象。
[*]配置MediaFetchOptions,紧张是ID,通过文件ID来查找文件对象。
[*]使用media.getFileAssets查询文件对象结果,这里可以是批量操作,得到一个FetchFileResult对象。
[*]遍历FileAsset数组,得到文件信息。
这里列下FileAsset的一些字段:
名称类型可读可写说明idnumber是否文件资源编号uristring是否文件资源uri(如:datashare:///media/image/2)mimeTypestring是否文件扩展属性mediaType8+MediaType是否媒体类型displayNamestring是是体现文件名,包含后缀名titlestring是是文件标题relativePath8+string是是相对公共目录路径parent8+number是否父目录idsizenumber是否文件大小(单元:字节)dateAddednumber是否添加日期(添加文件时间到1970年1月1日的秒数值)dateModifiednumber是否修改日期(修改文件时间到1970年1月1日的秒数值,修改文件名不会改变此值,当文件内容发生修改时才会更新)dateTakennumber是否拍摄日期(文件照相时间到1970年1月1日的秒数值)artist8+string是否作者audioAlbum8+string是否专辑widthnumber是否图片宽度(单元:像素)heightnumber是否图片高度(单元:像素)orientationnumber是是图片体现方向(顺时针旋转角度,如0,90,180 单元:度)duration8+number是否持续时间(单元:毫秒)albumIdnumber是否文件所归属的相册编号albumUri8+string是否文件所归属相册urialbumNamestring是否文件所归属相册名称 这里贴下MediaHelper.ts的完整代码
import common from '@ohos.app.ability.common';import picker from '@ohos.file.picker';import mediaLibrary from '@ohos.multimedia.mediaLibrary';import wantConstant from '@ohos.ability.wantConstant';import { MediaBean } from '../bean/MediaBean';import { StringUtils } from '../utils/StringUtils';import { Log } from '../utils/Log';/** * 多媒体辅助类 */export class MediaHelper {private readonly TAG: string = 'MediaHelper';private mContext: common.Context;constructor(context: common.Context) {    this.mContext = context;}/**
   * 选择图片
   */
public selectPicture(): Promise<MediaBean> {

    try {
      let photoSelectOptions = new picker.PhotoSelectOptions();
      photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
      photoSelectOptions.maxSelectNumber = 1;
      let photoPicker = new picker.PhotoViewPicker();
      return photoPicker.select(photoSelectOptions)
      .then((photoSelectResult) => {
          Log.info(this.TAG, 'PhotoViewPicker.select successfully, PhotoSelectResult uri: ' + JSON.stringify(photoSelectResult));

          if (photoSelectResult && photoSelectResult.photoUris && photoSelectResult.photoUris.length > 0) {
            let filePath = photoSelectResult.photoUris;
            Log.info(this.TAG, 'PhotoViewPicker.select successfully, PhotoSelectResult uri: ' + filePath);
            return filePath;
          }

      }).catch((err) => {
          Log.error(this.TAG, 'PhotoViewPicker.select failed with err: ' + err);
          return err;
      }).then(async (filePath) => {
          const mediaBean = await this.buildMediaBean(filePath);
          return mediaBean;
      });
    } catch (err) {
      Log.error(this.TAG, 'PhotoViewPicker failed with err: ' + err);
      return Promise.reject(err);
    }
}
/**
   * 选择文件
   */
public selectFile(): Promise<MediaBean> {
    try {
      let documentSelectOptions = new picker.DocumentSelectOptions();
      let documentPicker = new picker.DocumentViewPicker();
      return documentPicker.select(documentSelectOptions)
      .then((documentSelectResult) => {
          Log.info(this.TAG, 'DocumentViewPicker.select successfully, DocumentSelectResult uri: ' + JSON.stringify(documentSelectResult));

          if (documentSelectResult && documentSelectResult.length > 0) {
            let filePath = documentSelectResult;
            Log.info(this.TAG, 'DocumentViewPicker.select successfully, DocumentSelectResult uri: ' + filePath);
            return filePath;
          }

      }).catch((err) => {
          Log.error(this.TAG, 'PhotoViewPicker.select failed with err: ' + err);
          return err;
      }).then(async (filePath) => {

          const mediaBean = await this.buildMediaBean(filePath);
          return mediaBean;

      });
    } catch (err) {
      Log.error(this.TAG, 'PhotoViewPicker failed with err: ' + err);
      return Promise.reject(err);
    }
}
/**
   * 拍照
   */
public async takePhoto(context: common.UIAbilityContext): Promise<MediaBean> {


    let want = {
      'uri': '',
      'action': wantConstant.Action.ACTION_IMAGE_CAPTURE,
      'parameters': {},
    };
    return context.startAbilityForResult(want)
      .then((result) => {
      Log.info(this.TAG, `startAbility call back , ${JSON.stringify(result)}`);
      if (result.resultCode === 0 && result.want && StringUtils.isNotNullOrEmpty(result.want.uri)) {
          //拍照成功
          Log.info(this.TAG, 'takePhoto successfully, takePhotoResult uri: ' + result.want.uri);
          return result.want.uri;
      }
      }).catch((error) => {
      Log.info(this.TAG, `startAbility error , ${JSON.stringify(error)}`);
      return error;
      }).then(async (uri: string) => {
      const mediaBean = await this.buildMediaBean(uri);
      return mediaBean;
      });
}
/**
   * 封装多媒体实体类
   *
   * @param uri 文件路径
   */
private async buildMediaBean(uri: string): Promise<MediaBean> {

    if (StringUtils.isNullOrEmpty(uri)) {
      return null;
    }

    const mediaBean: MediaBean = new MediaBean();
    mediaBean.localUrl = uri;
    await this.appendFileInfoToMediaBean(mediaBean, uri);
    return mediaBean;
}
/**   * 通过Uri查找所选文件信息,插入到MediaBean中   * @param mediaBean   * @param uri   */private async appendFileInfoToMediaBean(mediaBean: MediaBean, uri: string) {    if (StringUtils.isNullOrEmpty(uri)) {      return;    }    let fileList: Array<mediaLibrary.FileAsset> = [];    const parts: string[] = uri.split('/');    const id: string = parts.length > 0 ? parts : '-1';    try {      let media = mediaLibrary.getMediaLibrary(this.mContext);      let mediaFetchOptions: mediaLibrary.MediaFetchOptions = {      selections: mediaLibrary.FileKey.ID + '= ?',      selectionArgs: ,      uri: uri      };      let fetchFileResult = await media.getFileAssets(mediaFetchOptions);      Log.info(this.TAG, `fileList getFileAssetsFromType fetchFileResult.count = ${fetchFileResult.getCount()}`);      fileList = await fetchFileResult.getAllObject();      fetchFileResult.close();      await media.release();    } catch (e) {      Log.error(this.TAG, "query: file dataexception ");    }    if (fileList && fileList.length > 0) {      let fileInfoObj = fileList;      Log.info(this.TAG, `file id = ${JSON.stringify(fileInfoObj.id)} , uri = ${JSON.stringify(fileInfoObj.uri)}`);      Log.info(this.TAG, `file fileList displayName = ${fileInfoObj.displayName} ,size = ${fileInfoObj.size} ,mimeType = ${fileInfoObj.mimeType}`);      mediaBean.fileName = fileInfoObj.displayName;      mediaBean.fileSize = fileInfoObj.size;      mediaBean.fileType = fileInfoObj.mimeType;    }}} API标记弃用问题

上面的代码,在api9实测是可以正常使用的,但是有一些API被标记为逾期,有一些在官方文档注明即将停用,但是我没有找到可以平替的API,如果有读者知道的,麻烦批评区告诉我一声,谢谢。

[*]ohos.app.ability.wantConstant
官方提示让我们切换到 ohos.app.ability.wantConstant这个类下,可是我们用到wantConstant.Action,这个Action在 ohos.app.ability.wantConstant中没有定义,我在SDK中也没有找到Action在哪一个类中定义;
[*]mediaLibrary.getMediaLibrary.getFileAssets
我们需要使用getMediaLibrary获取多媒体对象,调用getFileAssets查询文件的多媒体信息,官方提示让我们使用ohos.file.picker,可笑的是picker中没有getFileAssets 相关的方法,那我们通过picker只能拿到一个文件的Uri,文件名称、文件大小这些通例的文件相关的数据都拿不到,那功能都无法开辟,这也是我之前的一个疑问。
动态申请多媒体访问权限

我们读取文件的多媒体信息需要申请一个多媒体的读取权限 ohos.permission.READ_MEDIA,这个权限需要在
module.json5中添加配置requestPermissions,在该节点下配置READ_MEDIA权限,具体如下图:
https://img-blog.csdnimg.cn/479ebbb99310499da5e66d2e88226383.png
由于这个READ_MEDIA权限需要举行动态权限申请,由于还需要我们举行动态权限申请代码逻辑开辟,这里由于篇幅原因,我就不外多赘述,后续如果对这块动态权限申请有不明白的地方,我再重新写一篇文章先容,讲下动态申请权限,跳转体系权限设置页配置权限这些功能具体怎样实现。
这次的Demo,我们直接安装后,在体系设置中找到应用,把对应的权限开启即可(绕过权限动态申请)。
实现选择图片体现功能

下面我们编写UI页面,使用我们上面的MediaHelper工具类选择图片、照相,并将图片体现出来。
https://img-blog.csdnimg.cn/direct/643c2687758a486aa4c173ca480d3e6e.jpeg
我们在Index.ets文件中放三个按钮,以及体现文件名称、大小、文件类型以及文件路径、体现图片的控件。
完整的代码如下:
import common from '@ohos.app.ability.common';
import { MediaBean } from '../bean/MediaBean';
import { MediaHelper } from '../helper/MediaHelper';

@Entry
@Component
struct Index {
@State mediaBean: MediaBean = new MediaBean();
private mediaHelper: MediaHelper = new MediaHelper(getContext());

build() {
    Row() {
      Column() {
      Text('选择图片')
          .textAlign(TextAlign.Center)
          .width(200)
          .fontSize(16)
          .padding(10)
          .margin(20)
          .border({ width: 0.5, color: '#ff38f84b', radius: 15 })
          .onClick(() => {
            this.handleClick(MediaOption.Picture)
          })

      Text('选择文件')
          .textAlign(TextAlign.Center)
          .width(200)
          .fontSize(16)
          .padding(10)
          .margin(20)
          .border({ width: 0.5, color: '#ff38f84b', radius: 15 })
          .onClick(() => {
            this.handleClick(MediaOption.File)
          })

      Text('拍照')
          .textAlign(TextAlign.Center)
          .width(200)
          .fontSize(16)
          .padding(10)
          .margin(20)
          .border({ width: 0.5, color: '#ff38f84b', radius: 15 })
          .onClick(() => {
            this.handleClick(MediaOption.TakePhoto)
          })

      Divider()
          .width('100%')
          .height(0.5)
          .color('#ff99f6a2')
          .margin({ top: 20 })
          .padding({ left: 20, right: 20 })

      Text(`文件名称: ${this.mediaBean.fileName ? this.mediaBean.fileName : ''}`)
          .textAlign(TextAlign.Center)
          .width('100%')
          .fontSize(16)
          .margin(10)

      Text(`文件大小: ${this.mediaBean.fileSize ? this.mediaBean.fileSize : ''}`)
          .textAlign(TextAlign.Center)
          .width('100%')
          .fontSize(16)
          .margin(10)

      Text(`文件类型: ${this.mediaBean.fileType ? this.mediaBean.fileType : ''}`)
          .textAlign(TextAlign.Center)
          .width('100%')
          .fontSize(16)
          .margin(10)

      Text(`文件Uri: ${this.mediaBean.localUrl ? this.mediaBean.localUrl : ''}`)
          .textAlign(TextAlign.Center)
          .width('100%')
          .fontSize(16)
          .margin(10)

      Image(this.mediaBean.localUrl)
          .width(300)
          .height(300)
          .backgroundColor(Color.Grey)

      }
      .width('100%')
      .height('100%')
    }
    .height('100%')
}

async handleClick(option: MediaOption) {
    let mediaBean: MediaBean;
    switch (option) {
      case MediaOption.Picture:
      mediaBean = await this.mediaHelper.selectPicture();
      break;
      case MediaOption.File:
      mediaBean = await this.mediaHelper.selectFile();
      break;
      case MediaOption.TakePhoto:
      mediaBean = await this.mediaHelper.takePhoto(getContext() as common.UIAbilityContext);
      break;
      default:
      break;
    }

    if (mediaBean) {

      this.mediaBean = mediaBean;

    }

}
}

enum MediaOption {
Picture = 0,
File = 1,
TakePhoto = 2
}
打包测试

打包安装到真机上,需要我们给项目配置署名信息。我们点击File -> Project Structure ->Project ,选择 Signing Configs面板,勾选 Support HarmonyOS 跟Automatically generate signature,自动天生调试署名,天生完毕后,运行安装到手机上。
使用照相功能时,请使用真机运行,如果使用的当地模拟器运行,照相后返回,uri可能会返回“”。
注意:由于我们没有实现多媒体读取权限动态申请权限,因此需要在手机体系设置-应用中找到该应用,开启多媒体权限,该权限默认是克制的,开启后再打开应用操作即可。运行的具体的效果如文章开头贴图展示一样平常。
文本到此完毕,有疑问的请在批评区留言交流,谢谢阅读。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 【鸿蒙应用ArkTS开辟系列】- 选择图片、文件和照相功能实现