본문 바로가기

안드로이드/코틀린

[AOS] Custom QR Reader 만들기

반응형

일전에 qr reader에대해 가볍게 포스팅을 작성한 적이 있는데 테스트 목적으로 작성되다보니 부족한 부분도 많고 업데이트된 내용들이 있어서 새로 포스팅을 작성합니다.

 

zxing에서 제공하는 라이브러리를 사용해 구현하는데 기본적인 화면은 아래 사진과 같습니다. 

github 에 가시면 샘플 프로젝트를 다운받아서 실행시켜볼 수 있는데, 구현 가능한 기능들이 정리되어 있어 한번 다운 받아서 실행시켜 보시고 코드 분석 후 필요한 기능을 구현하시면 좋을것 같습니다. 

 

해당 포스팅에 담길 내용 네이버 렌즈와 같은 QR Reader 앱을 구현하는데 필요한 사항들입니다.

- 투명한 Status bar 

- 연속 QR 인식 

- QR 화면 Custom 

- 실행중 카메라 전환 

 

기본 QR 구현

AndroidManifest.xml

TextureView를 사용하기 때문에 추가 합니다.

    <application android:hardwareAccelerated="true" ... >

build.gradle( project level)

    repositories {
        mavenCentral()
    }

build.gradle( app level ) 

    implementation 'com.journeyapps:zxing-android-embedded:4.3.0'

Qr 엑티비티 추가

AndroidManifest.xml

기본적으로 제공하는 qr Reader를 AndroidManifest.xml에 추가해야 합니다. 

        <activity
            android:name="com.journeyapps.barcodescanner.CaptureActivity"
            android:screenOrientation="fullSensor"
            tools:replace="screenOrientation" />

qr 인식 시작 및 결과 확인

기존에는 activityForResult를 사용했지만 deprecate됐으며 자세한 사항은 구글링 해보시길 바랍니다.

그리고 카메라 권한이 필요하기 때문에 바코드 실행시 카메라 권한을 확인하고 요청하는 로직을 추가해야 합니다. 권한이 없을경우 에러가 발생하지 않지만 까만 화면이 나오기 때문에 사용자가 불편함을 느끼지 않고 자연스럽게 사용할 수 있도록 권한을 받아야 합니다.

    private val barcodeLauncher = registerForActivityResult(ScanContract()){ result ->

        if( result.contents == null){
            // 스캔 취소시
            L.d("cancelled")
        }else{
            // 스캔 성공시
            val info = result.contents
            L.d("info=${info}")

        }
    }
    
    ....
    
    val options = ScanOptions()
   barcodeLauncher.launch(options)

Custom QR Reader

activity_qr.xml

카메라에 비치는 모습을 표현할 영역을 설정합니다. 

<androidx.constraintlayout.widget.ConstraintLayout">

        <com.journeyapps.barcodescanner.DecoratedBarcodeView
            android:id="@+id/viewBarcode"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:zxing_scanner_layout="@layout/view_qrcode"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

실제 바코드를 인식하는 영역과 그 외 영역에 대한 설정인 위 코드상 'view_qrcode' 에서 설정합니다. 

 

view_qrcode.xml

ViewfinderView는 카메라 미리보기 위에 오버레이되어 바코드 인식하는 부분을 구분하기 쉽게 표현해주는 View입니다. 

설정되어 있는 result_point, mask, laser, 3가지 모두 색상 변경이 가능합니다 

BarcodeView는 qrcode를 실질적으로 읽어들이는 뷰로 인식 영역에 대한 넓이와 높이 조절이 가능합니다. ( rect_height, rect_width )

그리고 해당 view를 사용할땐 view.resum(), 사용이 끝난 후엔 view.pause()를 해야합니다. 

<?xml version="1.0" encoding="utf-8"?>
<merge 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">

    <com.journeyapps.barcodescanner.BarcodeView
        android:id="@+id/zxing_barcode_surface"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:zxing_framing_rect_height="100dp"
        app:zxing_framing_rect_width="250dp" />

    <com.journeyapps.barcodescanner.ViewfinderView
        android:id="@+id/zxing_viewfinder_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:zxing_possible_result_points="@color/zxing_custom_possible_result_points"
        app:zxing_result_view="@color/white"
        app:zxing_viewfinder_laser="@color/zxing_custom_viewfinder_laser"
        app:zxing_viewfinder_mask="#00000000" />

</merge>

저는 바코드 인식하는 영역 또한 커스텀하게 사용할 예정이기 때문에 mask에는 투명값 ( #00000000 ) 을 줘서 영역 구분을 없앴지만

laser의 경우 투명값을 설정하면 검정색으로 나오기 때문에 코드로 없애줘야 합니다. 

    private fun disableLager(){
        val viewFinder = binding.viewBarcode.viewFinder
        try{
            val scannerField = viewFinder.javaClass.getDeclaredField("SCANNER_ALPHA")
            scannerField.isAccessible = true
            scannerField.set(viewFinder, intArrayOf(1))

        }catch (e: Exception){

        }

    }

QRActivity.kt

카메라에 바코드가 인식될때마다  'decodeContinuous'가 호출되는 방식입니다. 인식후 일정 시간 텀을 두시려면 인식 후 binding.viewBarcode ( com.journeyapps.barcodescanner.DecoratedBarcodeView )의 onPause를 호출하시고 텀 시간이 흐른 후 resume을 호출해주시면 됩니다. 

    private fun initView(){
        disableLager()

        binding.viewBarcode.run {

            barcodeView.decoderFactory = DefaultDecoderFactory(mutableListOf(BarcodeFormat.QR_CODE, BarcodeFormat.CODE_39))
            initializeFromIntent(intent)
            decodeContinuous {
                // 인식됐을때
                L.d("message=${it.text}")
            }
        }
    }
    
    override fun onResume() {
        super.onResume()

        binding.viewBarcode.resume()
    }

    override fun onPause() {
        super.onPause()

        binding.viewBarcode.pause()
    }

그리고 엑티비티 생명주기 onResume, onPause에 맞게 호출해야 합니다.

 

카메라 변경

기본 Back Camera가 실행되는데, 실행도중 Front Camera로 변경하는 코드입니다. 

해당 기능을 구현한 코드를 찾기 어려워 구현하는데 좀 걸렸습니다. 결과적으로 카메라가 구동중일때 상태값을 변경하고 적용하려면 pause, resume 사이클을 거쳐야하는것 같습니다.

   
    private fun changeCamera(){
        binding.viewBarcode.run {

            pause()

            when(barcodeView.cameraSettings.requestedCameraId){
                1 -> barcodeView.cameraSettings.requestedCameraId = 0
                else -> barcodeView.cameraSettings.requestedCameraId = 1
            }

            resume()
        }
    }

 

투명 statusBar

코틀린의 확장 함수를 사용해서 구현했습니다.

fun Activity.setStatusBarTransparent() {
    window.apply {
        setFlags(
            WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
            WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
        )
    }

    if(Build.VERSION.SDK_INT >= 30) {	// API 30 에 적용
        WindowCompat.setDecorFitsSystemWindows(window, false)
    }
}

위 코드 사용시

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

        setStatusBarTransparent()

    }

 

제가 구현한 코드는 여기까지 입니다. 이전에는 테스트겸 간단하게 진행하다보니 습득한 정보가 부족했는데 실제 구현 단계에서 여러가지 고민하다보니 많은 것들을 얻을 수 있었습니다. 

구현하시다가 궁금한 내용이나 잘못된 내용이 있으면 댓글 남겨주세요.

 

감사합니다.

 

반응형