Today's

bad things at times, do happen to good people.

모바일 앱(안드로이드)

안드로이드 앱 만들기 : qrscan , barcode 스캐너

Billcorea 2022. 1. 10. 09:30
반응형

예전에도 살펴본 적이 있기는 하지만, 

 

https://github.com/zxing/zxing/wiki/Getting-Started-Developing

 

GitHub - zxing/zxing: ZXing ("Zebra Crossing") barcode scanning library for Java, Android

ZXing ("Zebra Crossing") barcode scanning library for Java, Android - GitHub - zxing/zxing: ZXing ("Zebra Crossing") barcode scanning library for Java, Android

github.com

 

바코드 스캐너 기능을 구현하는 라이브러리는 감사할 따름이다. 이런 걸 모르면 어떻게 일일이 코딩을 다 할 수 도 없고, 아무튼지, 좋은 일이다. 나도 언제쯤에 이런 라이브러리 하나 만들어서 공유을 해 볼 수 있을런지, 

 

오늘은 이 걸 이용해서 간단한 앱을 하나 만들어 보겠다.  한가지는 샘플 소스에서 custom 하게 구성해 보는 것을 따라해 볼 것이다. 

 

먼저 gradle 파일에 추가를 해 보자.

dependencies {

    .....

    implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
    implementation 'com.google.zxing:core:3.4.1'

    .....
}

다음은  layout 을 구성해볼 차례인데, 내가 만드는 layout 은 다음 처럼 구성했다.

 전에 다른 걸 할 때는 화면 전체를 바코드 읽기 위한 화면으로 활용을 했다면,  custom 하게 수정을 하면 이렇게 화면의 일부만 바코ㄷ 스캔을 하는 화면으로 구성해 볼 수 있다는 점이 달라진다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

이제 layout 을 보면 다음과 같이 구현 된다.

 

<?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=".ScanBarcodeActivity">

    <com.journeyapps.barcodescanner.DecoratedBarcodeView
        android:id="@+id/zxing_barcode_scanner"
        android:layout_width="371dp"
        android:layout_height="362dp"
        android:layout_alignParentStart="true"
        android:layout_alignParentEnd="true"
        android:layout_marginStart="16dp"
        android:layout_marginTop="12dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btnFlash"
        app:zxing_scanner_layout="@layout/zxing_barcode_scanner">

    </com.journeyapps.barcodescanner.DecoratedBarcodeView>

    <ImageButton
        android:id="@+id/btnFlash"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="336dp"
        android:layout_marginTop="80dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/ic_baseline_flash_on_24" />

    <ImageButton
        android:id="@+id/btnHome"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="276dp"
        android:layout_marginTop="52dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/zxing_barcode_scanner"
        app:srcCompat="@drawable/ic_baseline_home_24" />

    <ImageButton
        android:id="@+id/btnLogout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="52dp"
        app:layout_constraintStart_toEndOf="@+id/btnHome"
        app:layout_constraintTop_toBottomOf="@+id/zxing_barcode_scanner"
        app:srcCompat="@drawable/ic_baseline_logout_24" />

</androidx.constraintlayout.widget.ConstraintLayout>

특별하게 봐야할 것은 zxing_scanner_layout 을 따로 지정하고 그것을 만들어 주어야 하는 부분이다. 아래와 같이 만들어 주면 되고, zxing_framing_rect_width, zxing_framing_rect_height 을 이용하여 화면 가운데 나오는 스캔 view 의 크기를 조정해 주면 사용자가 조금은 쉽게 바코드 인식을 할 수 있게 만들 수 있다.

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
       xmlns:tools="http://schemas.android.com/tools"
       xmlns:app="http://schemas.android.com/apk/res-auto">

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

    <com.journeyapps.barcodescanner.ViewfinderView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/zxing_viewfinder_view"
        app:zxing_possible_result_points="@color/zxing_custom_possible_result_points"
        app:zxing_result_view="@color/zxing_custom_result_view"
        app:zxing_viewfinder_laser="@color/zxing_custom_viewfinder_laser"
        app:zxing_viewfinder_laser_visibility="true"
        app:zxing_viewfinder_mask="@color/zxing_custom_viewfinder_mask"/>

    <TextView
        android:id="@+id/zxing_status_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|center_horizontal"
        android:background="@color/zxing_transparent"
        android:text="@string/zxing_msg_default_status"
        android:textColor="@color/zxing_status_text"/>

</merge>

이제 실행을 위해서 activity을 구현해 보면

Mainactivity 는 이렇게 만들면 된다. 

 

import androidx.activity.result.ActivityResultLauncher;
import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

import com.billcoreatech.databinding.ActivityMainBinding;
import com.google.zxing.client.android.Intents;
import com.journeyapps.barcodescanner.ScanContract;
import com.journeyapps.barcodescanner.ScanOptions;

public class MainActivity extends AppCompatActivity {

    ActivityMainBinding binding ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());


        binding.btnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                ScanOptions options = new ScanOptions().setOrientationLocked(false).setCaptureActivity(ScanBarcodeActivity.class);
                options.setBarcodeImageEnabled(true);
                options.setBeepEnabled(true);
                options.setPrompt(getString(R.string.msgScanTitle));
                barcodeLauncher.launch(options);

            }
        });
    }

    private final ActivityResultLauncher<ScanOptions> barcodeLauncher = registerForActivityResult(new ScanContract(),
            result -> {
                if(result.getContents() == null) {
                    Intent originalIntent = result.getOriginalIntent();
                    if (originalIntent == null) {
                        Log.d("MainActivity", "Cancelled scan");
                        Toast.makeText(MainActivity.this, "Cancelled", Toast.LENGTH_LONG).show();
                    } else if(originalIntent.hasExtra(Intents.Scan.MISSING_CAMERA_PERMISSION)) {
                        Log.d("MainActivity", "Cancelled scan due to missing camera permission");
                        Toast.makeText(MainActivity.this, "Cancelled due to missing camera permission", Toast.LENGTH_LONG).show();
                    }
                } else {
                    Log.d("MainActivity", "Scanned");
                    Toast.makeText(MainActivity.this, "Scanned: " + result.getContents(), Toast.LENGTH_LONG).show();
                }
            });
}

ActivityResultLauncher 는 샘플 소스에서 그대로 복사해온 것이고, 이것을 부분을 이용하면 barcode 스캔 결과를 이용해서 다른 작업을 할 수 있다.  barcodeLauncher 에서 호출하는 ScanBarcodeActivity 소스는 다음과 같이 하면 되겠다. 

 

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.billcoreatech.databinding.ActivityScanBarcodeBinding;
import com.journeyapps.barcodescanner.CaptureManager;
import com.journeyapps.barcodescanner.DecoratedBarcodeView;
import com.journeyapps.barcodescanner.ViewfinderView;

import java.util.Random;

public class ScanBarcodeActivity extends AppCompatActivity implements DecoratedBarcodeView.TorchListener{

    ActivityScanBarcodeBinding binding ;
    private CaptureManager capture;
    SharedPreferences sp ;
    SharedPreferences.Editor flashStatus ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityScanBarcodeBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        sp = getSharedPreferences(getPackageName(), MODE_PRIVATE);

        binding.zxingBarcodeScanner.setTorchListener(this);
        if (!hasFlash()) {
            binding.btnFlash.setVisibility(View.GONE);
        }
        capture = new CaptureManager(this, binding.zxingBarcodeScanner);
        capture.initializeFromIntent(getIntent(), savedInstanceState);
        capture.setShowMissingCameraPermissionDialog(true);
        capture.decode();

        changeMaskColor(null);
        changeLaserVisibility(true);

        binding.btnHome.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                finish();
            }
        });
        binding.btnLogout.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                finish();
            }
        });
        binding.btnFlash.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                switchFlashlight(view);
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        capture.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        capture.onPause();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        capture.onDestroy();
    }

    @Override
    public void onBackPressed() {
        // super.onBackPressed();
        Toast.makeText(getApplicationContext(), getString(R.string.msgLogoutPress), Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        capture.onSaveInstanceState(outState);
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        return binding.zxingBarcodeScanner.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
    }

    /**
     * Check if the device's camera has a Flashlight.
     * @return true if there is Flashlight, otherwise false.
     */
    private boolean hasFlash() {
        return getApplicationContext().getPackageManager()
                .hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
    }

    public void switchFlashlight(View view) {
        if (sp.getBoolean("flashOn", true)) {
            binding.zxingBarcodeScanner.setTorchOn();
        } else {
            binding.zxingBarcodeScanner.setTorchOff();
        }
    }

    public void changeMaskColor(View view) {
        Random rnd = new Random();
        int color = Color.argb(100, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));
        binding.zxingBarcodeScanner.getViewFinder().setMaskColor(color);
    }

    public void changeLaserVisibility(boolean visible) {
        binding.zxingBarcodeScanner.getViewFinder().setLaserVisibility(visible);
    }

    @Override
    public void onTorchOn() {
        flashStatus = sp.edit();
        flashStatus.putBoolean("flashOn", false);
        flashStatus.commit();
        binding.btnFlash.setBackground(getDrawable(R.drawable.ic_baseline_flash_off_24));
    }

    @Override
    public void onTorchOff() {
        flashStatus = sp.edit();
        flashStatus.putBoolean("flashOn", true);
        flashStatus.commit();
        binding.btnFlash.setBackground(getDrawable(R.drawable.ic_baseline_flash_on_24));
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        capture.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}

화면 예시에서 보았던 flash 버튼의 역활도 구현을 통해서 어두운 곳에서는 flash 을 켜거나, 끄거나 하는 기능도 기능해 볼 수 있다.  zxingBarcodeScanner.setTorchOn / setTorchOff 을 통해서 구현이 가능 하다.

 

 

반응형