Kanade Labo

かなで研究所

Android Studio カメラアプリ作成その16(画像ファイルに保存2)

こんにちは かなで です。

参考サイト

https://developer.android.com/training/camerax/take-photo?hl=ja

によると、画像を保存するための記述はこれで終わりみたいです。

fun onClick() {
    val outputFileOptions = ImageCapture.OutputFileOptions.Builder(File(...)).build()
    imageCapture.takePicture(outputFileOptions, cameraExecutor,
        object : ImageCapture.OnImageSavedCallback {
            override fun onError(error: ImageCaptureException)
            {
                // insert your code here.
            }
            override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
                // insert your code here.
            }
        })
}

いつも通りどこに書くかはこちら↓

MainActivity.kt

たぶん間違ってるんですが、一旦ここでお願いします。

そして、入力する文言ですが、

この部分だけを入力します。

というのも、「fun onClick() { }」で、何かのオブジェクトをクリックしたときに、赤枠を処理する

という動作のはずですが、今は、とりあえず保存したいだけですので、「クリックしたとき(タップしたとき」の処理じゃなくて、事前処理の流れの中で保存までさせてみたためです。

今後、場所を変えることになると思いますが、まだ正解がわかってないのでお許しください。

MainActivity.kt

コピペすると、このようになりました。↑

インポートできたのは「ImageCaptureException」と「File」

インポート後の表示↑。一見Fileは解決してるように見えますが、問題ビューを見ると4つのエラーが。

Overload resolution ambiguity:
public constructor File(uri: URI!) defined in java.io.File
public constructor File(pathname: String!) defined in java.io.File
Expecting ')'
Expecting an expression
Unexpected tokens (use ';' to separate expressions on the same line)

まずはこの解決からしていきたいと思います。

調べてみると「File(...)」に入るのは「保存するファイル名をフルパス」が正解のようです。

この説明では、それを省略して「File(...)」とだけ書いていた感じですね。

最初「/sdcard/」とか直接書こうとしたのですが、デバイスによって場所が異なるらしいので、正解がわかりませんでした。

こういう時はこうしましょう。という例題をいくつか見て、解決した方法がこちらです↓

        val fname = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(System.currentTimeMillis()) + ".jpg"
        val file = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),fname)
        //Log.e(TAG, file.toString())

        val outputFileOptions = ImageCapture.OutputFileOptions.Builder(file).build()

下の行から説明していきます。

val outputFileOptions = ImageCapture.OutputFileOptions.Builder(file).build()

最初「File(...)」があったところを「file」に書き換えました。

これは必要な情報を、fileという変数に入れたからです。エラーにはなりません。

じゃあfileに何を入れたか。

それが真ん中の行です。

val file = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),fname)

fileの中にはFile関数を使って、ファイルのフルパスを入れてます。

記述方法は File(ディレクトリ,ファイル名)という記述です。

これに当てはめると、
 ディレクトリ=「Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)」
 ファイル名=「fname」
となります。

ディレクトリですが、「 Environment.getExternalStoragePublicDirectory 」という書き方をすると、共有ディレクトリを指すようです。

そして「DIRECTORY_PICTURE」は画像を保存する場所を指しています。

つまり「共有ディレクトリの画像を保存する場所」に保存するようにしてるわけですね。

次にファイル名ですが、fnameという変数にしており、fnameは

val fname = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(System.currentTimeMillis()) + ".jpg"

で作成しています。

現在の日時を、yyyyMMdd_HHmmssという形式に変換して、拡張子.jpgをつけると。

これにより、ファイル名の重複が避けられるという仕組みですね。

ちなみに「SimpleDateFormat」「Locale」「Environment」全部インポートできましたが、

「SimpleDataFormat」をインポートするときに、上記のような選択肢が出てきました。

とりあえず上を選んでみたところ、特に問題なかったのでそのままとしています。

出来上がったのがこちら↑

「getExternalStoragePublicDirectory」の部分に、取り消し線が引かれているのがわかるでしょうか。

何か問題があるのかなと思ったのですが、古いAndroidバージョン用の記載であり、新しいAndroidバージョン用の記載じゃないよ。

という事らしいんです。

新しい記述にするべきなのかもしれないですが、今は「Android 7.1.2」で使いたいのと、この場合、「古い」の方になるらしいので、これでいきます。

残るはこの「cameraExecutor」です。

「Executor」って、並列処理させるために、他の仕組みに任せる。みたいなそういう使い方をするらしいです。

ここは写真を保存する部分の処理なんですが、「写真をプレビューする」はメイン処理。「写真を保存する」はメイン処理とは無関係に、保存だけの処理を別で行う。みたいな。

そんなふわっとしたイメージです。

で、ここには、その「他に任せる」を、誰に任せるのかを指定するような?

結果論。こうしました↓

val cameraExecutor = Executors.newSingleThreadExecutor()

該当行のひとつ前に、「cameraExecutor」は「Executors.newSingleThreadExecutor()」だよ。

っていうものを追加。

newって書いてあるので、新しいシングルスレッドのエグゼキューターを指定したような感じ。

あっ、「Executors」はインポートできましたので…

    private fun bindPreview(cameraProvider : ProcessCameraProvider) {
        val preview : Preview = Preview.Builder()
            .build()

        val cameraSelector : CameraSelector = CameraSelector.Builder()
            .requireLensFacing(CameraSelector.LENS_FACING_BACK)
            .build()
        val kanadeFinder: PreviewView = findViewById(R.id.viewFinder)
        preview.setSurfaceProvider(kanadeFinder.surfaceProvider)

        val imageCapture = ImageCapture.Builder()
            //.setTargetRotation(view.display.rotation)
            .build()

        var camera = cameraProvider.bindToLifecycle(this as LifecycleOwner, cameraSelector, imageCapture, preview)
        //var camera = cameraProvider.bindToLifecycle(this as LifecycleOwner, cameraSelector, preview)

        val fname = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(System.currentTimeMillis()) + ".jpg"
        val file = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),fname)
        //Log.e(TAG, file.toString())

        val outputFileOptions = ImageCapture.OutputFileOptions.Builder(file).build()
        val cameraExecutor = Executors.newSingleThreadExecutor()
        imageCapture.takePicture(outputFileOptions, cameraExecutor,
            object : ImageCapture.OnImageSavedCallback {
                override fun onError(error: ImageCaptureException)
                {
                    // insert your code here.
                }
                override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
                    // insert your code here.
                }
            }
        )
    }

これで、「MainActivity.kt」への変更は終了しました。

カメラへのアクセスには、カメラへのアクセス権限が必要でした。

じゃあ画像フォルダへのアクセスには…

次回へ続きます(笑

最後までお読みいただきありがとうございました。
気になることがあったら、コメント頂けると嬉しいです。
自主学習も兼ねて記事にするかもしれません。

-AndroidStudio