Help us understand the problem. What is going on with this article?

KotlinでAndroidのカメラ機能を利用する

More than 1 year has passed since last update.

検証環境

この記事の内容は、以下の環境で検証しました。

  • Java:open jdk 1.8.0_152
  • Kotlin 1.2.0
  • Android Studio 3.0.2
  • CompileSdkVersion:26
  • MinSdkVersion:21
  • TargetSdkVersion:26
  • BuildToolsVersion:26.0.2
  • gradle:4.0.0

目標

自身のAndroidアプリからカメラを起動し、撮った画像をImageに表示します。
また、全てKotlinで記述するものとします。

完成イメージ

サンプルのアプリは、下記の順序で動作します。
①「カメラ起動」ボタンをクリックする
①' インストールした端末のOSが6(Marshmallow)以上は、許可を得るダイアログが表示する
②カメラのアプリが起動する
③カメラで撮影する
④撮影した画像をアプリのImageViewに表示する

イメージは以下の通りです。
アプリ概要.png

処理の流れ

アプリの処理の流れは以下のとおりです。

処理フロー.png

実装の説明

説明するファイル

この記事では、下記のファイルについて説明します。

ファイル名 説明
AndroidManifest.xml マニフェストファイル
values/strings.xml ボタンに表示する文字列の定義
layout/activity_main.xml アプリの画面レイアウト
jp.co.casareal.camerabasicsample.MainActivity カメラの起動や、パーミッションの取得、画像の表示を行っています。

詳細

AndroidManifest.xml

コード

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.co.casareal.camerabasicsample">

    <uses-permission android:name="android.permission.CAMERA" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

説明

カメラを使用するため、uses-paermissionのカメラを追記しています。

strings.xml

コード

<resources>
    <string name="app_name">CameraBasicSample</string>
    <string name="launch_camera">カメラ起動</string>
</resources>

説明

ボタンに表示する文字列を、「launch_camera」という名前で定義しています。

activity_main.xml

コード

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="jp.co.casareal.camerabasicsample.MainActivity">

    <Button
        android:id="@+id/btnLaunchCamera"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/launch_camera" />

    <ImageView
        android:id="@+id/cameraImage"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

説明

画面にカメラを起動するためのボタンと撮影した画像を表示するためのImageViewを定義しています。ボタンのIDは「btnLaunchCamera」で、ImageViewのIDは「cameraImage」です。

jp.co.casareal.camerabasicsample.MainActivity

コード

package jp.co.casareal.camerabasicsample

import android.Manifest
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.os.Bundle
import android.provider.MediaStore
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import android.support.v7.app.AppCompatActivity
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_main.*


class MainActivity : AppCompatActivity() {
    companion object {
        const val CAMERA_REQUEST_CODE = 1
        const val CAMERA_PERMISSION_REQUEST_CODE = 2
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    override fun onResume() {
        super.onResume()

        btnLaunchCamera.setOnClickListener {
            // カメラ機能を実装したアプリが存在するかチェック
            Intent(MediaStore.ACTION_IMAGE_CAPTURE).resolveActivity(packageManager)?.let {
                if (checkCameraPermission()) {
                    takePicture()
                } else {
                    grantCameraPermission()
                }
            } ?: Toast.makeText(this, "カメラを扱うアプリがありません", Toast.LENGTH_LONG).show()
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (requestCode == CAMERA_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
            val image = data?.extras?.get("data")?.let {
                cameraImage.setImageBitmap(it as Bitmap)
            }
        }
    }

    private fun takePicture() {
        val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {
            addCategory(Intent.CATEGORY_DEFAULT)
        }

        startActivityForResult(intent, CAMERA_REQUEST_CODE)
    }

    private fun checkCameraPermission() = PackageManager.PERMISSION_GRANTED ==
            ContextCompat.checkSelfPermission(applicationContext, Manifest.permission.CAMERA)


    private fun grantCameraPermission() =
            ActivityCompat.requestPermissions(this,
                    arrayOf(Manifest.permission.CAMERA),
                    CAMERA_PERMISSION_REQUEST_CODE)


    override fun onRequestPermissionsResult(requestCode: Int,
                                            permissions: Array<out String>,
                                            grantResults: IntArray) {
        if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
            if (grantResults.isNotEmpty() &&
                    grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                takePicture()
            }
        }
    }
}

説明

① カメラの機能を実装したアプリが存在するか
「カメラ起動」ボタンがクリックされた時に、まずはその端末にカメラのアプリが存在するか確認する必要があります。
Actionを設定したIntentを生成し、resolveActivityメソッドでActionに対応するパッケージが存在するかチェックが可能です。存在する場合は、Actionに対応したアプリのパッケージ情報が取得できます。存在しない場合は、nullが返ります。
resolveActivityメソッドの引数に、PackageManagerクラスのオブジェクトを渡します。

一部抜粋
Intent(MediaStore.ACTION_IMAGE_CAPTURE).resolveActivity(packageManager)

② カメラアプリが無い旨を通知する
カメラアプリが存在しない場合は、その旨をユーザに通知する必要があります。
今回の例では、Toastで行っています。

一部抜粋
Intent(MediaStore.ACTION_IMAGE_CAPTURE).resolveActivity(packageManager)?.let {
              ・・・省略・・・
            } ?: Toast.makeText(this, "カメラを扱うアプリがありません", Toast.LENGTH_LONG).show()
        }

③カメラのパーミッションを持っているか
このカメラアプリのパーミッション(利用許可)を得ているか事前に確認する必要があります。
パーミッションがない場合は、パーミッションを取得します。すでにパーミッションがある場合は、カメラアプリを起動します。

Android 6以降では、パーミッションが保護レベルにより「Normalパーミッション」と「Dangerousパーミッション」にわけられています。
「Normalパーミッション」はAndroid 5以前と同様にAndroidManifest.xmlに記述すれば、インストール時にパーミッションを取得できます。
しかし、「Dangerousパーミッション」は、対象の機能を実際に使用する際、パーミッションをダイアログで取得する必要があります。

主な「Normalパーミッション」と「Dangerousパーミッション」は参考のサイトを確認してください。

Normalパーミッション

https://developer.android.com/guide/topics/permissions/normal-permissions.html?hl=ja

Dangerousパーミッション

https://developer.android.com/guide/topics/security/permissions.html?hl=ja#normal-dangerous

本題に戻ります。パーミッションの有無を判定するために、「checkCameraPermission」メソッドを定義しました。メソッド内で 「ContextCompat#checkSelfPermission」メソッドを呼び出してチェックしています。第1引数にはContext、第2引数には対象のパーミッションを指定します。
すでにパーミッションがある場合は、「PackageManager.PERMISSION_GRANTED」が返り、パーミッションがない場合は、「PackageManager.PERMISSION_DENIED」が返ります。

一部抜粋
    private fun checkCameraPermission() = PackageManager.PERMISSION_GRANTED ==
            ContextCompat.checkSelfPermission(applicationContext, Manifest.permission.CAMERA)

④パーミッションを得る
パーミッションがない場合は、パーミッションをユーザから得る必要があります。パーミッションを得るために、「grantCameraPermission」メソッドを定義しました。メソッド内で「ActivityCompat#requestPermissions」メソッドを呼び出しています。第1引数にはActivity、第2引数には対象のパーミッションの文字列配列、第3引数にはリクエストコードを指定します。リクエストコードは任意ですが、0以上の整数にする必要があります。リクエストコードは結果を受け取る時の判断材料となるため、重要です。

一部抜粋
    private fun grantCameraPermission() =
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), CAMERA_PERMISSION_REQUEST_CODE)

⑤カメラのパーミッションが得られたか
パーミッションを要求するダイアログが表示され、その後、パーミッション取得の結果を確認する必要があります。
ユーザの操作が完了すると「onRequestPermissionsResult」メソッドがコールバックされます。
第1引数は「ActivityCompat#requestPermissions」メソッドを呼び出した時のリクエストコード、第2引数にはリクエストしたパーミッション、第3引数には結果が格納されています。
初めにリクエストコードが一致するか確認したのち、結果を確認します。結果には、パーミッションを取得できているとPackageManager.PERMISSION_GRANTEDが格納されており、取得できなかった場合は、PackageManager. PERMISSION_DENIEDが格納されています。

一部抜粋
    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
            if (!grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                takePicture()
            }
        }
    }

⑥カメラアプリを起動する
カメラアプリを起動するには、暗黙的インテントを使用します。
本記事では、カメラアプリの機能を「takePicture」メソッドにまとめました。
暗黙的インテントを利用するには、Intentのコンストラクターに利用したい機能のActionを指定します。そして、Intentにカテゴリを設定する必要があります。説明すると長くなるので、基本的には、Intent.CATEGORY_DEFAULTを設定するものだくらいの理解でいてください。
実行するには、「startActivityForResult」メソッドを呼び出します。このアプリでは撮った写真を取得するために結果を受け取れる「startActivityForResult」メソッドを利用しています。第1引数はIntent、第2引数はリクエストコードを渡します。

一部抜粋
    private fun takePicture() {
        val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {
            addCategory(Intent.CATEGORY_DEFAULT)
        }

        startActivityForResult(intent, CAMERA_REQUEST_CODE)
    }

⑦撮影した画像のサムネイルを取得する
カメラの撮影が完了すると、「onActivityResult」メソッドがコールバックされます。
第1引数はリクエストコード、第2引数にはリザルトコード、第3引数にはIntentが格納されています。
第1引数のリクエストコードは、「startActivityForResult」メソッドを呼び出した時の第2引数の値と一致しているかに利用できます。第2引数のリザルトコードには、通常、Activity.RESULT_OKもしくは、Activity.RESULT_CANCELEDが格納されています。どのようにActivityが終了したかこのリザルトコードで判断できます。第3引数に今回であれば撮影した画像のデータが格納されています。
撮影した画像のデータは、「data」という名前で格納されています。取得するデータの型は、とAny!型です。

一部抜粋
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (requestCode == CAMERA_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
            val image = data?.extras?.get("data")?.let {
                cameraImage.setImageBitmap(it as Bitmap)
            }
        }
    }

⑧画像をImageViewに表示する
カメラで撮影した画像が取得出来たら、ImageViewに設定します。その際、Bitmap型にキャストします。

一部抜粋
cameraImage.setImageBitmap(it as Bitmap)

補足

本記事では、使用するリクエストコードを定数として定義しています。

class MainActivity : AppCompatActivity() {
    companion object {
        const val CAMERA_REQUEST_CODE = 1
        const val CAMERA_PERMISSION_REQUEST_CODE = 2
    }
・・・省略・・・
}

参考

https://developer.android.com/training/camera/photobasics.html
https://developer.android.com/guide/topics/security/permissions.html?hl=ja#normal-dangerous

まとめ

カメラの機能を使うだけであれば、暗黙的インテントで使用するだけですが、Android Marshmallow以降に対応させるには、パーミッションの取得が必要のため、どうしても多くの処理が必要になります。
様々な機能を使うには、その機能が必要とするパーミッションを調べて、取得方法も併せて覚える必要がありそうです。

naoi
casareal
システム開発/評価・検証支援/品質改善支援サービスと現場に即した実践的なIT研修サービスを提供しています。
https://www.casareal.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away