GDSC SungShin Women's University 23-24/Story

[Winter Blog Challenge] CameraX를 통한 사진 프리뷰 구현하기 (Member 강신영)

GDSC SungShin Team 2024. 2. 23. 13:51

안녕하세요! GDSC 5기 멤버 강신영입니다!

안드로이드 앱을 구현하다 보면 카메라 기능이 필요할 때가 있습니다.

물론 핸드폰에 되어있는 카메라 앱을 연결하여 넘어가는 방법도 있습니다.

하지만 실행되는 앱 내부에서 진행되어야 할 경우도 있는데요 이럴 때 CameraX를 통해 구현 할 수 있습니다.

먼저 CameraX는 Jetpack 라이브러리로 Android 5.0(API 21)이상에서 지원합니다.

이제 CameraX를 사용하기 위해 기본 설정을 해주겠습니다.

  1. build.gradle에 들어가 종속 항목을 추가해줍니다.
dependencies {
  def camerax_version = "1.1.0-beta01"
  implementation "androidx.camera:camera-core:${camerax_version}"
  implementation "androidx.camera:camera-camera2:${camerax_version}"
  implementation "androidx.camera:camera-lifecycle:${camerax_version}"
  implementation "androidx.camera:camera-video:${camerax_version}"

  implementation "androidx.camera:camera-view:${camerax_version}"
  implementation "androidx.camera:camera-extensions:${camerax_version}"
}

 

2. 컴파일 옵션을 설정해주기 위해 buildTypes 뒤에 해당 코드를 추가합니다.

compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}

3. Manifest의 application 태그 앞에 필요한 권한을 구현하기 위해 해당 코드를 추가합니다.

<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
   android:maxSdkVersion="28" />

프리뷰를 보기 위한 레이아웃을 만들어보도록 하겠습니다.

<?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">

    <androidx.camera.view.PreviewView
        android:id="@+id/viewFinder"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <Button
        android:id="@+id/image_capture_button"
        android:layout_width="150dp"
        android:layout_height="wrap_content"
        android:layout_marginBottom="50dp"
        android:elevation="2dp"
        android:text="take_photo"
        android:textColor="@color/black"
        android:backgroundTint="@color/white"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

그리고 코틀린 파일에 코드를 작성해줍니다.

package com.example.camerax_test

import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageCapture
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import com.example.camerax_test.databinding.ActivityMainBinding


typealias LumaListener = (luma: Double) -> Unit

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    private var imageCapture: ImageCapture? = null

    private lateinit var cameraExecutor: ExecutorService
    override fun onCreate(savedInstanceState: Bundle?) {
        binding = ActivityMainBinding.inflate(layoutInflater)
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        // Request camera permissions
        if (allPermissionsGranted()) {
            startCamera()
        } else {
            ActivityCompat.requestPermissions(
                this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
        }

        // Set up the listeners for take photo and video capture buttons
        binding.imageCaptureButton.setOnClickListener{takePhoto()}

				cameraExecutor = Executors.newSingleThreadExecutor()
    }

    private fun startCamera() {
		   val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

		   cameraProviderFuture.addListener({
		       val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

       // Preview
       val preview = Preview.Builder()
          .build()
          .also {
              it.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider)
          }

       val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

       try {
           cameraProvider.unbindAll()

           cameraProvider.bindToLifecycle(
               this, cameraSelector, preview)

       } catch(exc: Exception) {
           Log.e(TAG, "Use case binding failed", exc)
       }

   }, ContextCompat.getMainExecutor(this))
}

    private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all{
			ContextCompat.checkSelfPermission(
			baseContext,it) == PackageManager.PERMISSION_GRANTED
		}

		override fun onDestroy() {
        super.onDestroy()
        cameraExecutor.shutdown()
		}

    companion object {
        private const val TAG = "CameraXApp"
        private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
        private const val REQUEST_CODE_PERMISSIONS = 10
        private val REQUIRED_PERMISSIONS =
				mutableListOf(
                Manifest.permission.CAMERA,
                Manifest.permission.RECORD_AUDIO
				).apply{
										if (Build.VERSION.SDK_INT<= Build.VERSION_CODES.P) {
                    add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
		                }
								}.toTypedArray()
		}
		override fun onRequestPermissionsResult(
	   requestCode: Int, permissions: Array<String>, grantResults:
	   IntArray) {
		   if (requestCode == REQUEST_CODE_PERMISSIONS) { //요청코드가 올바른지 확인한다.
		       if (allPermissionsGranted()) {
           startCamera()  //권한이 부여된다면 startCamera함수를 호출
		       } else {
           Toast.makeText(this,
               "Permissions not granted by the user.",
               Toast.LENGTH_SHORT).show()
           finish()
		       }
		   }
		}
}

그러면 화면에서 카메라 프리뷰를 볼 수 있습니다!

 

해당 프리뷰에 더해서 프리뷰에 담긴 모습으로 사진 촬영 코드를 추가 할 수 있고 동영상 촬영도 가능합니다!

이 기능은 Android Developers에 더 자세히 나와있어 참고해보면 좋을 것 같습니다!