본문 바로가기
안드로이드

[안드로이드/Kotlin] 카메라 사용하고 이미지 뷰에 띄워보기

by krapoi 2022. 8. 26.
반응형

친구가 안드로이드 책을 선물로 사줬는데 생각보다 재밌어 보이는 게 많아서 따라 하고 있다.

뭔가 복습도 되고 괜찮은느낌...

 

아무튼 이번에는 카메라를 사용하는 방법이다.

 

먼저 Gradle부터 추가하도록 하자.

카메라 촬영만 하는 것이 아니라 이미지를 띄워야 하기 때문에 ActivityResultLauncher를 사용해서 결과를 처리해야 한다.

이 ActivityResultLauncher에 관한 의존성이다.

android {
	//...

	buildFeatures{
    	viewBinding true
    }
}



dependencies {
	//...
    
    implementation "androidx.activity:activity-ktx:1.5.1"
    implementation "androidx.fragment:fragment-ktx:1.5.2"
}

하는 김에 뷰 바인딩도 해주자.

 

그다음에 레이아웃을 만들어 주자.

activity_main.xml을 수정해서 아래 사진과 같이 만들어주자.

아래는 xml코드이다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/cameraBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        android:text="카메라"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <ImageView
        android:id="@+id/imagePreview"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/cameraBtn"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:srcCompat="@tools:sample/avatars" />
</androidx.constraintlayout.widget.ConstraintLayout>

 

이제 Manifest에 접근 권한을 선언해주자.

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

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

그러면 이제 코드를 짜러 가보자.

 

먼저 우리는 Manifest에 추가해놓은 권한들을 처리해 줘야 한다.

이 권한들을 처리하는 런처들을 먼저 선언해 두자.

class MainActivity : AppCompatActivity() {

    val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }

    lateinit var cameraPermission: ActivityResultLauncher<String>
    lateinit var storagePermission: ActivityResultLauncher<String>
    lateinit var cameraLauncher: ActivityResultLauncher<Uri>

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

하는 김에 바인딩까지 해주자.

여기서 cameraPermission과 storagePermission의 제네릭 타입이 String인 이유는 Manifest의 permission이 String 타입이기 때문이고, cameraLauncher는 TakePicture를 사용하기 때문에 Uri로 정해주었다.

뭔 소린지 모르겠다면, 나중에 다 완성했을 때 하나하나 뜯어보자.

 

이제 외부 저장소 권한이 승인되었을 때 호출할 메서드를 하나 만들어주자. 이 메서드 안에는 버튼 클릭식 카메라 권한을 요청하는 코드가 들어간다. 아직 런처를 생성하진 않았지만 괜찮다.

    fun setViews() {
        binding.cameraBtn.setOnClickListener {
            cameraPermission.launch(Manifest.permission.CAMERA)
        }
    }

이런 식으로 우리가 아까 만들어 두었던 Launcher에 launch메서드에 권한을 담아 호출하면 된다.

 

이제 런처를 만들어주자.

onCreate안에 만들어 주면 된다.

storagePermission = registerForActivityResult(ActivityResultContracts.RequestPermission()) {

}

cameraPermission = registerForActivityResult(ActivityResultContracts.RequestPermission()) {

}

 

아래에다가 카메라 런처도 만들어주자. (cameraPermission 말고 cameraLauncher)

cameraLauncher = registerForActivityResult(ActivityResultContracts.TakePicture()) {

}

 

그리고 맨 아래줄에 storagePermission을 lanuch 시켜주자.

storagePermission.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)

그리고 storagePermission의 로직을 작성해 주자.

storagePermission = registerForActivityResult(ActivityResultContracts.RequestPermission()) {
	if (it) {
		setViews()
	} else {
		Toast.makeText(baseContext, "권한을 승인해야 앱을 사용할 수 있습니다.",Toast.LENGTH_SHORT).show()
		finish()
	}
}

여기서 it은 boolean으로 승인했는지 안 했는지의 여부를 따진다.

그리고 cameraPermission을 작성하면 작동할 카메라 메서드를 만들어 주자.

 

메서드를 작성하기 전에 사진을 Uri로 저장해 야하기 때문에 전역 변수로 uri변수를 하나 지정해주자.

    var photoUri:Uri? = null
    
     override fun onCreate(savedInstanceState: Bundle?) {
     //....

이제 메서드를 작성해 주자.

    fun openCamera() {
        val photoFile = File.createTempFile(
            "IMG_",
        ".jpg",
            getExternalFilesDir(Environment.DIRECTORY_PICTURES)
        )

        photoUri = FileProvider.getUriForFile(
            this, "${packageName}.provider", photoFile
        )

        cameraLauncher.launch(photoUri)
    }

그리고 이 메서드에서 카메라 런처를 launch 시켜 준다.

그리고 cameraPermission을 작성해 주자.

        cameraPermission = registerForActivityResult(ActivityResultContracts.RequestPermission()) {
            if (it) {
                openCamera()
            } else {
                Toast.makeText(baseContext, "권한을 승인해야 카메라를 사용할 수 있습니다.",Toast.LENGTH_SHORT).show()
            }
        }

 

이렇게 하면 사진을 띄우기는 하지만 사진의 썸네일을 이미지 뷰에 띄우기 때문에 흐릿하게 나온다.

그렇기 때문에 사진을 저장할 위치를 정하고 bitmap으로 불러와야 한다.

 

안드로이드 res폴더에 xml폴더를 열어 file_paths.xml을 만들어준다.

그다음 코드를 다음과 같이 작성하자.

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
        name="my_images"
        path="Android/data/패키지명(ex : com.google.android)/files/Pictures" />
</paths>

그다음 manifest에 들어가서 provider를 불러와주자.

<application
	...
    
            <provider
            android:authorities="${applicationId}.provider"
            android:name="androidx.core.content.FileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>
        
</application>

meta-data작성 시 resource에는 자신이 만든 file_paths의 이름을 적어주면 된다. (이름을 다르게 설정했다면)

적을 때 자동완성이 안되고 빨간색으로 뜨긴 하는데, 다 적으면 또 오류 없이 작동한다. (왜 그런지 모르겠음...)

아무튼 이렇게까지 한 다음 실행시키면 잘 작동하는 것을 볼 수 있다.

 

전체 코드는 깃헙에서 확인할 수 있다.

https://github.com/solotrining/Camera

 

GitHub - solotrining/Camera: 카메라 사용해보기

카메라 사용해보기. Contribute to solotrining/Camera development by creating an account on GitHub.

github.com

 

반응형