Archives 2020.08.23

Tweets for 2020-08-22

Android Q/API 29以上でViewのキャプチャを保存する

val contentValues = ContentValues().apply {
    put(MediaStore.Images.Media.DISPLAY_NAME, "view.png")
    put(MediaStore.Images.Media.MIME_TYPE, "image/png")
    put(MediaStore.Images.Media.IS_PENDING, "1")
}

val contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI

contentResolver.run {
    insert(contentUri, contentValues)?.let{ uri ->
       openOutputStream(uri)?.let{ stream ->
           val view = findViewById<View>(R.id.some_view)

           view.drawToBitmap().compress(
              Bitmap.CompressFormat.PNG,
              100,
              stream
           )

           stream.close()

           contentValues.put(
               MediaStore.MediaColumns.IS_PENDING, 0
           )
           update( uri, contentValues, null, null)
       }
   }
}

View のビットマップを取得するには Android KTXView.drawToBitmap() メソッドを使う。
API 28から getDrawingCache() は非推奨となっている。

MediaStoreクラスで外部ストレージのURIを取得する。
MediaStoreクラスを利用すると権限の認証は不要

外部ストレージURIとファイルのMimeTypeを指定しcontentResolverでインサートする。
contentResolver オブジェクトはコンテンツプロバイダとの仲介を行ってくれる。Androidのコンテンツプロバイダ APIはファイル情報などをDBで保持している(おそらくSQLite3)
保存するファイル情報のインサートが成功すれば、保存先URIが返ってくる。
URIは content://media/external/images/media/1 のような形式。

ストレージへのファイルの書込はURIに対し openOutPutStream オブジェクトが行う。
書込後にファイル情報DBをアップデートすることで、Photoアプリなど他のアプリで即座に画像を表示できる。

ファイル名が被ったら末尾に連番が入る。
上記の書き方だと、成功・失敗のメッセージもなくPictureフォルダにPNGファイルが保存されるのみ。

ただし、Android Q 未満は MediaStore を使えないので、Android P以下にも対応するには参考としたStack Overflowの回答のようにバージョン毎に処理を変える必要がある。

参考サイト

【Android】Viewのスクリーンショットを撮影してギャラリーアプリで見れるようにする – Qiita
最終的にこの記事のコードとちょっと違うだけになりました。

[Android] view.getDrawingCache()がDeprecatedになったのでPixelCopyでスクリーンキャプチャを撮る | Android開発知識ゼロ人間の生活
java – How to save an image in Android Q using MediaStore? – Stack Overflow
アンドロイド – MediaStoreにメディアファイルを保存する方法

公式リファレンス

View.getDrawingCache() | Android Developers
コンテンツ プロバイダの基本 | Android Developers

別解

Canvasを利用して、Viewのビットマップを取得するコードが掲載されている。
「Android」Viewの内容をBitmapの保存方法 | | 最新IT技術情報_arkgame.com

表示範囲外は drawToBitmap() では取得できないため、こちらの方法でCanvasに出力してから画像化する必要があると思われる。