Android:CameraXをつかったバーコードスキャンをON・OFFする方法
はじめに
バーコードスキャンや画像解析、画像・動画キャプチャをする際、JetpackライブラリのCameraXが便利です。
CameraXをつかったバーコードスキャンをON/OFFする方法について説明します。
例えば、バーコードスキャンした後で一度処理を止めたい、といったときに使うことができます。
サンプルコードはバーコードスキャンを行い、設置されているボタン「Stop」を押すとバーコードスキャンを停止、ボタン「Start」を押すとバーコードスキャンを開始します。
やり方
サンプルコードの全体
サンプルコードは下記のようになります。
さらにパーミッションの設定も必要になります。
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var cameraExecutors: ExecutorService
private lateinit var scanner: BarcodeScanner
private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
private lateinit var cameraProvider: ProcessCameraProvider
private lateinit var previewUseCase: Preview
private lateinit var analysisUseCase: ImageAnalysis
private lateinit var cameraSelector: CameraSelector
private val PERMISSION_WRITE_EX_STR = 1
private val PERMISSIONS = arrayOf(
Manifest.permission.CAMERA,
)
@RequiresApi(Build.VERSION_CODES.R)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// パーミッションをリクエスト
permissionRequest()
// スレッドを作成
cameraExecutors = Executors.newSingleThreadExecutor()
bindCameraUseCases()
val stopButton = findViewById<Button>(R.id.stopButton)
val startButton = findViewById<Button>(R.id.startButton)
stopButton.setOnClickListener {
stopPreview()
}
startButton.setOnClickListener {
bindCameraUseCases()
startPreview()
}
}
fun bindCameraUseCases() {
val previewView = findViewById<PreviewView>(R.id.previewView)
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
cameraProvider = cameraProviderFuture.get()
// preview部分にカメラ画像を表示
previewUseCase = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(previewView.surfaceProvider)
}
/*
スキャンできるバーコードの型を設定しておくことが可能
未指定の場合は自動判定が可能
今回はコメントアウト
*/
// BarcodeScannerOptions.Builder().setBarcodeFormats(
// Barcode.FORMAT_QR_CODE,
// Barcode.FORMAT_CODABAR,
// Barcode.FORMAT_CODE_128,
// Barcode.FORMAT_CODE_39,
// Barcode.FORMAT_CODE_93,
// Barcode.FORMAT_EAN_8,
// Barcode.FORMAT_EAN_13,
// ).build()
// CameraSelector.DEFAULT_BACK_CAMERA 背面カメラ
// CameraSelector.DEFAULT_FRONT_CAMERA 前面カメラ
cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
scanner = BarcodeScanning.getClient()
analysisUseCase = ImageAnalysis.Builder().build()
analysisUseCase.setAnalyzer(cameraExecutors)
{ imageProxy -> processImageProxy(scanner, imageProxy) }
startPreview()
}, ContextCompat.getMainExecutor(this))
}
@SuppressLint("UnsafeOptInUsageError")
private fun processImageProxy(barcodeScanner: BarcodeScanner, imageProxy: ImageProxy, ) {
imageProxy.image?.let { image ->
val inputImage =
InputImage.fromMediaImage(
image,
imageProxy.imageInfo.rotationDegrees
)
barcodeScanner.process(inputImage)
.addOnSuccessListener { barcodeList ->
val barcode = barcodeList.getOrNull(0)
barcode?.rawValue?.let { value ->
if(value.isNotEmpty()){
val barcodeView = findViewById<TextView>(R.id.barcodeView)
barcodeView.setText(value)
}
}
}
.addOnFailureListener {
Log.e(TAG, it.message.orEmpty())
}.addOnCompleteListener {
imageProxy.image?.close()
imageProxy.close()
}
}
}
fun startPreview(){
try {
cameraProvider.bindToLifecycle(
this,
cameraSelector,
previewUseCase,
analysisUseCase)
} catch (illegalStateException: IllegalStateException) {
Log.e(TAG, illegalStateException.message.orEmpty())
} catch (illegalArgumentException: IllegalArgumentException) {
Log.e(TAG, illegalArgumentException.message.orEmpty())
}
}
fun stopPreview(){
// previewViewへの投影を停止
cameraProvider.unbind(previewUseCase)
// バーコードスキャンを停止
scanner.close()
}
// パーミッションのリクエスト
private fun permissionRequest(){
if (Build.VERSION.SDK_INT >= 23) {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(this, PERMISSIONS, PERMISSION_WRITE_EX_STR)
}else{
Log.d("Permission is", "OK")
}
}
}
}
activity_main.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">
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.196">
</androidx.camera.view.PreviewView>
<Button
android:id="@+id/stopButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="300dp"
android:layout_marginBottom="25dp"
android:text="Stop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/startButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="300dp"
android:layout_marginBottom="25dp"
android:text="Start"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/barcodeView"
android:layout_width="400dp"
android:layout_height="40dp"
android:layout_marginBottom="25dp"
android:text=""
android:textColor="@color/white"
android:textSize="35dp"
android:gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/previewView"
app:layout_constraintVertical_bias="0.893" />
</androidx.constraintlayout.widget.ConstraintLayout>
ライブラリの追加
CameraXを使うために、app/build.gradleにモジュールを追加します。
dependencies {
// CameraX
def cameraxVersion = "1.2.0-alpha04"
implementation "androidx.camera:camera-core:${cameraxVersion}"
implementation "androidx.camera:camera-camera2:${cameraxVersion}"
implementation "androidx.camera:camera-lifecycle:${cameraxVersion}"
implementation "androidx.camera:camera-video:${cameraxVersion}"
implementation "androidx.camera:camera-view:${cameraxVersion}"
implementation "androidx.camera:camera-extensions:${cameraxVersion}"
// MLKit
// barcode-scanning
implementation 'com.google.mlkit:barcode-scanning:17.0.2'
}
カメラのパーミッションを通す
カメラを使用するのでパーミッションの設定が必要になります。
AndroidManifest.xmlに下記を追加します。
<uses-permission android:name="android.permission.CAMERA" />
そして、MainActivity.ktでは下記の関数を呼び出すことでパーミッションをリクエストしています。
private val PERMISSION_WRITE_EX_STR = 1
private val PERMISSIONS = arrayOf(
Manifest.permission.CAMERA,
)
// パーミッションのリクエスト
private fun permissionRequest(){
if (Build.VERSION.SDK_INT >= 23) {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(this, PERMISSIONS, PERMISSION_WRITE_EX_STR)
}else{
Log.d("Permission is", "OK")
}
}
}
カメラの映像を表示
先程、activity_main.xmlのコードを示しました。
CameraXをつかって、カメラの映像をアプリ上でストリーミングするには PreviewViewをレイアウトに追加します。
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.196">
</androidx.camera.view.PreviewView>
そして、このPreviewViewに映像をひもづけます。
その処理はMainActivity.ktのbindCameraUseCases()関数内で行われています。
fun bindCameraUseCases() {
val previewView = findViewById<PreviewView>(R.id.previewView)
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
cameraProvider = cameraProviderFuture.get()
// preview部分にカメラ画像を表示
previewUseCase = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(previewView.surfaceProvider)
}
/*
スキャンできるバーコードの型を設定しておくことが可能
未指定の場合は自動判定が可能
今回はコメントアウト
*/
// BarcodeScannerOptions.Builder().setBarcodeFormats(
// Barcode.FORMAT_QR_CODE,
// Barcode.FORMAT_CODABAR,
// Barcode.FORMAT_CODE_128,
// Barcode.FORMAT_CODE_39,
// Barcode.FORMAT_CODE_93,
// Barcode.FORMAT_EAN_8,
// Barcode.FORMAT_EAN_13,
// ).build()
// CameraSelector.DEFAULT_BACK_CAMERA 背面カメラ
// CameraSelector.DEFAULT_FRONT_CAMERA 前面カメラ
cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
scanner = BarcodeScanning.getClient()
analysisUseCase = ImageAnalysis.Builder().build()
analysisUseCase.setAnalyzer(cameraExecutors)
{ imageProxy -> processImageProxy(scanner, imageProxy) }
startPreview()
}, ContextCompat.getMainExecutor(this))
}
上記の処理はAndroid Developerの「プレビューを実装する」で詳細説明されています。
かいつまんで説明すると、下記のような手順を踏んでいます。
- CameraProviderをリクエスト
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
- CameraProviderが利用可能かどうかを確認
fun bindCameraUseCases() {
val previewView = findViewById<PreviewView>(R.id.previewView)
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
cameraProvider = cameraProviderFuture.get()
/*
省略
*/
startPreview()
}, ContextCompat.getMainExecutor(this))
- カメラを選択してライフサイクルとユースケースをバインドします。こちらはstartPreview()関数内で処理しています。
fun startPreview(){
try {
cameraProvider.bindToLifecycle(
this,
cameraSelector,
previewUseCase,
analysisUseCase)
} catch (illegalStateException: IllegalStateException) {
Log.e(TAG, illegalStateException.message.orEmpty())
} catch (illegalArgumentException: IllegalArgumentException) {
Log.e(TAG, illegalArgumentException.message.orEmpty())
}
}
バーコードスキャン
バーコードスキャンする場合は、Previewと同じようにAnalyzerをバインドします。
下記で記述したanalysisUseCaseをstartPreview()関数のbindToLifecycle内に渡します。
analysisUseCase = ImageAnalysis.Builder().build()
analysisUseCase.setAnalyzer(cameraExecutors)
{ imageProxy -> processImageProxy(scanner, imageProxy) }
バーコードスキャンのON/OFF
バーコードスキャンのONにするときはstartPreview()を呼び出します。
OFFにするときはstopPreview()を呼び出します。
stopPreview()では、previewViewへのストリーミングを停止(unbind)し、BarcodeScanning.getClient()でインスタンス作成したscannerをcloseします。
そうするとバーコードスキャンの処理が停止します。
fun startPreview(){
try {
cameraProvider.bindToLifecycle(
this,
cameraSelector,
previewUseCase,
analysisUseCase)
} catch (illegalStateException: IllegalStateException) {
Log.e(TAG, illegalStateException.message.orEmpty())
} catch (illegalArgumentException: IllegalArgumentException) {
Log.e(TAG, illegalArgumentException.message.orEmpty())
}
}
fun stopPreview(){
// previewViewへのストリーミングを停止
cameraProvider.unbind(previewUseCase)
// バーコードスキャンを停止
scanner.close()
}
おわりに
上記のやり方でCameraXをつかったバーコードスキャンをON/OFFすることができました。
Discussion