본문 바로가기
안드로이드 공부 & 앱

[안드로이드/코틀린] 카메라/갤러리 사진 이미지뷰에 로딩하기

by 문톰 2022. 1. 28.

0.기본 변수

private lateinit var binding: ActivityIdRegisterBinding
private var picture_flag = 0
private var fileAbsolutePath: String? = null

 

 

1.카메라에서 사진을 찍은 후 갤러리에 저장 후 이미지뷰에 로딩하기

1)Gradle 설정

//테드 퍼미션 
    implementation "gun0912.ted:tedpermission:2.2.3"

 

2)메니페스트에 권한 설정 및 프로바이더 추가

Manifest.xml

<!--카메라 갤러리 관련 권한 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />


<uses-feature
    android:name="android.hardware.camera"
    android:required="true" />

<queries>
    <intent>
        <action android:name="android.media.action.IMAGE_CAPTURE" />
    </intent>
</queries>

 

 

-provider태그안에 authorites에서 com.example.sharelanguage부분을 본인의 패키지명으로 변경한다. (중요)

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="com.example.sharelanguage.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

 

file_paths.xml

-path에서  com.example.sharelanguage부분을 본인의 패키지명으로 변경한다. (중요)

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path
        name="my_images"
        path="Android/data/com.example.sharelanguage/files/Pictures" />
</paths>

 

 

3)카메라 관련 권한 설정 

-카메라 버튼을 클릭하면 입력값(permis_num) 2번에 해당하는 카메라 관련 권한을 체크하는 팝업창이 뜨고 

-권한을 허락하면 카메라로 이동한다.

//4) 카메라
binding.ivCameraRegister.id -> {
    //2번 -> 카메라 권한체크
    settingPermission(2)
}
//카메라 관련 권한을 설정해주는 함수
// permis_num
// -1번 -> 갤러리 권한
// -2번 -> 카메라 권한
fun settingPermission(permis_num: Int) {
    val permis = object : PermissionListener {
        //어떠한 형식을 상속받는 익명 클래스의 객체를 생성하기 위해 다음과 같이 작성
        override fun onPermissionGranted() {
            if (permis_num == 1) {
                //갤러리로 이동
                move_gallery()
            } else if (permis_num == 2) {
                //카메라로 이동
                move_camera()
            }
        }

        override fun onPermissionDenied(deniedPermissions: MutableList<String>?) {
        }
    }
    //1번 -> 갤러리 권한
    if (permis_num == 1) {
        checkPer_gallery(permis)
    }
    //2번 -> 카메라 권한
    if (permis_num == 2) {
        checkPer_camera(permis)
    }
}

 

 

 

4)카메라에서 찍은 사진 이미지뷰에 로딩

-카메라에서 찍은 사진을 File형식으로 변환 후 절대경로를 변수에 저장하고 갤러리에 저장하기 위해 Content형식으로 변환한다 

    *content는 가상 에뮬레이터에서 사용하는 이미지의 경로형식이다.

-카메라에서 사진을 찍은 후 엑티비티로 돌아오는 경우 picture_flag가 2번이 저장된다.

- getUriForFile 메서드의 두번째 입력값에는 패키지이름.fileprovider 형식으로 와야 한다.

//intent를 이용해서 카메라로 이동하는 함수
fun move_camera() {
    Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
        takePictureIntent.resolveActivity(packageManager)?.also {
            //찍은 사진을 File형식으로 변환
            val photoFile: File? = try {
                createImageFile()
            } catch (ex: IOException) {
                null
            }
            //File형식의 Uri를 Content형식의 Uri로 변환
            photoFile?.also {
                val photoURI: Uri = FileProvider.getUriForFile(
                    this,
                    "com.example.sharelanguage.fileprovider",
                    it
                )
                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
                resultLauncher.launch(takePictureIntent)
            }
        }
        //2번 -> 카메라
        picture_flag = 2
    }
}

 

 

-registerForActivityResult에서 카메라에서 갖고온 데이터를 확인하고 picture_flag가 2번(카메라)에 해당하는 조건문에서

카메라에서 찍은 이미지를 이미지뷰에 로딩한다.

//엑티비티에서 받아온 데이터를 반환하는 메서드
resultLauncher =
    registerForActivityResult(ActivityResultContracts.StartActivityForResult())
    {
        //엑티비티에서 데이터를 갖고왔을 때만 실행
        if (it.resultCode == RESULT_OK) {
            //1번 ->  갤러리
            if (picture_flag == 1) {
                //갤러리에서 갖고온 이미지가 있는 경우
                it.data?.data?.let { uri ->
                    //이미지 uri
                    val imageUri: Uri? = it.data?.data
                    //image가 있는 경우에만
                    if (imageUri != null) {
                        Glide.with(applicationContext).load(imageUri).override(500, 500)
                            .into(binding.ivProfileImgRegister)
                    }
                }
            }
            //2번 ->  카메라
            else if (picture_flag == 2) {
                val file = File(fileAbsolutePath)
                var bitmap: Bitmap? = null
                //SDK 28버전 미만인 경우 getBitMap 사용
                if (Build.VERSION.SDK_INT < 28) {
                    //카메라에서 찍은 사진을 비트맵으로 변환
                    bitmap = MediaStore.Images.Media
                        .getBitmap(contentResolver, Uri.fromFile(file))
                    //이미지뷰에 이미지 로딩
                    binding.ivProfileImgRegister.setImageBitmap(bitmap)
                } else {
                    //SDK 28버전 이상인 경우 setImageBitmap 사용
                    //카메라에서 찍은 사진을 디코딩
                    val decode = ImageDecoder.createSource(this.contentResolver,
                        Uri.fromFile(file.absoluteFile))
                    //디코딩한  사진을 비트맵으로 변환
                    bitmap = ImageDecoder.decodeBitmap(decode)
                    //이미지뷰에 이미지 로딩
                    binding.ivProfileImgRegister.setImageBitmap(bitmap)
                    //갤러리에 저장
                }

                if (bitmap != null) {
                    saveImageFile(file.name, getExtension(file.name), bitmap)
                }
            }

        }
    }

 

 

 

5)카메라에서 찍은 사진 갤러리에 저장

//이미지의 확장자를 추출하는 메서드
fun getExtension(fileStr: String): String {
    val fileExtension = fileStr.substring(fileStr.lastIndexOf(".") + 1, fileStr.length);
    return fileExtension
}
//갤러리에 찍은 사진을 저장하는 메서드
fun saveImageFile(filename: String, mimeType: String, bitmap: Bitmap): Uri? {
    //이미지 Uri 생성
    //contentValues는 ContentResolver가 사용하는 데이터 정보이다.
    var values = ContentValues()
    //contentValues의 이름, 타입을 정한다.
    values.put(MediaStore.Images.Media.DISPLAY_NAME, filename)
    values.put(MediaStore.Images.Media.MIME_TYPE, mimeType)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        // 파일 저장을 완료하기 전까지 다른 곳에서 해당 데이터를 요청하는 것을 무시
        values.put(MediaStore.Images.Media.IS_PENDING, 1)
    }


    // MediaStore에 파일 등록
    val uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
    try {
        if (uri != null) {
            // 파일 디스크립터 획득
            val descriptor = contentResolver.openFileDescriptor(uri, "w")
            if (descriptor != null) {
                // FileOutputStream으로 비트맵 파일 저장. 숫자는 압축률
                val fos = FileOutputStream(descriptor.fileDescriptor)
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos)
                fos.close()

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    // 데이터 요청 무시 해제
                    values.clear()
                    values.put(MediaStore.Images.Media.IS_PENDING, 0)
                    contentResolver.update(uri, values, null, null)
                }
            }
        }
    } catch (e: java.lang.Exception) {
        Log.e("File", "error=")
    }
    return uri
}

 

 

 

2.갤러리에서 이미지 선택하고 이미지뷰에 로딩하기

//갤러리 관련 권한 체크
fun checkPer_gallery(permis: PermissionListener) {
    TedPermission.with(applicationContext)
        .setPermissionListener(permis)
        .setDeniedMessage("[설정] > [권한] 에서 권한을 허용할 수 있습니다.")
        .setPermissions(
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE
        ).check()
}

fun move_gallery() {
    val intent = Intent(Intent.ACTION_PICK)
    intent.data = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
    intent.type = "image/*"
    //이미지 여러장 선택하기
    //엑티비티이동
    resultLauncher.launch(intent)
    //1번 -> 갤러리
    picture_flag = 1
}

 

//1번 ->  갤러리
if (picture_flag == 1) {
    //갤러리에서 갖고온 이미지가 있는 경우
    it.data?.data?.let { uri ->
        //이미지 uri
        val imageUri: Uri? = it.data?.data
        //image가 있는 경우에만
        if (imageUri != null) {
            Glide.with(applicationContext).load(imageUri).override(500, 500)
                .into(binding.ivProfileImgRegister)
        }
    }
}

 

전체 코드

package com.example.sharelanguage.Activity.LoginRegister


import android.Manifest
import android.content.ContentValues
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.ImageDecoder
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.provider.MediaStore
import android.util.Log
import android.view.View
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import androidx.databinding.DataBindingUtil
import com.bumptech.glide.Glide
import com.example.sharelanguage.R
import com.example.sharelanguage.databinding.ActivityIdRegisterBinding
import com.gun0912.tedpermission.PermissionListener
import com.gun0912.tedpermission.TedPermission
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.*

class ID_Register : AppCompatActivity(), View.OnClickListener {
    private val TAG = "MainActivity"
    private lateinit var binding: ActivityIdRegisterBinding
    private var picture_flag = 0
    private var fileAbsolutePath: String? = null

    //갤러리, 카메라에서  데이터(사진) 갖고올 때 사용
    lateinit var resultLauncher: ActivityResultLauncher<Intent>
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_id_register)

        //버튼 리스너
        binding.btnNextLRegister.setOnClickListener(this) //다음 버튼
        binding.btnBackLRegister.setOnClickListener(this) //뒤로가기 버튼
        binding.btnRedundancyRegister.setOnClickListener(this)//중복체크 버튼
        binding.ivCameraRegister.setOnClickListener(this)//카메라
        binding.ivGalleryRegister.setOnClickListener(this)//갤러리
        binding.ivSearchDetailRegister.setOnClickListener(this)//비밀번호 보이게하기

        //엑티비티에서 받아온 데이터를 반환하는 메서드
        resultLauncher =
            registerForActivityResult(ActivityResultContracts.StartActivityForResult())
            {
                //엑티비티에서 데이터를 갖고왔을 때만 실행
                if (it.resultCode == RESULT_OK) {
                    //1번 ->  갤러리
                    if (picture_flag == 1) {
                        //갤러리에서 갖고온 이미지가 있는 경우
                        it.data?.data?.let { uri ->
                            //이미지 uri
                            val imageUri: Uri? = it.data?.data
                            //image가 있는 경우에만
                            if (imageUri != null) {
                                Glide.with(applicationContext).load(imageUri).override(500, 500)
                                    .into(binding.ivProfileImgRegister)
                            }
                        }
                    }
                    //2번 ->  카메라
                    else if (picture_flag == 2) {
                        val file = File(fileAbsolutePath)
                        var bitmap: Bitmap? = null
                        //SDK 28버전 미만인 경우 getBitMap 사용
                        if (Build.VERSION.SDK_INT < 28) {
                            //카메라에서 찍은 사진을 비트맵으로 변환
                            bitmap = MediaStore.Images.Media
                                .getBitmap(contentResolver, Uri.fromFile(file))
                            //이미지뷰에 이미지 로딩
                            binding.ivProfileImgRegister.setImageBitmap(bitmap)
                        } else {
                            //SDK 28버전 이상인 경우 setImageBitmap 사용
                            //카메라에서 찍은 사진을 디코딩
                            val decode = ImageDecoder.createSource(this.contentResolver,
                                Uri.fromFile(file.absoluteFile))
                            //디코딩한  사진을 비트맵으로 변환
                            bitmap = ImageDecoder.decodeBitmap(decode)
                            //이미지뷰에 이미지 로딩
                            binding.ivProfileImgRegister.setImageBitmap(bitmap)
                            //갤러리에 저장
                        }

                        if (bitmap != null) {
                            saveImageFile(file.name, getExtension(file.name), bitmap)
                        }
                    }

                }
            }


    }

    override fun onClick(v: View?) {
        when (v?.id) {
            //1)다음 버튼
            binding.btnNextLRegister.id -> {

                val intent = Intent(applicationContext, Profile_Register::class.java)
                startActivity(intent)
            }

            //2) 뒤로가기 버튼
            binding.btnBackLRegister.id -> {
                finish()
            }

            //3) 중복체크 버튼
            binding.btnRedundancyRegister.id -> {

            }

            //4) 카메라
            binding.ivCameraRegister.id -> {
                //2번 -> 카메라 권한체크
                settingPermission(2)
            }

            //5) 갤러리
            binding.ivGalleryRegister.id -> {
                //1번 -> 갤러리 권한체크
                settingPermission(1)


            }

            //6) 비밀번호 보이게하기
            binding.ivSearchDetailRegister.id -> {

            }
            else -> {
            }
        }
    }

    //사진을 찍고 이미지를 파일로 저장해 주는 함수
    @Throws(IOException::class)
    private fun createImageFile(): File {
        val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
        //이미지 경로 지정
        val storageDir: File? = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
        return File.createTempFile(
            "JPEG_${timeStamp}_",
            ".jpg",
            storageDir
        ).apply {
            //절대경로 변수에 저장
            fileAbsolutePath = absolutePath
        }
    }

    //이미지의 확장자를 추출하는 메서드
    fun getExtension(fileStr: String): String {
        val fileExtension = fileStr.substring(fileStr.lastIndexOf(".") + 1, fileStr.length);
        return fileExtension
    }

    fun move_gallery() {
        val intent = Intent(Intent.ACTION_PICK)
        intent.data = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
        intent.type = "image/*"
        //이미지 여러장 선택하기
        //엑티비티이동
        resultLauncher.launch(intent)
        //1번 -> 갤러리
        picture_flag = 1
    }

    //intent를 이용해서 카메라로 이동하는 함수
    fun move_camera() {
        Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
            takePictureIntent.resolveActivity(packageManager)?.also {
                //찍은 사진을 File형식으로 변환
                val photoFile: File? = try {
                    createImageFile()
                } catch (ex: IOException) {
                    null
                }
                //File형식의 Uri를 Content형식의 Uri로 변환
                photoFile?.also {
                    val photoURI: Uri = FileProvider.getUriForFile(
                        this,
                        "com.example.sharelanguage.fileprovider",
                        it
                    )
                    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
                    resultLauncher.launch(takePictureIntent)
                }
            }
            //2번 -> 카메라
            picture_flag = 2
        }
    }

    //갤러리에 찍은 사진을 저장하는 메서드
    fun saveImageFile(filename: String, mimeType: String, bitmap: Bitmap): Uri? {
        //이미지 Uri 생성
        //contentValues는 ContentResolver가 사용하는 데이터 정보이다.
        var values = ContentValues()
        //contentValues의 이름, 타입을 정한다.
        values.put(MediaStore.Images.Media.DISPLAY_NAME, filename)
        values.put(MediaStore.Images.Media.MIME_TYPE, mimeType)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            // 파일 저장을 완료하기 전까지 다른 곳에서 해당 데이터를 요청하는 것을 무시
            values.put(MediaStore.Images.Media.IS_PENDING, 1)
        }


        // MediaStore에 파일 등록
        val uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
        try {
            if (uri != null) {
                // 파일 디스크립터 획득
                val descriptor = contentResolver.openFileDescriptor(uri, "w")
                if (descriptor != null) {
                    // FileOutputStream으로 비트맵 파일 저장. 숫자는 압축률
                    val fos = FileOutputStream(descriptor.fileDescriptor)
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos)
                    fos.close()

                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                        // 데이터 요청 무시 해제
                        values.clear()
                        values.put(MediaStore.Images.Media.IS_PENDING, 0)
                        contentResolver.update(uri, values, null, null)
                    }
                }
            }
        } catch (e: java.lang.Exception) {
            Log.e("File", "error=")
        }
        return uri
    }


    //카메라 관련 권한을 설정해주는 함수
    // permis_num
    // -1번 -> 갤러리 권한
    // -2번 -> 카메라 권한
    fun settingPermission(permis_num: Int) {
        val permis = object : PermissionListener {
            //어떠한 형식을 상속받는 익명 클래스의 객체를 생성하기 위해 다음과 같이 작성
            override fun onPermissionGranted() {
                if (permis_num == 1) {
                    //갤러리로 이동
                    move_gallery()
                } else if (permis_num == 2) {
                    //카메라로 이동
                    move_camera()
                }
            }

            override fun onPermissionDenied(deniedPermissions: MutableList<String>?) {
            }
        }
        //1번 -> 갤러리 권한
        if (permis_num == 1) {
            checkPer_gallery(permis)
        }
        //2번 -> 카메라 권한
        if (permis_num == 2) {
            checkPer_camera(permis)
        }
    }
    //갤러리 관련 권한 체크
    fun checkPer_gallery(permis: PermissionListener) {
        TedPermission.with(applicationContext)
            .setPermissionListener(permis)
            .setDeniedMessage("[설정] > [권한] 에서 권한을 허용할 수 있습니다.")
            .setPermissions(
                Manifest.permission.WRITE_EXTERNAL_STORAGE,
                Manifest.permission.READ_EXTERNAL_STORAGE
            ).check()
    }

    //카메라 관련 체크
    fun checkPer_camera(permis: PermissionListener) {
        TedPermission.with(applicationContext)
            .setPermissionListener(permis)
            .setDeniedMessage("[설정] > [권한] 에서 권한을 허용할 수 있습니다.")
            .setPermissions(
                Manifest.permission.WRITE_EXTERNAL_STORAGE,
                Manifest.permission.READ_EXTERNAL_STORAGE,
                Manifest.permission.CAMERA
            ).check()
    }

}

 

 

참고

[Android Studio] Camera 촬영 및 내부/외부 저장소에 저장

[ Kotlin ] 코틀린 안드로이드 카메라 원본 이미지 저장 방법

출처:

https://believecom.tistory.com/724

[BelieveCom]

[Android/Kotlin] 카메라로 사진 찍고 이미지뷰에 넣기

https://kangmin1012.tistory.com/22

[Kotlin] 8장. 앱 개발 - 카메라, 갤러리, 쓰레드

https://velog.io/@hwi_chance/Kotlin-8장.-앱-개발-카메라-갤러리-쓰레드

[android] targetSdkVersion 30, intent.resolveActivity가 null일때

action.IMAGE_CAPTURE가 안될 때 사용

https://shary1012.tistory.com/249

안드로이드 파일의 확장자 알아내기 getFileExtensionFromUrl

https://stickyny.tistory.com/97

 

댓글