【鸿蒙实战开发】ArkTS开发选择图片、文件和拍照功能实现

45 篇文章 0 订阅
38 篇文章 0 订阅

前言

在使用App的时候,我们经常会在一些社交软件中聊天时发一些图片或者文件之类的多媒体文件,那在鸿蒙原生应用中,我们怎么开发这样的功能呢? 本文会给大家对这个功能点进行讲解,我们采用的是拉起系统组件来进行图片、文件的选择,拉起系统相机进行拍照的这样一种实现方式。

在文章开始之前,按照惯例,我们先展示本文Demo效果图:

在这里插入图片描述

下面我们正式开始讲解。

创建多媒体Demo工程

我们使用Empty 模板创建一个Demo工程。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

创建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文件


在这里插入图片描述

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[0];
            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[0];
            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[parts.length - 1] : '-1';

    try {

      let media = mediaLibrary.getMediaLibrary(this.mContext);
      let mediaFetchOptions: mediaLibrary.MediaFetchOptions = {
        selections: mediaLibrary.FileKey.ID + '= ?',
        selectionArgs: [id],
        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 data  exception ");
    }

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

      let fileInfoObj = fileList[0];
      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;

    }
  }
}

MediaHelper 类定义了5个方法,

1.selectPicture 提供选择图片功能

我们通过系统组件 picker.PhotoViewPicker 来进行图片选择,通过配置PhotoSelectOptions,指定选择的MIMEType类型(这里PhotoViewMIMETypes.IMAGE_TYPE 图片类型) 、选择的图片最大数量 maxSelectNumber ,这里我们实现单选功能,数值设置为1即可。使用photoPicker.select 拉起系统组件进行选择,然后在回调中获取图片的uri。

2.selectFile 提供选择文件功能

选择文件的功能,我们通过系统组件 picker.DocumentViewPicker来进行文件选择,代码基本是跟图片选择是一样的,区别在于DocumentSelectOptions,目前api9并没有配置项提供,具体关注后续的api版本情况。

3.takePhoto 提供拍照功能

拍照的功能,我们也是拉起相机来进行拍照的,我们使用 startAbilityForResult 方法 + 配置拉起action (wantConstant.Action.ACTION_IMAGE_CAPTURE)的方式拉起系统相机,拍照结束后,在then中接收返回的数据,我们通过返回码result.resultCode 来判断是否进行了拍照,如果状态值===0,说明进行了拍照,我们再使用result.want.uri获取拍照后的照片uri。

4.buildMediaBean 内部方法,提供MediaBean对象封装

这个方法的作用主要是封装一个多媒体实体类,并触发appendFileInfoToMediaBean 获取Uri对应文件的一些文件信息。代码很简单,相信大家一目了然。

5.appendFileInfoToMediaBean 内部方法,提供追加查询所选文件的文件信息的功能

这个方法的作用主要是通过uri查询文件的详细信息,包括文件名称、文件大小、文件类型。通过Uri获取文件ID。使用mediaLibrary.getMediaLibrary获取media对象。配置MediaFetchOptions,主要是ID,通过文件ID来查找文件对象。使用media.getFileAssets查询文件对象结果,这里可以是批量操作,得到一个FetchFileResult对象。遍历FileAsset数组,得到文件信息。

通过系统组件选择图片、文件或者拍照之后,系统只是简单的返回一个文件的Uri,如果我们需要展示文件的名称、文件大小、文件类型,需要通过appendFileInfoToMediaBean 方法另外去获取。

API标记弃用问题

上面的代码,在api9实测是可以正常使用的,但是有一些API被标记为过期,有一些在官方文档注明即将停用。
1.ohos.app.ability.wantConstant
官方提示让我们切换到 ohos.app.ability.wantConstant这个类下,可是我们用到wantConstant.Action,这个Action在 ohos.app.ability.wantConstant中没有定义,我在SDK中也没有找到Action在哪一个类中定义;

2.mediaLibrary.getMediaLibrary.getFileAssets
我们需要使用getMediaLibrary获取多媒体对象,调用getFileAssets查询文件的多媒体信息,官方提示让我们使用ohos.file.picker,奇怪的是picker中没有getFileAssets 相关的方法,不知道官方基于什么考虑,可能后续API10会增加相应方法支持吧。那我们通过picker只能拿到一个文件的Uri,文件名称、文件大小这些常规的文件相关的数据都拿不到,那功能都无法开发,这也是我之前的一个疑问。

动态申请多媒体访问权限

我们读取文件的多媒体信息需要申请一个多媒体的读取权限 ohos.permission.READ_MEDIA,这个权限需要在

module.json5中添加配置requestPermissions,在该节点下配置READ_MEDIA权限,具体如下图:

在这里插入图片描述

由于这个READ_MEDIA权限需要进行动态权限申请,因为还需要我们进行动态权限申请代码逻辑开发,这里由于篇幅原因,我就不过多赘述,后续如果对这块动态权限申请有不明白的地方,我再重新写一篇文章介绍,讲下动态申请权限,跳转系统权限设置页配置权限这些功能具体如何实现。

这次的Demo,我们直接安装后,在系统设置中找到应用,把对应的权限开启即可(绕过权限动态申请)。

实现选择图片显示功能

下面我们编写UI页面,使用我们上面的MediaHelper工具类选择图片、拍照,并将图片显示出来。

在这里插入图片描述

我们在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,自动生成调试签名,生成完毕后,运行安装到手机上。

注意:由于我们没有实现多媒体读取权限动态申请权限,因此需要在手机系统设置-应用中找到该应用,开启多媒体权限,该权限默认是禁止的,开启后再打开应用操作即可。运行的具体的效果如文章开头贴图展示一般。

写在最后

有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。

这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、openHarmony南向开发、鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。

希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!

获取这份完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

鸿蒙(HarmonyOS NEXT)最新学习路线

在这里插入图片描述

有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。

获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

《鸿蒙 (OpenHarmony)开发入门教学视频》

在这里插入图片描述

《鸿蒙生态应用开发V3.0白皮书》

在这里插入图片描述

《鸿蒙 (OpenHarmony)开发基础到实战手册》

OpenHarmony北向、南向开发环境搭建

在这里插入图片描述

《鸿蒙开发基础》

●ArkTS语言
●安装DevEco Studio
●运用你的第一个ArkTS应用
●ArkUI声明式UI开发
.……
在这里插入图片描述

《鸿蒙开发进阶》

●Stage模型入门
●网络管理
●数据管理
●电话服务
●分布式应用开发
●通知与窗口管理
●多媒体技术
●安全技能
●任务管理
●WebGL
●国际化开发
●应用测试
●DFX面向未来设计
●鸿蒙系统移植和裁剪定制
……
在这里插入图片描述

《鸿蒙进阶实战》

●ArkTS实践
●UIAbility应用
●网络案例
……
在这里插入图片描述

获取以上完整鸿蒙HarmonyOS学习资料,请点击→纯血版全套鸿蒙HarmonyOS学习资料

  • 9
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值