14
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Android】Viewのスクリーンショットを撮影してギャラリーアプリで見れるようにする

Last updated at Posted at 2019-11-30

背景

画面内に表示されている特定の情報を画像として保存し、後から見返せるようにしてユーザー利便性を向上させたいという要件がありました。(例えば、ゲームアプリのユーザー情報だったり、乗換案内アプリの検索結果だったり。)

この記事ではこの要件の実現方法について、下記のポイントを解説いたします。

  • スクリーンショットの撮影方法
  • ギャラリーへの反映方法

コード解説

全てのソースコードはGithubで公開しています。
クローンして手元で動かしてみると処理の流れがイメージしやすいと思います。
https://github.com/umechanhika/playground/tree/master/android/AddScreenshotToGallery

今回のポイントとなるコードは、スクリーンショットの撮影から保存までを行なっているScreenshotUtil.ktです。

ScreenshotUtil.kt
package com.example.addscreenshottogallery.ui.util

import android.content.ContentResolver
import android.content.ContentValues
import android.graphics.Bitmap
import android.graphics.Canvas
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import android.view.View
import androidx.core.view.drawToBitmap
import java.io.File
import java.io.FileOutputStream

object ScreenshotUtil {
  private const val DIRECTORY_NAME = "AddScreenshotToGallery"
  private const val FILE_EXTENSION = ".jpg"

  fun take(
    contentResolver: ContentResolver,
    targetView: View,
    fileName: String,
    completion: (Bitmap, Uri) -> Unit
  ) {
    // 対象のViewをBitmapに書き出します、スクリーンショットの撮影処理はこれだけです
    val bitmap = targetView.drawToBitmap()
    targetView.draw(Canvas(bitmap))

    // ギャラリーへの反映時に必要な情報を格納している値です
    // AndroidQ以降とそれより前で設定可能なKeyが異なるため、
    // ここでは共通部分のみ生成してif文内でそれぞれに必要な値を追加しています
    val contentValues = createContentValues(fileName)
    val uri: Uri

    if (Build.VERSION_CODES.Q <= Build.VERSION.SDK_INT) {
      contentValues.apply {
        // 画像の保存先を指定します(AndroidQより前と指定方法が異なります)
        put(
          MediaStore.Images.Media.RELATIVE_PATH,
          "${Environment.DIRECTORY_PICTURES}/$DIRECTORY_NAME"
        )
        put(MediaStore.MediaColumns.IS_PENDING, 1)
      }

      contentResolver.run {
        uri = insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
          ?: return

        // AndroidQ以降では画像の書き出し前にギャラリーへの登録(正確に言うとMediaStoreへの登録)を
        // 済ませてから該当Uriに画像を書き出します(処理の順番がAndroidQより前と異なります)
        openOutputStream(uri).use { bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it) }

        contentValues.put(MediaStore.MediaColumns.IS_PENDING, 0)
        update(uri, contentValues, null, null)
      }
    } else {
      // AndroidQより前の処理では一部メソッドがDeprecatedになっていますが、
      // Q以降の処理に互換性がないためSDK_INTによって処理を分岐させています

      // スクリーンショット画像書き出し用のディレクトリ・ファイルを準備
      val directory = File(
        // ファイルの書き出し先はいくつか候補がありますが、
        // アプリを削除してもファイルが消えない、外部アプリからアクセス可能という理由から、
        // getExternalStoragePublicDirectoryを選択しています
        Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
        DIRECTORY_NAME
      )
      if (!directory.exists()) directory.mkdirs()
      val file = File(directory, "$fileName$FILE_EXTENSION")

      // Bitmapをファイルに書き出します
      FileOutputStream(file).use { bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it) }

      // 書き出した画像をギャラリーに反映させています
      contentResolver.run {
        contentValues.put(MediaStore.Images.Media.DATA, file.absolutePath)
        uri = insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) ?: return
      }
    }

    completion(bitmap, uri)
  }

  /**
   * [Build.VERSION_CODES.Q]以上とそれより前で設定できる値が違うため、ここでは共通部分のみ設定
   */
  private fun createContentValues(name: String) = ContentValues().apply {
    put(MediaStore.Images.Media.DISPLAY_NAME, "$name$FILE_EXTENSION")
    put(MediaStore.Images.Media.TITLE, name)
    put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
    put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1_000)
  }
}

GIF

screen_record.gif

参考リンク

AndroidQからのストレージアクセスに関する変更点や、MediaStoreの保存処理の変更点についての情報を記載してくださっています
https://medium.com/@star_zero/android-q%E3%81%AEscoped-storage%E3%81%AB%E3%82%88%E3%82%8B%E5%A4%89%E6%9B%B4-afe41cde9f35
https://medium.com/@mag0716/scoped-storage-%E8%AA%BF%E6%9F%BB%E3%83%A1%E3%83%A2-8a13c70d3ec8

Androidのストレージに関する公式ドキュメントです(日本語ページでは一部情報が足りていない場合があるため、言語設定は英語で閲覧することをおすすめします)
https://developer.android.com/training/data-storage

AndroidQ以降とそれより前のバージョンでの実装の違いについて記載されています
https://stackoverflow.com/questions/56904485/how-to-save-an-image-in-android-q-using-mediastore

14
11
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
14
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?