ZXing для Kotlin: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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

Рекомендаций