본문 바로가기

개발 일기/Android

[Kotlin] 간단 메모장 만들기 5 #Camera #Gallery #PopupMenu

이번에는 카메라에서 사진을 찍어 이미지를 가져오고 갤러리에서 이미지를 불러올 것이다.

 

참고: https://developer.android.com/training/camera/photobasics?hl=ko

 

사진 촬영  |  Android 개발자  |  Android Developers

이 과정에서는 기존 카메라 애플리케이션을 사용하여 사진을 캡처하는 방법을 설명합니다. 클라이언트 앱을 실행하는 기기에서 촬영한 하늘 사진을 조합하여 세계 날씨 지도를 만드는 크라우드 소싱 날씨 서비스를 구현하고 있다고 가정하겠습니다…

developer.android.com

 

- Menu

 

WriteActivity에 ImageButton을 넣어 PopupMenu를 띄워준다. 

 

WriteActivity.kt

 

private fun showImagePopup(view: View) {
        PopupMenu(this, view).apply {
            setOnMenuItemClickListener(this@내Activity)
            inflate(R.menu.menu) //내가 만든 메뉴 넣기
            show()
        }
}

 override fun onMenuItemClick(item: MenuItem?): Boolean {
        return when (item!!.itemId) {
            R.id.menu_write_camera -> {
                //클릭시 실행되는 곳
                true
            }
            R.id.menu_write_gallery -> {
                //클릭시 실행되는 곳
                true
            }
            R.id.menu_write_url -> {
                //클릭시 실행되는 곳
                true
            }
            else -> false
        }
}

 

inflate에 내가 만든 menu를 넣어준다.

layout.menu에 menu.xml을 만들어 준다.

 

type에 Menu를 골라주면 menu.xml을 만들 수 있다.

 

menu.xml

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/menu_write_camera"
        android:title="카메라"
        app:showAsAction="never" />
    <item
        android:id="@+id/menu_write_gallery"
        android:title="갤러리"
        app:showAsAction="never" />
    <item
        android:id="@+id/menu_write_url"
        android:title="URL"
        app:showAsAction="never" />
</menu>

 

showAsAction에 설정을 주면 보여주는 모습을 바꿀 수 있다.

 

- 카메라

 

카메라를 사용하고 저장하려면 두 가지의 permission허용이 필요하다. 

 

manifests

<manifest ...>

        <uses-permission android:name="android.permission.CAMERA" />
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
      
</manifest>

 

WriteActivity.kt

 

 private fun startCamera() {
        Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
            takePictureIntent.resolveActivity(packageManager)?.also {
                val photoFile: File? = try {
                    createImageFile()
                } catch (ex: IOException) {
                    Snackbar.make(
                        findViewById(R.id.Write_Layout_view),
                        "이미지를 불러오지 못했습니다.",
                        Snackbar.LENGTH_LONG
                    ).show()
                    null
                }
                photoFile?.also {
                    val photoURI: Uri = FileProvider.getUriForFile(
                        this,
                        BuildConfig.APPLICATION_ID,
                        it
                    )
                    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
                    startActivityForResult(takePictureIntent, REQUEST_ACTION_IMAGE_CAPTURE)
                }
            }
        }
    }

 

Intent(MediaStore.ACTION_IMAGE_CAPTURE)로 기본 카메라 어플을 띄워주고 찍은 파일을 내 어플리케이션 폴더에 저장되도록 설정해주기 위해 createImageFile()함수를 실행한다.

 

private lateinit var currentPhotoPath: String
private lateinit var currentPhotoName: String

@Throws(IOException::class)
    private fun createImageFile(): File {
        // Create an image file name
        val timeStamp: String =
            SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
        val storageDir: File = getExternalFilesDir(Environment.DIRECTORY_PICTURES)!!
        currentPhotoName = "${timeStamp}_.jpg"
        return File.createTempFile(
            "${timeStamp}_",
            ".jpg", /* suffix */
            storageDir /* directory */
        ).apply {
            // Save a file: path for use with ACTION_VIEW intents
            currentPhotoPath = absolutePath
        }
    }

 

getExternalFilesDir()를 통해 내 어플리케이션 내부 폴더에 접근할 수 있다. 이 폴더는 어플 삭제시 지워지는 폴더이다.

currentPhotoPath과 currentPhotoName는 나중에 사용하기위해 전역변수로 설정하였다.

 

위에서

takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
startActivityForResult(takePictureIntent, REQUEST_ACTION_IMAGE_CAPTURE)

이런 식으로 넘겨주었기 때문에 onActivityResult()로 override 할 수 있다.

 

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == REQUEST_ACTION_IMAGE_CAPTURE && resultCode == RESULT_OK) {
            //여기서 행동한다.
        } 
    }

 

갤러리에서 가져오는 것은 많은 고생을 했다... 그건 뒤에 설명하고 일단 기본 카메라에서 가져오는 것을 설명하겠다.

REQUEST_ACTION_IMAGE_CAPTURE로 위에서 한 행동에 대한 결과가 넘어오는지 알 수 있다.

여기서 뷰에 보여주면 끝! 여기서 보여주기 위해 아까 currentPhotoPath를 전역변수로 남겨둔 것이다.

Glide를 쓰면 쉽게 뷰에 보여줄 수 있다.

 

- 갤러리

 

Intent(Intent.ACTION_GET_CONTENT).setType("image/*").also {
	startActivityForResult(Intent.createChooser(it, "Get Album"), REQUEST_ACTION_PICK)
}

 

기본 갤러리 불러오기

Intent.createChooser(it, "Get Album")를 사용해 갤러리를 선택할 수 있게 해줬다.

카메라와 똑같이 행동하기 때문에 onActivityResult로 또 넘어온다.

 

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == REQUEST_ACTION_PICK && resultCode == RESULT_OK) {
            // 갤러리에서 가져온 사진이 여기로 넘어옴
            data!!.data?.also { uri ->
                // 갤러리에서 가져온 사진 저장을 위해 새 파일 생성
                createImageFile()
                val filePath = PathUtil.getPath(this, uri) //갤러리에서 가져온 이미지 path
                val newFile = File(currentPhotoPath) //새로 만든 어플 내 파일
                val outputStream = FileOutputStream(newFile)
                outputStream.write(File(filePath).readBytes()) // 어플 내 파일에 갤러리에서 가져온 이미지를 write
                outputStream.close()
            }
        }
    }

 

카메라와 갤러리 모두 사진을 가져와 같은 폴더에 저장해야하기 때문에 고생했다.

uri는 쉽게 가져올 수 있지만 파일이 가져와지지 않는다.

그래서 새 파일을 생성 후 uri에서 가져온 사진의 path를 가져와야 하는데 그냥 쓰면 오류가 난다.

이걸 우리가 아는 경로로 바꿔줘야한다.

그리고 FileOutputStream을 통해 갤러리에서 가져온 uri의 path로 만든 File을 내가 만든 이름의 파일에 write해준다.

아래는 uri의 실제 경로를 가져오는 클래스이다.

 

PathUtil.java

 

public class PathUtil {
    /*
     * Gets the file path of the given Uri.
     */
    @SuppressLint("NewApi")
    public static String getPath(Context context, Uri uri) throws URISyntaxException {
        final boolean needToCheckUri = Build.VERSION.SDK_INT >= 19;
        String selection = null;
        String[] selectionArgs = null;
        // Uri is different in versions after KITKAT (Android 4.4), we need to
        // deal with different Uris.
        if (needToCheckUri && DocumentsContract.isDocumentUri(context.getApplicationContext(), uri)) {
            if (isExternalStorageDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                return Environment.getExternalStorageDirectory() + "/" + split[1];
            } else if (isDownloadsDocument(uri)) {
                final String id = DocumentsContract.getDocumentId(uri);
                uri = ContentUris.withAppendedId(
                        Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
            } else if (isMediaDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];
                if ("image".equals(type)) {
                    uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                } else if ("video".equals(type)) {
                    uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                } else if ("audio".equals(type)) {
                    uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                }
                selection = "_id=?";
                selectionArgs = new String[]{ split[1] };
            }
        }
        if ("content".equalsIgnoreCase(uri.getScheme())) {
            String[] projection = { MediaStore.Images.Media.DATA };
            Cursor cursor = null;
            try {
                cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
                int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
                if (cursor.moveToFirst()) {
                    return cursor.getString(column_index);
                }
            } catch (Exception e) {
            }
        } else if ("file".equalsIgnoreCase(uri.getScheme())) {
            return uri.getPath();
        }
        return null;
    }


    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is ExternalStorageProvider.
     */
    public static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is DownloadsProvider.
     */
    public static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is MediaProvider.
     */
    public static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri.getAuthority());
    }
}

출처: https://stackoverflow.com/questions/13209494/how-to-get-the-full-file-path-from-uri

 

열심히 검색해서 찾았다. 이거 때문에 하루정도 투자했다... 왜 안되는건지 몰라서 ㅠ_ㅠ

 

이렇게 하면

여기 폴더에 파일이 들어온다.

 

머나먼 여정이였다....

 

이제 디비에서 이미지와 텍스트를 연결해서 메인에서 보여주자!!