Kotlin : 사진 등록 처리

2023. 8. 25. 00:38[Android APP] feat. Kotlin/Kotlin 공부

아이템을 추가한 뒤 그 아이템에 제목과 정보를 넣어주고 사진을 첨부할 수 있도록 만들었다.

 

 

1. 기존 이미지 조회 후, 사진 등록 버튼 클릭 시 pickPicture 함수 실행

//이미지 조회(pick Picture 함수를 실행 시키면 사진 편집이 가능하다)
binding.imageRecycler.apply {
    layoutManager = LinearLayoutManager(this@WorkEditPhoto)
    adapter = OutsideAdapter(this@WorkEditPhoto, userToken, sysDocNum, consCode, imageOutsideData, {deleteItem(it)}, {pickPicture(it)})
}

 

2. 새로운 리사이클러 아이템 추가

//아이템 추가 버튼
binding.addItemBtn.setOnClickListener {
    addItem()
}
//아이템 추가
@SuppressLint("NotifyDataSetChanged")
fun addItem() {
    val division = arrayListOf<InsideImageInfoList>()
    imageOutsideInputData = OutSideImageInfo(defaultTitle, division)
    imageOutsideData.add(imageOutsideInputData)
    binding.imageRecycler.adapter?.notifyDataSetChanged()
}

 

3. 사진 등록 클릭 시(picPicture 함수) 

//사진 선택 다이어로그
private fun pickPicture(position : Int){
    val dialog = Dialog(this@WorkEditPhoto)
    dialog.setContentView(R.layout.custom_dialog_camera)

    val text =dialog.findViewById<TextView>(R.id.camera_title)
    val camera = dialog.findViewById<Button>(R.id.camera_btn)
    val album = dialog.findViewById<Button>(R.id.album_btn)

    text.text = "사진 등록"

    camera.setOnClickListener {
        dispatchTakePictureIntentEx(position)
        dialog.dismiss()
    }

    album.setOnClickListener {
        val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
        intent.type = "image/*"
        intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
        positions = position
        Log.d("position_value", position.toString())
        startActivityForResult(intent, CodeList.Album)
        dialog.dismiss()
    }

    dialog.window!!.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT)
    dialog.setCanceledOnTouchOutside(true)
    dialog.setCancelable(true)
    dialog.show()
}

 

 

4. 카메라 호출 함수(camera로 등록 클릭 시)

//카메라 호출 함수
@SuppressLint("SimpleDateFormat")
private fun dispatchTakePictureIntentEx(position: Int)
{
    val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
    val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
    val uri : Uri? = createImageUri("JPEG_${timeStamp}")
    photoURI = uri
    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
    positions = position
    startActivityForResult(takePictureIntent, CodeList.Camera)
}

카메라 호출 후 사진 저장(jpeg 형태로 저장)

//카메라 사진 저장 함수
private fun createImageUri(filename:String): Uri?{
    val values = ContentValues()
    values.put(MediaStore.Images.Media.DISPLAY_NAME,filename)
    values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
    return contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
}

 

5. 데이터 삭제(data로 imageOutsideData 리스트의 OutsideImageInfo 데이터를 삭제해준다.)

//사진 리스트 삭제(Outside)
@SuppressLint("NotifyDataSetChanged")
private fun deleteItem(data : OutSideImageInfo){
    imageOutsideData.remove(data)
    binding.imageRecycler.adapter?.notifyDataSetChanged()
}

 

6. 앨범, 카메라 처리(multiple 처리를 통해 사진 여러장을 동시에 등록할 수 있다)

@SuppressLint("NotifyDataSetChanged")
@Deprecated("Deprecated in Java")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (resultCode != Activity.RESULT_OK) {
        return
    }
    when (requestCode) {
        //사진 - 파일 탐색기에서 가져오기
        CodeList.Album -> {
            data ?: return
            val clipData = data.clipData

            Log.d("position", positions.toString())

            if (clipData == null || clipData.itemCount == 1) {
                // Single photo selected
                val uri = if (clipData != null)
                clipData.getItemAt(0).uri
                else data.data

                val filename = getName(uri)
                val imgPath = usiPathUtil.getRealPathFromURI(this, uri!!)
                if (filename != "" && imgPath != null) {
                    val `in` = contentResolver.openInputStream(uri)
                    val file = File(applicationContext.filesDir, filename)
                    if (`in` != null) {
                        try {
                            val out: OutputStream = FileOutputStream(file)
                            try {
                                val buf = ByteArray(4096)
                                var len: Int
                                while (`in`.read(buf).also { len = it } > 0) {
                                    out.write(buf, 0, len)
                                }
                                val title = imageOutsideData[positions].parentTitle //position은 현재 등록할(선택된) 아이템의 인덱스를 의미한다.
                                imageOutsideData[positions].fileList.add(InsideImageInfoList(imageOutsideData[positions].fileList.size, filename, "", file.path, title,file))
                                binding.imageRecycler.adapter?.notifyDataSetChanged()
                            } finally {
                                out.close()
                            }
                        } finally {
                            `in`.close()
                        }
                    }
                }
            } else {
                // Multiple photos selected
                for (i in 0 until clipData.itemCount) {
                     val uri = clipData.getItemAt(i).uri

                    Log.d("position", positions.toString())

                    val filename = getName(uri)
                    val imgPath = usiPathUtil.getRealPathFromURI(this, uri)
                    if (filename != "" && imgPath != null) {
                        val `in` = contentResolver.openInputStream(uri)
                        val file = File(applicationContext.filesDir, filename)
                        if (`in` != null) {
                            try {
                                val out: OutputStream = FileOutputStream(file)
                                try {
                                    val buf = ByteArray(4096)
                                    var len: Int
                                    while (`in`.read(buf).also { len = it } > 0) {
                                        out.write(buf, 0, len)
                                    }
                                    val title = imageOutsideData[positions].parentTitle
                                    imageOutsideData[positions].fileList.add(InsideImageInfoList(imageOutsideData[positions].fileList.size, filename, "", file.path, title, file))
                                    binding.imageRecycler.adapter?.notifyDataSetChanged()
                                } finally {
                                    out.close()
                                }
                            } finally {
                                `in`.close()
                            }
                        }
                    }
                }
            }
        }
        //사진 - 카메라에서 가져오기
        CodeList.Camera -> {
            if( photoURI != null)
            {
                val filename = getName(photoURI)
                val imgPath =  usiPathUtil.getRealPathFromURI(this, photoURI as Uri)
                if (filename != "" && imgPath != null) {
                    Log.d("position", positions.toString())
                    val `in` = contentResolver.openInputStream(photoURI as Uri) //src
                    val file = File(applicationContext.filesDir, filename)
                    if (`in` != null) {
                        try {
                            val out: OutputStream = FileOutputStream(file) //dst
                            try {
                                // Transfer bytes from in to out
                                val buf = ByteArray(4096)
                                var len: Int
                                while (`in`.read(buf).also { len = it } > 0) {
                                    out.write(buf, 0, len)
                                }
                                val title = imageOutsideData[positions].parentTitle
                                imageOutsideData[positions].fileList.add(InsideImageInfoList(imageOutsideData[positions].fileList.size ,filename , "",file.path, title, file))
                                binding.imageRecycler.adapter?.notifyDataSetChanged()

                            } finally {
                                out.close()
                            }
                        } finally {
                            `in`.close()
                        }
                    }
                }
            }
        }
    }
}

 

//Uri to 파일명 추출 함수
private fun getName(uri: Uri?): String {
    val projection = arrayOf(MediaStore.Files.FileColumns.DISPLAY_NAME)
    val cursor = managedQuery(uri, projection, null, null, null)
    val columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME)
    cursor.moveToFirst()
    return cursor.getString(columnIndex)
}

 

7. 아이템과 사진을 모두 등록한 뒤 최종 등록 처리

//등록 버튼
binding.bottomPostBtn.setOnClickListener {
Toast.makeText(this@WorkEditPhoto, "사진이 등록중입니다.", Toast.LENGTH_SHORT).show()
CoroutineScope(Dispatchers.IO).launch {
compareList(imageOriginData, imageOutsideData)
delay(2000)
val intent = Intent(this@WorkEditPhoto, DailyWorkDocument::class.java)
intent.putExtra("userId", userId)
intent.putExtra("sysDocNum", sysDocNum)
intent.putExtra("code", consCode)
startActivity(intent)
finish() }
}

 

8. 

package com.allscapeservice.a22allscape_app.objects

import android.content.ContentUris
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.Environment
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.provider.OpenableColumns
import android.util.Log
import android.webkit.MimeTypeMap
import android.widget.Toast
import java.io.File
import java.io.FileOutputStream

class UriPathUtils
{
    fun getRealPathFromURI(context: Context, uri: Uri): String? {
        when {
            // DocumentProvider
            DocumentsContract.isDocumentUri(context, uri) -> {
                when {
                    // ExternalStorageProvider
                    isExternalStorageDocument(uri) -> {
                        //Toast.makeText(context, "From Internal & External Storage dir", Toast.LENGTH_SHORT).show()
                        val docId = DocumentsContract.getDocumentId(uri)
                        val split = docId.split(":").toTypedArray()
                        val type = split[0]
                        // This is for checking Main Memory
                        return if ("primary".equals(type, ignoreCase = true)) {
                            if (split.size > 1) {
                                Environment.getExternalStorageDirectory().toString() + "/" + split[1]
                            } else {
                                Environment.getExternalStorageDirectory().toString() + "/"
                            }
                            // This is for checking SD Card
                        } else {
                            "storage" + "/" + docId.replace(":", "/")
                        }
                    }
                    isDownloadsDocument(uri) -> {
                        //Toast.makeText(context, "From Downloads dir", Toast.LENGTH_SHORT).show()
                        val fileName = getFilePath(context, uri)
                        if (fileName != null) {
                            return Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName
                        }
                        var id = DocumentsContract.getDocumentId(uri)
                        if (id.startsWith("raw:")) {
                            id = id.replaceFirst("raw:".toRegex(), "")
                            val file = File(id)
                            if (file.exists()) return id
                        }
                        val contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id))
                        return getDataColumn(context, contentUri, null, null)
                    }
                    isMediaDocument(uri) -> {
                        //Toast.makeText(context, "From Documents dir", Toast.LENGTH_SHORT).show()
                        val docId = DocumentsContract.getDocumentId(uri)
                        val split = docId.split(":").toTypedArray()
                        val type = split[0]
                        var contentUri: Uri? = null


                        when (type) {
                            "image" -> {
                                contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
                            }
                            "video" -> {
                                contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
                            }
                            "audio" -> {
                                contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
                            }
                            "document" -> {
                                    val fileName = getFilePath(context, uri)
                                    if (fileName != null) {
                                        return Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName
                                    }
                                    var id = DocumentsContract.getDocumentId(uri)
                                    if (id.startsWith("raw:")) {
                                        id = id.replaceFirst("raw:".toRegex(), "")
                                        val file = File(id)
                                        if (file.exists()) return id
                                    }
                                    val contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id))
                                    return getDataColumn(context, contentUri, null, null)
                            }
                        }
                        val selection = "_id=?"
                        val selectionArgs = arrayOf(split[1])
                        return getDataColumn(context, contentUri, selection, selectionArgs)
                    }
                    isGoogleDriveUri(uri) -> {
                        Toast.makeText(context, "From Google Drive", Toast.LENGTH_SHORT).show()
                        return getDriveFilePath(uri, context)
                    }
                }
            }
            "content".equals(uri.scheme, ignoreCase = true) -> {
                //Toast.makeText(context, "From Content Uri", Toast.LENGTH_SHORT).show()
                // Return the remote address
                return if (isGooglePhotosUri(uri)) uri.lastPathSegment else getDataColumn(context, uri, null, null)
            }
            "file".equals(uri.scheme, ignoreCase = true) -> {
                //Toast.makeText(context, "From File Uri", Toast.LENGTH_SHORT).show()
                return uri.path
            }
        }
        return null
    }

    private fun getDataColumn(context: Context, uri: Uri?, selection: String?, selectionArgs: Array<String>?): String? {
        var cursor: Cursor? = null
        val column = "_data"
        val projection = arrayOf(column)
        try {
            if (uri == null) return null
            cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)


            //여기서부터 갈림
            if (cursor != null && cursor.moveToFirst()) {
                val index = cursor.getColumnIndexOrThrow(column)

                return cursor.getString(index)
            }
        } finally {
            cursor?.close()
        }
        return null
    }

    //파일 확장자 함수
    fun getFileExtension(context : Context, uri: Uri): String? {
        val fileType: String? = context.contentResolver.getType(uri)
        return MimeTypeMap.getSingleton().getExtensionFromMimeType(fileType)
    }


    private fun getMediaDocumentPath(context: Context, uri: Uri?,selection: String?, selectionArgs: Array<String>?): String? {
        var cursor: Cursor? = null
        val column = "_data"
        val projection = arrayOf(column)
        try {
            if (uri == null) return null
            cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)

            if (cursor != null && cursor.moveToFirst()) {
                val index = cursor.getColumnIndexOrThrow(column)
                return cursor.getString(index)
            }
        } finally {
            cursor?.close()
        }
        return null
    }

    private fun getFilePath(context: Context, uri: Uri?): String? {
        var cursor: Cursor? = null
        val projection = arrayOf(MediaStore.MediaColumns.DISPLAY_NAME)
        try {
            if (uri == null) return null
            cursor = context.contentResolver.query(uri, projection, null, null,
                null)
            if (cursor != null && cursor.moveToFirst()) {
                val index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME)
                return cursor.getString(index)
            }
        } finally {
            cursor?.close()
        }
        return null
    }

    private fun getDriveFilePath(uri: Uri, context: Context): String? {
        val returnCursor = context.contentResolver.query(uri, null, null, null, null)
        val nameIndex = returnCursor!!.getColumnIndex(OpenableColumns.DISPLAY_NAME)
       // val sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE)
        returnCursor.moveToFirst()
        val name = returnCursor.getString(nameIndex)
        //val size = returnCursor.getLong(sizeIndex).toString()
        val file = File(context.cacheDir, name)
        try {
            val inputStream = context.contentResolver.openInputStream(uri)
            val outputStream = FileOutputStream(file)
            var read = 0
            val maxBufferSize = 1 * 1024 * 1024
            val bytesAvailable = inputStream!!.available()

            //int bufferSize = 1024;
            val bufferSize = Math.min(bytesAvailable, maxBufferSize)
            val buffers = ByteArray(bufferSize)
            while (inputStream.read(buffers).also { read = it } != -1) {
                outputStream.write(buffers, 0, read)
            }
            inputStream.close()
            outputStream.close()
        } catch (e: Exception) {
            Log.e("Exception", e.message!!)
        }
        return file.path
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is ExternalStorageProvider.
     */
    private fun isExternalStorageDocument(uri: Uri): Boolean {
        return "com.android.externalstorage.documents" == uri.authority
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is DownloadsProvider.
     */
    private fun isDownloadsDocument(uri: Uri): Boolean {
        return "com.android.providers.downloads.documents" == uri.authority
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is MediaProvider.
     */
    private fun isMediaDocument(uri: Uri): Boolean {
        return "com.android.providers.media.documents" == uri.authority
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is Google Photos.
     */
    private fun isGooglePhotosUri(uri: Uri): Boolean {
        return "com.google.android.apps.photos.content" == uri.authority
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is Google Photos.
     */
    private fun isGoogleDriveUri(uri: Uri): Boolean {
        return "com.google.android.apps.docs.storage" == uri.authority || "com.google.android.apps.docs.storage.legacy" == uri.authority
    }
}

 

안드로이드에서 카메라 사용 시 서버로 사진파일을 전송할 때(저장할 때) 썸네일이 들어가 화질이 깨지는 문제와

사진이 회전되서 저장되는 문제도 고려해야한다. 이 문제를 해결하기 위해 조금 시간을 썼었던 기억이 난다.

반응형