はじめに
前回は、端末やカメラアプリから画像を取得して表示する簡単なアプリを作成しました。
しかし、カメラアプリで新規に写真撮影した場合の解像度が低いという不満がありました。
そこで今回は、カメラアプリに新規撮影を指示してフルサイズの画像を受け取る方法を模索してみます。
フルサイズの画像取得方法
前回の考察にも書いた通り、今回は MediaStore#ACTION_IMAGE_CAPTURE に EXTRA_OUTPUT を指定する方法を用いることにします。
注意事項
Android 7.0 (API level 24) 以降は EXTRA_OUTPUT に渡す Uri の作成に Uri.fromFile(File) が使えないので、FileProvider を使用します。
For more recent apps targeting Android 7.0 (API level 24) and higher, passing a file:// URI across a package boundary causes a FileUriExposedException. Therefore, we now present a more generic way of storing images using a FileProvider.
https://developer.android.com/training/camera/photobasics より引用
機能仕様
前回とほぼ同様です。
画面仕様
ボタンを1つだけにしました。※前回はこちら
- GET IMAGE ボタン
- 端末から画像コンテンツを取得するアプリもしくはカメラアプリを選択する chooser を起動する。
- カメラが使えない場合はカメラアプリは chooser から除外される。
実装
◆ AndroidManifest.xml
追加部分のみ抜粋
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application ...>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.objectfanatics.ex_camera.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_path" />
</provider>
</application>
</manifest>
◆ file_path.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="/" />
</paths>
◆ activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/image_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:gravity="center"
android:src="@mipmap/ic_launcher"
app:layout_constraintBottom_toTopOf="@id/get_image_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/get_image_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="get image"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
◆ MainActivity
package com.objectfanatics.ex_camera
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity(R.layout.activity_main) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val launcher = registerForActivityResult(PickImageOrTakePicture(this, "画像を取得"), this::onImagePick)
findViewById<View>(R.id.get_image_button).setOnClickListener { launcher.launch(null) }
}
private fun onImagePick(uri: Uri?) {
findViewById<ImageView>(R.id.image_view).setImageURI(uri ?: return)
}
}
◆ ActivityResultContract の独自実装
package com.objectfanatics.ex_camera
import android.app.Activity.RESULT_OK
import android.content.Context
import android.content.Intent
import android.content.Intent.ACTION_PICK
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Environment
import android.provider.MediaStore
import androidx.activity.result.contract.ActivityResultContract
import androidx.core.content.FileProvider
import java.io.File
import java.io.IOException
class PickImageOrTakePicture(private val context: Context, private val chooserTitle: String) : ActivityResultContract<Void?, Uri?>() {
private var imageUri: Uri? = null
override fun createIntent(context: Context, input: Void?): Intent =
Intent.createChooser(pickerIntent, chooserTitle).apply {
val uri = context.createImageUri().apply { imageUri = this }
if (context.hasCameraFeature && uri != null) {
putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(takePictureIntent(uri)))
}
}
override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
val data = intent?.data
return when {
resultCode != RESULT_OK -> null
data == null -> imageUri
else -> intent.data
}
}
private fun Context.createImageUri(): Uri? {
return try {
val dir: File = getExternalFilesDir(Environment.DIRECTORY_PICTURES) ?: return null
val file = File.createTempFile("IMG", ".jpg", dir)
FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.fileprovider", file)
} catch (e: IOException) {
null
}
}
companion object {
private val Context.hasCameraFeature: Boolean
get() = packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
private val pickerIntent = Intent().apply {
action = ACTION_PICK
type = "image/*"
}
private fun takePictureIntent(input: Uri): Intent =
Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply { putExtra(MediaStore.EXTRA_OUTPUT, input) }
}
}
実行
◆ 初期状態

◆ Chooser 起動

◆ カメラアプリ起動
◆ 撮影直後
◆ Gallery アプリ

◆ 画像選択後

今回やったこと
フルサイズの写真をカメラアプリから取得しました。
おわりに
それでは、また。