ZXing для Kotlin: Полное руководство по сканированию QR-кодов и штрихкодов
В современном мире мобильных приложений и автоматизации процессов, возможность быстрого считывания информации с QR-кодов и штрихкодов стала стандартом де-факто. Библиотека ZXing (произносится как "zebra crossing") — это мощный инструмент с открытым исходным кодом, который позволяет разработчикам легко интегрировать функционал распознавания и генерации одномерных (EAN, UPC, Code 128) и двумерных (QR, Data Matrix, Aztec) кодов в свои приложения.
Данное руководство предназначено для разработчиков на языке Kotlin, которые хотят освоить ZXing от базовой установки до написания сложных сканеров с оптимизацией производительности. Мы разберем не только стандартные сценарии, но и тонкие моменты, которые часто вызывают ошибки у новичков.
1. Что это и зачем нужна?
ZXing — это библиотека для обработки изображений, специализирующаяся на декодировании и кодировании штрихкодов. Она была создана компанией Google и поддерживается сообществом. Основное её преимущество — кроссплатформенность (Java/Kotlin, C++, JavaScript, Python) и поддержка огромного количества форматов: от классических QR-кодов до специфических промышленных кодов типа PDF417.
Зачем использовать ZXing в Kotlin? Во-первых, это золотой стандарт для Android-разработки. Во-вторых, библиотека предоставляет готовые классы для работы с камерой (через CameraX или Camera2), что избавляет от необходимости писать низкоуровневый код захвата видео. В-третьих, ZXing умеет работать не только с живым видео, но и с готовыми изображениями (файлами из галереи), что расширяет сценарии использования: от сканирования чеков до распознавания кодов на упаковках товаров в логистике.
2. Установка
Для Kotlin (Android) установка производится через систему сборки Gradle. Добавьте следующие зависимости в файл build.gradle (Module: app):
dependencies {
// Основная библиотека ZXing
implementation("com.google.zxing:core:3.5.3")
// Дополнительный модуль для работы с камерой (рекомендуется)
implementation("com.journeyapps:zxing-android-embedded:4.3.0")
}
Если вы используете Kotlin Multiplatform или чистый серверный Kotlin (Ktor), вам потребуется только core-модуль:
implementation("com.google.zxing:core:3.5.3")
Для десктопных приложений (JVM) добавьте зависимость в Maven или Gradle аналогично. После синхронизации проекта библиотека готова к использованию.
3. Быстрый старт — минимальный рабочий пример
Самый простой способ протестировать ZXing — запустить встроенное Activity для сканирования. Для этого достаточно вызвать Intent. Ниже приведен минимальный код для Android Activity на Kotlin:
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanOptions
class MainActivity : AppCompatActivity() {
// Регистрируем контракт для запуска сканера
private val barcodeLauncher = registerForActivityResult(ScanContract()) { result ->
if (result.contents == null) {
Toast.makeText(this, "Сканирование отменено", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "Результат: ${result.contents}", Toast.LENGTH_LONG).show()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Настройка опций сканирования
val options = ScanOptions()
options.setDesiredBarcodeFormats(ScanOptions.QR_CODE) // Только QR
options.setPrompt("Наведите камеру на QR-код")
options.setBeepEnabled(true) // Звуковой сигнал
options.setOrientationLocked(false) // Разрешить поворот экрана
// Запуск сканера
barcodeLauncher.launch(options)
}
}
Этот код запускает полноэкранный сканер. После успешного распознавания результат отобразится в Toast. Обратите внимание на ScanContract — это современный способ работы с Activity Result API, который заменяет устаревший startActivityForResult.
4. Основные методы/функции/классы
Рассмотрим 8 ключевых компонентов библиотеки, которые покрывают 95% задач разработчика.
4.1. Класс BinaryBitmap
Сигнатура: class BinaryBitmap(binarizer: Binarizer)
Что делает: Представляет изображение в бинарном (черно-белом) формате, готовом для декодирования. Это основной контейнер, который передается в ридер.
import com.google.zxing.BinaryBitmap
import com.google.zxing.common.HybridBinarizer
import com.google.zxing.RGBLuminanceSource
// Предположим, у нас есть массив пикселей (intArray)
val pixels: IntArray = getPixelsFromBitmap(bitmap)
val width = bitmap.width
val height = bitmap.height
val source = RGBLuminanceSource(width, height, pixels)
val binarizer = HybridBinarizer(source)
val bitmap = BinaryBitmap(binarizer)
4.2. Класс RGBLuminanceSource
Сигнатура: class RGBLuminanceSource(width: Int, height: Int, pixels: IntArray)
Что делает: Преобразует массив RGB-пикселей в оттенки серого (luminance). Это первый шаг перед бинаризацией изображения.
// Создание источника яркости из Bitmap Android
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.test_qr)
val intArray = IntArray(bitmap.width * bitmap.height)
bitmap.getPixels(intArray, 0, bitmap.width, 0, 0, bitmap.width, bitmap.height)
val luminanceSource = RGBLuminanceSource(bitmap.width, bitmap.height, intArray)
4.3. Класс MultiFormatReader
Сигнатура: class MultiFormatReader : Reader
Что делает: Универсальный ридер, который автоматически определяет формат кода (QR, EAN, Code 128 и т.д.) и пытается его декодировать. Рекомендуется для большинства случаев.
import com.google.zxing.MultiFormatReader
import com.google.zxing.NotFoundException
val reader = MultiFormatReader()
try {
val result = reader.decode(bitmap) // bitmap - это BinaryBitmap
println("Тип кода: ${result.barcodeFormat}")
println("Содержимое: ${result.text}")
} catch (e: NotFoundException) {
println("Код не найден на изображении")
}
4.4. Класс QRCodeReader
Сигнатура: class QRCodeReader : Reader
Что делает: Специализированный ридер только для QR-кодов. Работает быстрее, чем MultiFormatReader, если вы точно знаете, что ищете QR.
import com.google.zxing.qrcode.QRCodeReader
val qrReader = QRCodeReader()
val result = qrReader.decode(bitmap)
println("QR-данные: ${result.text}")
4.5. Класс Result
Сигнатура: class Result(text: String, rawBytes: ByteArray, resultPoints: Array<ResultPoint>, format: BarcodeFormat)
Что делает: Объект, возвращаемый после успешного декодирования. Содержит текст, байты, координаты углов кода на изображении и формат.
val result: Result = reader.decode(bitmap)
// Получение координат углов QR-кода
result.resultPoints.forEachIndexed { index, point ->
println("Точка $index: x=${point.x}, y=${point.y}")
}
// Сырые байты (полезно для бинарных данных)
val rawData = result.rawBytes
println("Байты: ${rawData.joinToString(", ") { it.toString() }}")
4.6. Класс BitMatrix
Сигнатура: class BitMatrix(width: Int, height: Int)
Что делает: Представляет собой битовую матрицу (сетку из черных и белых точек). Используется как для декодирования, так и для генерации кодов.
import com.google.zxing.common.BitMatrix
// Создание пустой матрицы 10x10
val matrix = BitMatrix(10, 10)
matrix.set(2, 3) // Установить черную точку в (2,3)
val isSet = matrix.get(2, 3) // true
4.7. Класс QRCodeWriter
Сигнатура: class QRCodeWriter : Writer
Что делает: Генерирует QR-код из текстовой строки. Возвращает BitMatrix, которую можно преобразовать в изображение.
import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.BarcodeFormat
import com.google.zxing.WriterException
val writer = QRCodeWriter()
try {
val bitMatrix: BitMatrix = writer.encode("Hello, ZXing!", BarcodeFormat.QR_CODE, 300, 300)
// Далее bitMatrix можно конвертировать в Bitmap
} catch (e: WriterException) {
e.printStackTrace()
}
4.8. Класс BarcodeFormat
Сигнатура: enum class BarcodeFormat
Что делает: Перечисление всех поддерживаемых форматов штрихкодов. Используется для фильтрации при сканировании или указания формата при генерации.
import com.google.zxing.BarcodeFormat
// Список всех форматов
val formats = BarcodeFormat.values()
formats.forEach { format ->
println("Поддерживаемый формат: ${format.name}")
}
// Использование в опциях сканера
val options = ScanOptions()
options.setDesiredBarcodeFormats(
BarcodeFormat.QR_CODE.name,
BarcodeFormat.CODE_128.name
)
5. Реальный практический пример (100-200 строк кода)
Создадим приложение, которое сканирует QR-код с камеры, а затем генерирует новый QR-код на основе полученных данных. Это классический сценарий "скопировать код".
import android.graphics.Bitmap
import android.graphics.Color
import android.os.Bundle
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.google.zxing.BarcodeFormat
import com.google.zxing.MultiFormatWriter
import com.google.zxing.WriterException
import com.google.zxing.common.BitMatrix
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanOptions
class QrScannerActivity : AppCompatActivity() {
private lateinit var scannedText: TextView
private lateinit var qrImage: ImageView
private lateinit var scanButton: Button
private lateinit var generateButton: Button
private var lastScannedData: String? = null
// Контракт для сканирования
private val scanLauncher = registerForActivityResult(ScanContract()) { result ->
if (result.contents != null) {
lastScannedData = result.contents
scannedText.text = "Сканировано: ${result.contents}"
Toast.makeText(this, "Успешно!", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "Сканирование отменено", Toast.LENGTH_SHORT).show()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
scannedText = findViewById(R.id.scannedText)
qrImage = findViewById(R.id.qrImage)
scanButton = findViewById(R.id.scanButton)
generateButton = findViewById(R.id.generateButton)
scanButton.setOnClickListener {
startScanner()
}
generateButton.setOnClickListener {
lastScannedData?.let { data ->
generateQrCode(data)
} ?: run {
Toast.makeText(this, "Сначала отсканируйте код", Toast.LENGTH_SHORT).show()
}
}
}
private fun startScanner() {
val options = ScanOptions()
options.setDesiredBarcodeFormats(ScanOptions.QR_CODE)
options.setPrompt("Наведите на QR-код")
options.setBeepEnabled(true)
options.setOrientationLocked(true)
scanLauncher.launch(options)
}
private fun generateQrCode(text: String) {
try {
// Генерация BitMatrix
val writer = MultiFormatWriter()
val bitMatrix: BitMatrix = writer.encode(text, BarcodeFormat.QR_CODE, 512, 512)
// Конвертация BitMatrix в Bitmap
val width = bitMatrix.width
val height = bitMatrix.height
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
for (x in 0 until width) {
for (y in 0 until height) {
bitmap.setPixel(x, y, if (bitMatrix[x, y]) Color.BLACK else Color.WHITE)
}
}
qrImage.setImageBitmap(bitmap)
Toast.makeText(this, "QR-код сгенерирован", Toast.LENGTH_SHORT).show()
} catch (e: WriterException) {
Toast.makeText(this, "Ошибка генерации: ${e.message}", Toast.LENGTH_LONG).show()
}
}
}
Пояснение к примеру:
- Сканирование: Используется
ScanContractдля запуска камеры. Результат сохраняется вlastScannedData
Также в библиотеке
kotlinx-datetime для Kotlin: полное руководство с примерами
Prometheus метрики в Rust: библиотека prometheus и metrics-exporter-prometheus
Конфигурация приложений C# (.NET): библиотеки и инструменты
Мониторинг приложений Kotlin: библиотеки и инструменты для отслеживания производительности
go-playground/validator | Валидация данных в Go