0
1

More than 3 years have passed since last update.

【Kotlin研修13日目】暗黙的インテントを利用したカメラ機能の実装

Last updated at Posted at 2021-06-21

カメラ機能の実装

カメラ機能を実装する方法は、以下の2通り。

  1. 暗黙的インテントによるOS標準の「カメラ」アプリの利用
  2. android.hardware.camera2APIによるカメラ機能の作成

暗黙的インテントを利用したカメラ機能の実装

参考: 研修11日目
暗黙的インテントを用いてカメラ機能を実装する手順は、以下の通り。

  1. マニフェストファイルに端末のストレージを利用するためのパーミッションを付与
  2. 撮影した画像ファイル名一意となるよう、SimpleDateFormatを用いて日時フォーマッタを作成
  3. ストレージに格納する画像ファイル名ファイル形式を指定
  4. ContentResolverを用いてデータの格納先URIを作成
  5. 暗黙的インテントを用いてOS標準の「カメラ」アプリを起動
  6. 遷移元アクティビティに戻った際に実行される処理を記述

マニフェストファイルへのパーミッションの記述

参考: 研修12日目
アプリケーションが端末のストレージを利用できるよう、マニフェストファイル(=AndroidManifest.xml)に<uses-permission>タグを追記する。

なお、ユーザに対してパーミッションダイアログの表示が必要なのは、
アプリケーションに付与されたパーミッションを用いて外部(=インターネット)との通信が行われる場合に限定されるため、端末内部でデータのやり取りを行う場合はパーミッションダイアログの表示は不要である。

サンプルコード

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
    <uses-permission 
        android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    />
    ...
</manifest>

日時フォーマッタの作成

ストレージファイルを格納する場合、ファイル名が同名となり自動で上書きされないよう、一意となるファイル名にする必要がある。
ファイル名を一意とするためには、タイムスタンプを利用する。

タイムスタンプの利用にあたって、日時フォーマッタであるSimpleDateFormatオブジェクト、現在時刻を保持するDateオブジェクトを利用する。

ここで、Dateオブジェクトをタイムスタンプとして利用する場合、
タイムスタンプString型であるため、
DateFormatクラスのformat()メソッドを用いてDateオブジェクトをString型に変換する必要がある。

SimpleDateFormat

アルファベットを用いたユーザ定義日時フォーマッタを生成するクラス。

Date

ミリ秒単位で特定の「瞬間」を表すクラス。

定義

// 日時フォーマットを指定したSimpleDateFormatオブジェクトの生成
SimpleDateFormat(pattern: String)
// パラメータ
// pattern: 日時の形式

// Date型 -> String型 への変換
// <- SimpleDateFormatクラスはDateFormatクラスを継承しているため、
//    SimpleDateFormatクラスからも利用可能
DateFormat.format(date: Date): String
// パラメータ
// date: 日時を表すDateオブジェクト

日時形式の主な表現方法

参考: SimpleDateFormat

文字 内容
yyyy 2021
MM 06
dd 21
EEE 曜日 Mon
HH 10
mm 56
ss 48
SSS ミリ秒 978

サンプルコード

MainActivity.kt
// 日時形式を指定したSimpleDateFormatオブジェクト
val dateFormat = SimpleDateFormat("yyyyMMddHHmmss")

// 現在の時刻を保持するDateオブジェクト
val now = Date()

// Date型 -> String型 への変換
val nowStr = dateFormat.format(now)

// タイムスタンプを利用したファイル名の指定
val fileName = "CameraIntentPhoto_${nowStr}.jpg"

ContentValueオブジェクトの生成・定義

ContentValueオブジェクトを生成し、やり取りするデータのファイル名と、データの種類(=MIMEタイプ)を定義する。

ContentValue

ContentResolverが生成するURIに含まれる、コンテンツプロバイダが取り扱うデータ情報マップ構造で保持するクラス。

ContentResolver

コンテンツプロバイダに渡す、データの格納先URI(=Uriオブジェクト)とデータ情報(=ContentValueオブジェクト)が含まれるURIを生成するクラス。

コンテンツプロバイダ(ContentProvider)

参考: コンテンツプロバイダ
端末のストレージや複数の外部アプリケーションと直接データのやり取りを行う、アプリケーションの構成要素。

コンテンツプロバイダ0.png
出典: コンテンツプロバイダと他のコンポーネントとの関係

コンテンツプロバイダは、ContentResolverオブジェクトによって作成されたURIを基に、指定されたストレージに対して指定されたデータをやり取りする。

端末のストレージへのアクセス手順を図式化すると、以下のようになる。

コンテンツプロバイダ.png
出典: ストレージとのやり取りの流れ

定義

ContentValues.put(
    key: String!, 
    value: <T>!
): Unit
// パラメータ
// key: データの種類を表すキー
// value: 値

データの種類を表すContentValuesのキー

キー名 内容
MediaStore.Images.Media.TITLE 画像ファイル名
MediaStore.Images.Media.MIME_TYPE ファイルの種類(=MIMEタイプ)

サンプルコード

MainActivity.kt
// タイムスタンプを利用したファイル名の指定
val fileName = "CameraIntentPhoto_${nowStr}.jpg"

// ContentValuesオブジェクト
val values =  ContentValues()

// ContentValuesオブジェクトに格納するファイル名
values.put(MediaStore.Images.Media.TITLE, fileName)

// ContentValuesオブジェクトに格納するMIMEタイプ(=ファイルの種類)
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")

Uriオブジェクトの生成

ContentResolverクラスのinsert()メソッドを用いて、
データの格納先URI」(=Uriオブジェクト)と、
格納する「データ情報」(=ContentValuesオブジェクト)を保持するUriオブジェクトを生成する。

なお、ContentResolverコンテンツプロバイダに対して渡すURIリレーショナルデータベースのデータ構造をとっており、
URIに含まれるデータの格納先URI毎にテーブルが存在し、
テーブル毎に取り扱うデータ情報が含まれる。

上記サンプルコードの場合のテーブルのデータ構造は、以下の通り。

キー
MediaStore.Images.Media.TITLE ${filename}
MediaStore.Images.Media.MIME_TYPE "image/jpeg"

定義

ContentResolver.insert(
    url: Uri, 
    values: ContentValues?
): Uri?
// パラメータ
// url: 「データの格納先」を表すUriオブジェクト
// values: 「データ情報」を表すContentValuesオブジェクト

サンプルコード

MainActivity.kt
// ContentValuesオブジェクト(=データ情報)
val values =  ContentValues()

// ContentValuesオブジェクトに格納するファイル名
values.put(MediaStore.Images.Media.TITLE, fileName)

// ContentValuesオブジェクトに格納するMIMEタイプ(=ファイルの種類)
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")

// データの格納先Uriとデータ情報(=ContentValuesオブジェクト)を保持するURI
_imageUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)

暗黙的インテントの定義

参考: 研修11日目
インテントが行うアクションアプリケーションそのものを表す場合、URIの指定は不要となる。

また、「カメラ」アプリを利用し、撮影した画像データストレージに保存する場合、
インテントExtraデータに、ContentResolverを用いて作成したURIを追加する。
ただし、その場合のExtraデータのキー(=name)は、値が「データの出力先」であることを表すMediaStore.EXTRA_OUTPUTとする。

定義

// 暗黙的インテントを利用したIntentオブジェクトの生成
Intent(action: String!)
// パラメータ
// action: インテントが実行するアクション

// データの出力先URIを指定するExtraデータの追加
Intent.putExtra(
    name: String!, 
    value: Parcelable?
): Intent
// name: 格納するデータの名称
// value: 格納するデータ

サンプルコード

MainActivity.kt
// 「カメラ」アプリを起動するインテント
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)

// 画像の出力先をURIで指定
intent.putExtra(MediaStore.EXTRA_OUTPUT, _imageUri)

遷移先での処理後に遷移元へ戻る画面遷移の実装

参考: 研修3日目
遷移先アクティビティでの処理終了時に、自動的に遷移元アクティビティに戻る場合、
ComponentActivityクラスのstartActivityForResult()メソッドを用いてインテントを開始する。

定義

ComponentActivity.startActivityForResult(
    intent Intent!, 
    requestCode: Int
): Unit
// パラメータ
// intent: 遷移先アクティビティを表すIntentオブジェクト
// requestCode: onActivityResult()メソッド(後述)と
//              一対一で対応させるリクエストコード

サンプルコード

MainActivity.kt
// 「カメラ」アプリを起動するインテント
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)

// 遷移先での処理後に遷移元へ戻る画面遷移の実行
startActivityForResult(intent, 200)

画面遷移後に遷移元で呼び出される処理の記述

ComponentActivityクラスのstartActivityForResult()メソッドを用いてインテントを開始した場合、遷移先アクティビティでの処理終了後、
遷移元アクティビティComponentActivityクラスのonActivityResult()メソッドが呼び出される。

また、遷移元アクティビティで撮影した画像データを表示する場合は、
あらかじめImageViewを配置しておき、ImageViewに対して画像データが保存されている格納先ストレージURIを指定する。

定義

// startActivityForResult()メソッドの終了時に呼び出される処理
@CallSuper
ComponentActivity.onActivityResult(
    requestCode: Int, 
    resultCode: Int, 
    @Nullable data: Intent?
): Unit
// パラメータ
// requestCode: startActivityForResult()メソッドと
//              一対一で対応させるリクエストコード
// resultCode: 処理結果を表すActivityクラス定数

// URIを指定してImageViewに画像を表示
ImageView.setImageURI(uri: Uri?): Unit
// uri: 画像データのURI

処理結果を表すActivityクラス定数

定数名 内容
RESULT_OK 正常終了
RESULT_CANCELED キャンセル

サンプルコード

MainActivity.kt
// データの格納先URIとデータ情報(=ContentValuesオブジェクト)を保持するURI
_imageUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)

// startActivityForResult()による遷移先アクティビティでの処理終了後、
// 遷移元アクティビティで呼び出される処理(=コールバック処理)
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    // リクエストコードが一致し、かつ正常に処理が終了していた場合の処理
    if(requestCode == 200 && resultCode == AppCompatActivity.RESULT_OK) {

        // 「カメラ」アプリによって変換されたBitmapオブジェクト(=画像)
        // <- ストレージを利用しない場合はサムネイル画像しか取得できないため、
        //    解像度の低い画像となる
        // val bitmap = data?.getParcelableExtra<Bitmap>("data")

        // 画像を表示するImageView
        val ivCamera = findViewById<ImageView>(R.id.ivCamera)

        // Bitmapデータを指定してImageViewに画像を反映
        // ivCamera.setImageBitmap(bitmap)

        // URIを指定してImageViewに画像を反映
        ivCamera.setImageURI(_imageUri)
    }
}
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1