카테고리 없음

[Android/Kotlin] Gridview를 이용하여 갤러리 & 카메라 구현

dev_zoe 2021. 8. 20. 03:09
반응형

1) 우선 다음과 같은 설정 필요

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tipklemoa.tipkle">

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.CAMERA" />
    ...
    
    <!-- 이부분 주의! 반드시 require=false를 해야 앱 출시할때 사용가능 기기가 나옴 -->
    <uses-feature
        android:name="android.hardware.camera2"
        android:required="false" />
        
    <application
        android:requestLegacyExternalStorage="true" <!-- 이부분 추가 -->
        
        ....
     <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.tipklemoa.tipkle"
            android:exported="false"
            android:grantUriPermissions="true">
        <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>
        
        ...
        
 </manifest>

res/xml/file_paths

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
        name="external"
        path="/Pictures/팁끌" />
    <external-files-path
        name="external_files"
        path="/Pictures/팁끌" />
    <cache-path
        name="cache"
        path="/Pictures/팁끌" />
    <external-cache-path
        name="external_cache"
        path="/Pictures/팁끌" />
    <files-path
        name="files"
        path="/Pictures/팁끌" />
</paths>

 

2) SelectPicActivity

class SelectPicActivity : BaseActivity<ActivitySelectPicBinding>(ActivitySelectPicBinding::inflate) {

    var adapter: SelectPicAdapter?=null
    var uriArr = arrayListOf("1") //맨 첫번째에 카메라 아이콘을 넣기위함
    private lateinit var timeStamp:String
    private lateinit var imageFileName:String //현재 파일 이름
    var selectedviewList = arrayListOf<View>()
    var selectedimageUrlList = arrayListOf<String>() //선택된 이미지 리스트
    var selectCount = 0 //오른쪽 상단에 선택된 이미지의 개수

    //아래는 카메라 관련
    private val REQUEST_TAKE_PHOTO = 1
    private lateinit var currentPhotoPath: String

    private var readGalleryListener: PermissionListener = object : PermissionListener {
        override fun onPermissionGranted() {
            getAllShownImagesPath()

            adapter = SelectPicAdapter(this@SelectPicActivity, uriArr) //gridview adapter
            binding.rvGallery.numColumns = 3 // 한 줄에 3개씩
            binding.rvGallery.adapter = adapter
        }

        override fun onPermissionDenied(deniedPermissions: MutableList<String>?) {
            showCustomToast("갤러리 권한 접근을 허용하지 않으셨습니다")
        }
    }

    private var takePicListener: PermissionListener = object : PermissionListener {
        override fun onPermissionGranted() {
            takePictureIntent()
        }

        override fun onPermissionDenied(deniedPermissions: MutableList<String>?) {
            showCustomToast("카메라 권한을 허용하지 않으셨습니다")
        }
    }


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        //권한 체크
        TedPermission.with(applicationContext)
            .setPermissionListener(readGalleryListener)
            .setPermissions(
                Manifest.permission.READ_EXTERNAL_STORAGE,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
            )
            .check()

        binding.imgPicBack.setOnClickListener {
            finish()
        }

//        selectedimageUrlList = if (intent.getStringArrayListExtra("selectedimageUrlList")!=null){
//            intent.getStringArrayListExtra("selectedimageUrlList")!!
//        } else{
//            arrayListOf()
//        }

        binding.rvGallery.setOnItemClickListener { parent, view, position, id ->
            val selectedImage = binding.rvGallery.getItemAtPosition(position)
            val imgBackground = view.findViewById<ImageView>(R.id.imgSelectBackGround)
            val tvSelectImageCount = view.findViewById<TextView>(R.id.tvSelectImageCount)
            val imgFrame = view.findViewById<ImageView>(R.id.imgFrame)

            if (position>=1) { //이미지 부분 눌러서 추가해주는 부분
                if (imgBackground.visibility == View.INVISIBLE) { //이미지 처음 누를때
                    if (selectCount>=5){ //5개 이상인 상태에서 누르면 띄워주고 막기
                        showCustomToast("사진은 최대 5개 선택할 수 있습니다.")
                    }
                    else { //5개 미만
                        selectedviewList.add(view)
                        selectCount++

                        imgBackground.visibility = View.VISIBLE //위에 프레임이랑 순서숫자 보여주는 부분
                        tvSelectImageCount.visibility = View.VISIBLE
                        imgFrame.visibility = View.VISIBLE

                        selectedimageUrlList.add(selectedImage.toString())
                        Log.d("test", selectedimageUrlList.size.toString())
                    }
                } else { //이미 눌린 상태면
                    selectedviewList.remove(view)
                    selectCount--

                    imgBackground.visibility = View.INVISIBLE
                    tvSelectImageCount.visibility = View.INVISIBLE
                    imgFrame.visibility = View.INVISIBLE
                    //바로 전 뷰의 순서를 -1 해주어야함!
                    if (selectCount>=2) { //2개이상일때
                        selectedviewList[(selectedviewList.size-1)].findViewById<TextView>(R.id.tvSelectImageCount).text = selectCount.toString()
                    }

                    selectedimageUrlList.remove(selectedImage)
                    Log.d("test", selectedimageUrlList.size.toString())
                }
                tvSelectImageCount.text = selectCount.toString()
                binding.tvSelectPicCount.text = selectCount.toString()
            }
            else if (position==0){ //사진찍기
                if (selectCount>=5){ //5개 이상인 상태에서 누르면 띄워주고 막기
                    showCustomToast("사진은 최대 5개 선택할 수 있습니다.")
                }
                else{
                    TedPermission.with(applicationContext)
                        .setPermissionListener(takePicListener)
                        .setPermissions(
                            Manifest.permission.CAMERA
                        )
                        .check()
                }
            }
        }

        binding.tvCompleteAddPic.setOnClickListener {
            val toRegisterIntent = Intent(this, RegisterNewTipActivity::class.java)
            toRegisterIntent.putExtra("selectedimageUrlList", selectedimageUrlList)
            startActivity(toRegisterIntent)
            finish()
        }
    }

    // 사진 찍는 인텐트
    private fun takePictureIntent() {
        Log.d("test", "takePictureIntent")
        val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) //사진 인텐트
        if (takePictureIntent.resolveActivity(packageManager) != null) {

            val photoFile: File?
            try {
                photoFile = createImageFile()
            } catch (ex: IOException) {
                Log.e("captureCamera Error", ex.toString())
                return
            }
            // getUriForFile의 두 번째 인자는 Manifest provier의 authorites와 일치해야 함
            val providerURI = FileProvider.getUriForFile(this, packageName, photoFile)
            // 인텐트에 전달할 때는 FileProvier의 Return값인 content://로만!!, providerURI의 값에 카메라 데이터를 넣어 보냄
            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, providerURI)
            startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO)
        }
    }

    //사진을 찍었을때 이미지 만드는 부분
    @Throws(IOException::class)
    fun createImageFile(): File { // Create an image file name
        timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", getDefault()).format(Date())
        imageFileName = "JPEG_$timeStamp.jpg"
        val imageFile: File?
        val storageDir = File(
            getExternalStorageDirectory().toString() + "/Pictures",
            "팁끌"
        )
        if (!storageDir.exists()) {
            Log.i("mCurrentPhotoPath1", storageDir.toString())
            storageDir.mkdirs()
        }
        imageFile = File(storageDir, imageFileName)
        currentPhotoPath = imageFile.absolutePath
        return imageFile
    }
    
    //갤러리의 모든 image path -> uri 반환하는 부분
    private fun getAllShownImagesPath() {
        val uriExternal: Uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
        var columnIndexID: Int
        var imageId: Long
        val cursor = contentResolver.query(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            null,
            null,
            null,
            MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC"
        )
        if (cursor != null) {
            while (cursor.moveToNext()) {
                // 사진 경로 Uri 가져오기
                columnIndexID = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
                while (cursor.moveToNext()) {
                    imageId = cursor.getLong(columnIndexID)
                    val uriImage = Uri.withAppendedPath(uriExternal, "" + imageId)
                    uriArr.add(uriImage.toString())
                }
            }
            cursor.close()
        }
    }

    //갤러리에 저장 & 방금 찍은 사진 리스트에 저장
    private fun savePhoto() {
        //사진 폴더에 저장하기 위한 경로 선언
        val file = File(currentPhotoPath)
        val uri = Uri.fromFile(file)
        MediaScannerConnection.scanFile(this, arrayOf(file.toString()),
            null, null)
        selectedimageUrlList.add(file.toUri().toString())
    }

    @RequiresApi(Build.VERSION_CODES.Q)
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if(requestCode == REQUEST_TAKE_PHOTO && resultCode == RESULT_OK){
//         사진저장
            savePhoto()

//           팁 등록 페이지로 이미지 리스트를 넘김
            val intent = Intent(this, RegisterNewTipActivity::class.java)
            intent.putStringArrayListExtra("selectedimageUrlList", selectedimageUrlList)
            startActivity(intent)
            finish()
        }
    }
반응형