カメラ/アルバム機能をKotlinで実装した時の備忘録。
Environment.getExternalStoragePublicDirectory
がAPIレベル29からdeprecatedになりましたので全体的に見直しました。
特に解決できていなかった「GalleryAppを開く」「端末によって画像のOrientationがおかしい時がある」事象の改善も含まれています。
AndroidManifest
<provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths" /> </provider>
provider_paths.xml
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="cache" path="."/> </paths>
Adapter(Activityに処理を移譲しています)
lateinit var listener: OnItemClickListener ... private fun configure() { editProfileImageRecyclerViewHolder.editProfileImageTextView.setOnClickListener { if (this.checkPermission()) { val items = arrayOf("カメラ", "アルバム") AlertDialog.Builder(this.context) .setTitle("選択してください") .setItems(items, DialogInterface.OnClickListener { dialog, which -> when(which) { 0 -> { this.listener.onSelectCameraListener() } 1 -> { this.listener.onSelectGalleryListener() } } }) .show() } else { Toast.makeText(this.context, "Permissions not granted by the user.", Toast.LENGTH_SHORT).show() } } } private fun checkPermission(): Boolean { val cameraPermission = PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(this.context, android.Manifest.permission.CAMERA) val extraStoragePermission = PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(this.context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) return cameraPermission && extraStoragePermission } interface OnItemClickListener { fun onSelectGalleryListener() fun onSelectCameraListener() } fun onItemClickListener(param: OnItemClickListener) { this.listener = param }
Activity
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == CAMERA_REQUEST_CODE && resultCode == RESULT_OK) { // カメラの場合 val inputStream = FileInputStream(File(this.path)) val bitmap = BitmapFactory.decodeStream(inputStream) ExifInterface(this.path).getAttribute(ExifInterface.TAG_ORIENTATION)?.toInt()?.apply { val matrix = Matrix() when(this) { 6 -> { matrix.postRotate(90.toFloat()) } 8 -> { matrix.postRotate(270.toFloat()) } 3 -> { matrix.postRotate(180.toFloat()) } else -> { matrix.postRotate(0.toFloat()) } } val rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true) adapter.bindProfileImage(rotatedBitmap) } } if (resultCode != 0) { // ギャラリーの場合 data?.data?.also { uri -> val inputStream = this.contentResolver.openInputStream(uri) val bitmap: Bitmap? = BitmapFactory.decodeStream(inputStream) val cursor: Cursor? = this.getContentResolver().query( uri, arrayOf(MediaStore.Images.ImageColumns.ORIENTATION), null, null, null ) cursor?.moveToFirst() val orientation = cursor?.getInt(0) cursor?.close() bitmap?.let { bm -> if (orientation == 90) { val matrix = Matrix() matrix.postRotate(90.toFloat()) // 回転値 val rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true) this.adapter.bindProfileImage(rotatedBitmap) } else { this.adapter.bindProfileImage(bm) } } } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_profile_edit) supportActionBar?.hide() this.grantCameraPermission() this.bind() } private fun bind() { val recyclerView: RecyclerView = this.findViewById(R.id.recycler_view) this.adapter = Adapter(this) recyclerView.adapter = adapter recyclerView.layoutManager = LinearLayoutManager(this) adapter.onItemClickListener(object: EditProfileRecyclerAdapter.OnItemClickListener { override fun onSelectGalleryListener() { // GalleryApp val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) intent.type = "image/*" startActivityForResult(intent, 42) } override fun onSelectCameraListener() { takePicture() } }) } private fun grantCameraPermission() = ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.CAMERA, android.Manifest.permission.WRITE_EXTERNAL_STORAGE), EditProfileRecyclerAdapter.CAMERA_PERMISSION_REQUEST_CODE ) private fun takePicture() { val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply { addCategory(Intent.CATEGORY_DEFAULT) putExtra(MediaStore.EXTRA_OUTPUT, makeSaveFileUri()) } this.startActivityForResult(intent, CAMERA_REQUEST_CODE) } private fun makeSaveFileUri(): Uri { val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.JAPAN).format(Date()) val fileName = "IMG" + timeStamp val folder = this.getExternalFilesDir(null) val file = File(folder, fileName) this.path = file.absolutePath this.uri = FileProvider.getUriForFile( applicationContext, applicationContext.packageName, file ) return this.uri }
Kotlinスタートブック -新しいAndroidプログラミング
- 作者:長澤 太郎
- 発売日: 2016/07/13
- メディア: 単行本(ソフトカバー)
- 作者:英一, 野崎
- 発売日: 2018/04/01
- メディア: 単行本
基本からしっかり身につくAndroidアプリ開発入門 Android Studio 3対応 (「黒帯エンジニア」シリーズ)
- 作者:森 洋之
- 発売日: 2018/05/22
- メディア: 単行本