본문 바로가기
안드로이드 코틀린

[Kotlin][Android] QR 코드 리더기

by teamnova 2021. 7. 1.

안녕하세요.

 

ZXing 라이브러리를 이용하여 QR 코드 리더기 앱을 만들어 보도록 하겠습니다.

 

개발 순서는 다음과 같습니다.

 

1. 라이브러리 추가 및 권한 설정

2. 화면 만들기(XML)

3. 코드 작성하기(QrCodeActivity.kt)

 

1. 라이브러리 추가 및 권한 설정

1-1) ZXing 라이브러리

github.com/journeyapps/zxing-android-embedded

 

journeyapps/zxing-android-embedded

Barcode scanner library for Android, based on the ZXing decoder - journeyapps/zxing-android-embedded

github.com

Build, Gradle에 Zxing 라이브러리 추가하기

 

SDK 버전에 따라 라이브러리를 다르게 설정해줘야 합니다.

 

- SDK Version 24 미만 일 때 사용 가능

implementation('com.journeyapps:zxing-android-embedded:4.1.0') { transitive = false }
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'com.google.zxing:core:3.3.0'

- SDK Version 24 이상 일 때 사용 가능

implementation 'com.journeyapps:zxing-android-embedded:4.1.0'
implementation 'androidx.appcompat:appcompat:1.0.2'

 

그리고 추가로 자바의 호환성 지정을 해줘야 합니다. 안 그러면 다음과 같은 에러가 발생하게 됩니다.

java.lang.NoSuchMethodError: No static method metafactory  
(declaration of 'java.lang.invoke.LambdaMetafactory' appears in /apex/com.android.runtime/javalib/core-oj.jar

 

그래서 다음과 같이 app:gradle.build에 다음과 같이 작성해줍시다.

compileOptions {
  	sourceCompatibility JavaVersion.VERSION_1_8
 	targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
	jvmTarget = '1.8'
}

 

easypermissions 라이브러리 추가

안드로이드의 권한 정책으로 "위엄한 권한" 경우 반드시 사용자로 부터 권한을 사용하겠다는 것을 허락을 받아야 합니다. 이를 런타임 퍼미션이라고 불리며 이를 쉽게 구현할 수 있도록 돕는 라이브러리입니다.

github.com/googlesamples/easypermissions

 

googlesamples/easypermissions

Simplify Android M system permissions. Contribute to googlesamples/easypermissions development by creating an account on GitHub.

github.com

dependencies {
    implementation 'pub.devrel:easypermissions:3.0.0'
}

 

cardview 라이브러리 추가

이번 예제에서는 cardview를 이용했는데요. 이를 사용하실려면 아래 라이브러리를 추가해주셔야 합니다.

developer.android.com/jetpack/androidx/releases/cardview

 

Cardview  |  Android 개발자  |  Android Developers

Cardview 둥근 모서리와 그림자로 머티리얼 디자인 카드 패턴을 구현합니다. 최근 업데이트 현재 안정화 버전 다음 버전 후보 베타 버전 알파 버전 2018년 9월 21일 1.0.0 - - - 종속 항목 선언 CardView의

developer.android.com

dependencies {
    implementation "androidx.cardview:cardview:1.0.0"
}

 

1-2) 매니패스트(manifext.xml)

qr코드를 사용하기 위해서 카메라 권한을 설정해줍시다.

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

 

2. 화면 만들기(XML)

여기서 사용한 resource는 입맛대로 사용하시면 되나 혹시나 찾는데 번거로움이 있을까 사용한 이미지는 아래 첨부파일로 올려놓겠습니다.

<?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"
android:background="@color/colorWhite"
tools:context=".QrCodeActivity">

<TextView
    android:id="@+id/tvText"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="50dp"
    android:text=""
    android:textColor="@color/colorBlack"
    android:textSize="20sp"
    android:textStyle="bold"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

<androidx.cardview.widget.CardView
    android:id="@+id/cardView1"
    android:layout_width="300dp"
    android:layout_height="wrap_content"
    android:layout_marginTop="50dp"
    android:visibility="gone"
    app:cardBackgroundColor="@color/colorWhite"
    app:cardCornerRadius="10dp"
    app:cardElevation="10dp"
    app:layout_constraintBottom_toTopOf="@+id/linearLayout"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toBottomOf="@id/tvText">


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="10dp"
        android:orientation="horizontal">

        <EditText
            android:id="@+id/edtCode"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:background="@color/colorWhite"
            android:padding="10dp"
            android:layout_weight="1"
            android:hint="Enter Code here"
            android:textColor="@color/colorBlack"
            android:textColorHint="@color/colorBlack"
            android:visibility="visible" />



        <Button
            android:id="@+id/btnEnter"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:layout_weight="0.5"
            android:background="@color/colorPurple"
            android:text="Enter"
            android:textColor="@color/colorWhite" />
    </LinearLayout>


</androidx.cardview.widget.CardView>

<androidx.cardview.widget.CardView
    android:id="@+id/cardView2"
    android:layout_width="300dp"
    android:layout_height="300dp"
    android:visibility="gone"
    app:cardBackgroundColor="@color/colorWhite"
    app:cardCornerRadius="10dp"
    app:cardElevation="10dp"
    app:layout_constraintBottom_toTopOf="@+id/linearLayout"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/tvText">


    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="30dp"
        android:src="@drawable/ic_qr_code" />


</androidx.cardview.widget.CardView>


<LinearLayout
    android:id="@+id/linearLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="50dp"
    android:orientation="horizontal"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent">

    <Button
        android:id="@+id/btnScan"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:layout_weight="1"
        android:background="@color/colorPurple"
        android:text="Scan Code"
        android:textColor="@color/colorWhite" />

    <Button
        android:id="@+id/btnEnterCode"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:layout_weight="1"
        android:background="@color/colorOrange"
        android:text="Enter Code"
        android:textColor="@color/colorWhite" />

</LinearLayout>


</androidx.constraintlayout.widget.ConstraintLayout>

 

화면 예시
qr-code.svg
0.00MB

3. 코드 작성하기(QrCodeActivity.kt)

3-1. 권한 설정하기

// 3-1. 권한 설정하기
class QrCodeActivity : AppCompatActivity(), EasyPermissions.PermissionCallbacks,
    EasyPermissions.RationaleCallbacks {
    // 3-2. View Inflate 및 클릭 이벤트 리스너 설정
    // var cardView1: CardView? = null
    // ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_qr_code)
    
    // 3-3. ZXing 설정 및 받은 데이터 처리
    // private fun cameraTask() {
    // ...

    //EasyPermissions 콜백메서드
    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this)
    }

    override fun onPermissionsDenied(requestCode: Int, perms: MutableList<String>) {
        if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
            AppSettingsDialog.Builder(this).build().show()
        }
    }

    override fun onPermissionsGranted(requestCode: Int, perms: MutableList<String>) {
    }

    override fun onRationaleDenied(requestCode: Int) {
    }

    override fun onRationaleAccepted(requestCode: Int) {
    }
}

 

3-2. View Inflate 및 클릭 이벤트 리스너 설정

// 3-1. 권한 설정하기
class QrCodeActivity : AppCompatActivity(), EasyPermissions.PermissionCallbacks,
    EasyPermissions.RationaleCallbacks {
    // 3-2. View Inflate 및 클릭 이벤트 리스너 설정
    var cardView1: CardView? = null
    var cardView2: CardView? = null
    var btnScan: Button? = null
    var btnEnterCode: Button? = null
    var btnEnter: Button? = null
    var edtCode: EditText? = null
    var tvText: TextView? = null
    
       override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_qr_code)

        // View Infalte 작업
        cardView1 = findViewById(R.id.cardView1)
        cardView2 = findViewById(R.id.cardView2)
        btnScan = findViewById(R.id.btnScan)
        btnEnterCode = findViewById(R.id.btnEnterCode)
        btnEnter = findViewById(R.id.btnEnter)
        edtCode = findViewById(R.id.edtCode)
        tvText = findViewById(R.id.tvText)

        tvText!!.setText("Scan QR Code")
        cardView2!!.visibility = View.VISIBLE

        btnScan!!.setOnClickListener {
            cardView2!!.visibility = View.VISIBLE
            cardView1!!.visibility = View.GONE
            tvText!!.setText("Scan QR Code Here")
        }

        cardView2!!.setOnClickListener {
            cameraTask()
        }

        btnEnter!!.setOnClickListener {

            if (edtCode!!.text.toString().isNullOrEmpty()) {
                Toast.makeText(this, "Please enter code", Toast.LENGTH_SHORT).show()
            } else {
                var value = edtCode!!.text.toString()

                Toast.makeText(this, value, Toast.LENGTH_SHORT).show()

            }
        }

        btnEnterCode!!.setOnClickListener {
            cardView2!!.visibility = View.GONE
            cardView1!!.visibility = View.VISIBLE
            tvText!!.setText("Enter QR Code")
        }
    }
    
    // 3-3. ZXing 설정 및 받은 데이터 처리
    // private fun cameraTask() {
    // ...

    //EasyPermissions 콜백메서드
    // override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
    // ...
}

 

3-3. ZXing 설정 및 받은 데이터 처리

// 3-1. 권한 설정하기
class QrCodeActivity : AppCompatActivity(), EasyPermissions.PermissionCallbacks,
    EasyPermissions.RationaleCallbacks {
    // 3-2. View Inflate 및 클릭 이벤트 리스너 설정
    // var cardView1: CardView? = null
    // ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_qr_code)
    
   // 3-3. ZXing 설정 및 받은 데이터 처리
    private fun cameraTask() {
        if (hasCameraAccess()) {
            var qrScanner = IntentIntegrator(this)
            qrScanner.setPrompt("QR코드를 인증해주세요.") // 원하는 문구 기입
            qrScanner.setCameraId(0)
            qrScanner.setOrientationLocked(false) // 세로,가로 모드를 고정 시켜주는 역할
            qrScanner.setBeepEnabled(true) // QR코드 스캔시 소리 나게 하려면 true 아니면 false로 지정
            qrScanner.captureActivity = CaptureActivity::class.java
            qrScanner.initiateScan() //QR코드 스캔의 결과 값은 onActivityResult 함수로 전달
        } else {
            EasyPermissions.requestPermissions(
                this,
                "QR 코드 기능을 사용하기 위해서는 카메라 권한 설정을 허용해 주셔야 합니다.",
                123,
                android.Manifest.permission.CAMERA
            )
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        var result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
        if (result != null) {
            if (result.contents == null) {
                Toast.makeText(this, "결과를 찾을 수 없습니다", Toast.LENGTH_SHORT).show()
                edtCode!!.setText("")
            } else {
                try {
//                    cardView1!!.startAnimation(reveal)
//                    cardView2!!.startAnimation(hide)
                    cardView1!!.visibility = View.VISIBLE
                    cardView2!!.visibility = View.GONE
                    edtCode!!.setText(result.contents.toString())
                } catch (exception: JSONException) {
                    Toast.makeText(this, exception.localizedMessage, Toast.LENGTH_SHORT).show()
                    edtCode!!.setText("")
                }
            }
        } else {
            super.onActivityResult(requestCode, resultCode, data)
        }
        if (requestCode == AppSettingsDialog.DEFAULT_SETTINGS_REQ_CODE) {
            Toast.makeText(this, "Permission Granted", Toast.LENGTH_SHORT).show()
        }
    }

    //EasyPermissions 콜백메서드
    // override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
    // ...
}

 

저는 ZXing 설정 부분과 카메라로 인식 값을 받아오는 부분을 스틱코드로 올려놔서 필요할 때마다 쉽게 가져다 사용하고 있습니다. 필요하신 분들은 아래 링크를 참고해주세요.

stickode.com/detail.html?no=2188

 

스틱코드

 

stickode.com

결과 화면입니다.