【鴻蒙應用ArkTS開發(fā)系列】- 選擇圖片、文件和拍照功能實現(xiàn)

前言

在使用App的時候掩浙,我們經常會在一些社交軟件中聊天時發(fā)一些圖片或者文件之類的多媒體文件花吟,那在鴻蒙原生應用中,我們怎么開發(fā)這樣的功能呢厨姚? 本文會給大家對這個功能點進行講解衅澈,我們采用的是拉起系統(tǒng)組件來進行圖片、文件的選擇谬墙,拉起系統(tǒng)相機進行拍照的這樣一種實現(xiàn)方式今布。

創(chuàng)建多媒體Demo工程

我們使用Empty 模板創(chuàng)建一個Demo工程。

創(chuàng)建MediaBean 實體類

在src->main->ets 下面創(chuàng)建bean文件夾拭抬,在文件夾下創(chuàng)建MediaBean.ts文件

/**
 * 多媒體數(shù)據(jù)類
 */
export class MediaBean {
  /**
   * 文件名稱
   */
  public fileName: string;
  /**
   * 文件大小
   */
  public fileSize: number;
  /**
   * 文件類型
   */
  public fileType: string;
  /**
   * 本地存儲地址
   */
  public localUrl: string;
}

創(chuàng)建MediaHelper工具類

在src->main->ets 下面創(chuàng)建helper 文件夾部默,在文件夾下創(chuàng)建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個方法造虎,

  • selectPicture 提供選擇圖片功能

     我們通過系統(tǒng)組件 picker.PhotoViewPicker 來進行圖片選擇傅蹂,通過配置PhotoSelectOptions,指定選擇的MIMEType類型(這里PhotoViewMIMETypes.IMAGE_TYPE 圖片類型) 算凿、選擇的圖片最大數(shù)量 maxSelectNumber 份蝴,這里我們實現(xiàn)單選功能,數(shù)值設置為1即可澎媒。使用photoPicker.select 拉起系統(tǒng)組件進行選擇搞乏,然后在回調中獲取圖片的uri。
    
  • selectFile 提供選擇文件功能

    選擇文件的功能戒努,我們通過系統(tǒng)組件 picker.DocumentViewPicker來進行文件選擇请敦,代碼基本是跟圖片選擇是一樣的,區(qū)別在于DocumentSelectOptions储玫,目前api9并沒有配置項提供侍筛,具體關注后續(xù)的api版本情況。

  • takePhoto 提供拍照功能

    拍照的功能撒穷,我們也是拉起相機來進行拍照的匣椰,我們使用 startAbilityForResult 方法 + 配置拉起action (wantConstant.Action.ACTION_IMAGE_CAPTURE)的方式拉起系統(tǒng)相機,拍照結束后端礼,在then中接收返回的數(shù)據(jù)禽笑,我們通過返回碼result.resultCode 來判斷是否進行了拍照入录,如果狀態(tài)值===0,說明進行了拍照佳镜,我們再使用result.want.uri獲取拍照后的照片uri僚稿。

  • buildMediaBean 內部方法,提供MediaBean對象封裝

    這個方法的作用主要是封裝一個多媒體實體類蟀伸,并觸發(fā)appendFileInfoToMediaBean 獲取Uri對應文件的一些文件信息蚀同。代碼很簡單,相信大家一目了然啊掏。

  • appendFileInfoToMediaBean 內部方法蠢络,提供追加查詢所選文件的文件信息的功能

    這個方法的作用主要是通過uri查詢文件的詳細信息,包括文件名稱迟蜜、文件大小刹孔、文件類型。通過Uri獲取文件ID小泉。使用mediaLibrary.getMediaLibrary獲取media對象芦疏。配置MediaFetchOptions冕杠,主要是ID微姊,通過文件ID來查找文件對象。使用media.getFileAssets查詢文件對象結果分预,這里可以是批量操作兢交,得到一個FetchFileResult對象。遍歷FileAsset數(shù)組笼痹,得到文件信息配喳。

    這里列下FileAsset的一些字段:

通過系統(tǒng)組件選擇圖片、文件或者拍照之后凳干,系統(tǒng)只是簡單的返回一個文件的Uri晴裹,如果我們需要展示文件的名稱、文件大小救赐、文件類型涧团,需要通過appendFileInfoToMediaBean 方法另外去獲取。

API標記棄用問題

上面的代碼经磅,在api9實測是可以正常使用的泌绣,但是有一些API被標記為過期,有一些在官方文檔注明即將停用预厌,但是我沒有找到可以平替的API阿迈,如果有讀者知道的,麻煩評論區(qū)告訴我一聲轧叽,謝謝苗沧。

  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 相關的方法,不知道官方基于什么考慮牺荠,可能后續(xù)API10會增加相應方法支持吧翁巍。那我們通過picker只能拿到一個文件的Uri,文件名稱、文件大小這些常規(guī)的文件相關的數(shù)據(jù)都拿不到休雌,那功能都無法開發(fā)灶壶,這也是我之前的一個疑問。

動態(tài)申請多媒體訪問權限

我們讀取文件的多媒體信息需要申請一個多媒體的讀取權限 ohos.permission.READ_MEDIA杈曲,這個權限需要在

module.json5中添加配置requestPermissions驰凛,在該節(jié)點下配置READ_MEDIA權限,具體如下圖:

由于這個READ_MEDIA權限需要進行動態(tài)權限申請担扑,因為還需要我們進行動態(tài)權限申請代碼邏輯開發(fā)恰响,這里由于篇幅原因,我就不過多贅述涌献,后續(xù)如果對這塊動態(tài)權限申請有不明白的地方胚宦,我再重新寫一篇文章介紹,講下動態(tài)申請權限燕垃,跳轉系統(tǒng)權限設置頁配置權限這些功能具體如何實現(xiàn)枢劝。

這次的Demo,我們直接安裝后卜壕,在系統(tǒng)設置中找到應用您旁,把對應的權限開啟即可(繞過權限動態(tài)申請)。

實現(xiàn)選擇圖片顯示功能

下面我們編寫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,自動生成調試簽名两曼,生成完畢后皂甘,運行安裝到手機上。

注意:由于我們沒有實現(xiàn)多媒體讀取權限動態(tài)申請權限悼凑,因此需要在手機系統(tǒng)設置-應用中找到該應用偿枕,開啟多媒體權限,該權限默認是禁止的户辫,開啟后再打開應用操作即可渐夸。運行的具體的效果如文章開頭貼圖展示一般。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末渔欢,一起剝皮案震驚了整個濱河市墓塌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌奥额,老刑警劉巖苫幢,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異垫挨,居然都是意外死亡韩肝,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門棒拂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伞梯,“玉大人,你說我怎么就攤上這事帚屉。” “怎么了漾峡?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵攻旦,是天一觀的道長。 經常有香客問我生逸,道長牢屋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任槽袄,我火速辦了婚禮烙无,結果婚禮上,老公的妹妹穿的比我還像新娘遍尺。我一直安慰自己截酷,他們只是感情好,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布乾戏。 她就那樣靜靜地躺著迂苛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上启上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天矿微,我揣著相機與錄音,去河邊找鬼念搬。 笑死抑堡,一個胖子當著我的面吹牛,可吹牛的內容都是我干的朗徊。 我是一名探鬼主播夷野,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼荣倾!你這毒婦竟也來了悯搔?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤舌仍,失蹤者是張志新(化名)和其女友劉穎妒貌,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铸豁,經...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡灌曙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了节芥。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片在刺。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖头镊,靈堂內的尸體忽然破棺而出蚣驼,到底是詐尸還是另有隱情,我是刑警寧澤相艇,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布颖杏,位于F島的核電站,受9級特大地震影響坛芽,放射性物質發(fā)生泄漏留储。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一咙轩、第九天 我趴在偏房一處隱蔽的房頂上張望获讳。 院中可真熱鬧,春花似錦活喊、人聲如沸丐膝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽尤误。三九已至侠畔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間损晤,已是汗流浹背软棺。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留尤勋,地道東北人喘落。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像最冰,于是被迫代替她去往敵國和親瘦棋。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內容