怠慢プログラマーの備忘録

怠慢でナマケモノなプログラマーの備忘録です。

【Kotlin】カメラ/アルバム機能[備忘録] (2020/06/19更新)

カメラ/アルバム機能をKotlinで実装した時の備忘録。

Environment.getExternalStoragePublicDirectoryAPIレベル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プログラミング

Kotlinスタートブック -新しいAndroidプログラミング

  • 作者:長澤 太郎
  • 発売日: 2016/07/13
  • メディア: 単行本(ソフトカバー)

やさしいKotlin入門

やさしいKotlin入門