Android library for creating QR-codes with logo, custom pixel/eyes shapes, background image. Powered by ZXing.
Some useful links:
- Examples with source code
- Wiki
- Version for Flutter with some limitations
To get a Git project into your build:
Step 1. Add the JitPack repository to your build file
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
Or for gradle 7+ to settings.gradle file:
dependencyResolutionManagement {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
Step 2. Add the dependency.
dependencies {
implementation 'com.github.alexzhirkevich:custom-qr-generator:1.5.6'
}
Step 3. Press ⭐ if you liked this lib
There are 2 types of QR code image - raster (deprecated) image and vector image.
Raster (deprecated) | Vector | |
---|---|---|
Output image type | android.graphics.Bitmap |
android.graphics.drawable.Drawable |
Size | ❌ Fixed | ✅ Dynamic. Based on View size |
Speed | ❌ Slow (> 500 ms in average), so must be created in advance and only in background thread. Coroutines support included | ✅ Instant. All calculations performed during Drawable.setBounds , almost instantly |
You should use deprecated Raster QR codes only if you need extra customizability or special features like in this example.
Drawable
QR codes should not be converted to Bitmap
. Use this Accompanist library or AndroidView
for Jetpack Compose interop.
Drawable
QR codes can be safely coverted to Bitmap
and used as Compose ImageBitmap
. Previous solutions work too.
Step 1. Create QR code data. There are multiple QR types: Plain Text, Url, Wi-Fi, Email, GeoPos, Profile Cards, Phone, etc.
val data = QrData.Url("https://example.com")
Step 2. Define styling options.
val options = createQrVectorOptions {
padding = .125f
background {
drawable = DrawableSource
.Resource(R.drawable.frame)
}
logo {
drawable = DrawableSource
.Resource(R.drawable.tg)
size = .25f
padding = QrVectorLogoPadding.Natural(.2f)
shape = QrVectorLogoShape
.Circle
}
colors {
dark = QrVectorColor
.Solid(Color(0xff345288))
ball = QrVectorColor.Solid(
ContextCompat.getColor(context, R.color.your_color)
)
}
shapes {
darkPixel = QrVectorPixelShape
.RoundCorners(.5f)
ball = QrVectorBallShape
.RoundCorners(.25f)
frame = QrVectorFrameShape
.RoundCorners(.25f)
}
}
val options = QrVectorOptions.Builder()
.padding(.3f)
.logo(
QrVectorLogo(
drawable = DrawableSource
.Resource(R.drawable.tg),
size = .25f,
padding = QrVectorLogoPadding.Natural(.2f),
shape = QrVectorLogoShape
.Circle
)
)
.background(
QrVectorBackground(
drawable = DrawableSource
.Resource(R.drawable.frame),
)
)
.colors(
QrVectorColors(
dark = QrVectorColor
.Solid(Color(0xff345288)),
ball = QrVectorColor.Solid(
ContextCompat.getColor(context, R.color.your_color)
)
)
)
.shapes(
QrVectorShapes(
darkPixel = QrVectorPixelShape
.RoundCorners(.5f),
ball = QrVectorBallShape
.RoundCorners(.25f),
frame = QrVectorFrameShape
.RoundCorners(.25f),
)
)
.build()
Step 3. Create QR code drawable:
val drawable = QrCodeDrawable(context, data, options)
Deprecated (click to show)
Step 1. Create QR code data. There are multiple QR types: Plain Text, Url, Wi-Fi, Email, GeoPos, Profile Cards, Phone, etc.
val data = QrData.Url("https://example.com")
Step 2. Define styling options using builder:
// Color(v : Long) and Long.toColor() functions take
// 0xAARRGGBB long and convert it to color int.
// Colors from android resources also can be used.
val options = QrOptions.Builder(1024)
.padding(.3f)
.background(
QrBackground(
drawable = DrawableSource
.Resource(R.drawable.frame),
)
)
.logo(
QrLogo(
drawable = DrawableSource
.Resource(R.drawable.tg),
size = .25f,
padding = QrLogoPadding.Accurate(.2f),
shape = QrLogoShape
.Circle
)
)
.colors(
QrColors(
dark = QrColor
.Solid(Color(0xff345288)),
highlighting = QrColor
.Solid(0xddffffff.toColor()),
)
)
.shapes(
QrElementsShapes(
darkPixel = QrPixelShape
.RoundCorners(),
ball = QrBallShape
.RoundCorners(.25f),
frame = QrFrameShape
.RoundCorners(.25f),
highlighting = QrBackgroundShape
.RoundCorners(.05f)
)
)
.build()
Or using DSL:
val options = createQrOptions(1024, 1024, .3f) {
background {
drawable = DrawableSource
.Resource(R.drawable.frame)
}
logo {
drawable = DrawableSource
.Resource(R.drawable.tg)
size = .25f
padding = QrLogoPadding.Accurate(.2f)
shape = QrLogoShape
.Circle
}
colors {
dark = QrColor
.Solid(0xff345288.toColor())
highlighting = QrColor
.Solid(Color(0xddffffff))
}
shapes {
darkPixel = QrPixelShape
.RoundCorners()
ball = QrBallShape
.RoundCorners(.25f)
frame = QrFrameShape
.RoundCorners(.25f)
highlighting = QrBackgroundShape
.RoundCorners(.05f)
}
}
Step 3. Create a QR code generator and pass your data and options into it:
val generator = QrCodeGenerator(context)
val bitmap = generator.generateQrCode(data, options)
QrCodeGenerator
is an interface, but also is a function, that returns generator instance.
generateQrCodeSuspend
is always performed with Dispatchers.Default
//todo: don't use GlobalScope
GlobalScope.launch {
val bitmap = generator.generateQrCodeSuspend(data, options)
}
Generator can work in parallel threads (different Default coroutine dispatchers).
By default generator works in SingleThread. To change it pass another ThreadPolicy
to
QrCodeGenerator
function.
For example:
val threadPolicy = when(Runtime.getRuntime().availableProcessors()){
in 1..3 -> ThreadPolicy.SingleThread
in 4..6 -> ThreadPolicy.DoubleThread
else -> ThreadPolicy.QuadThread
}
val generator = QrCodeGenerator(context, threadPolicy)
Shapes of QR code elements can be customized using android.graphics.Path
.
For example, this is an implementation of circle pixels:
object Circle : QrVectorPixelShape {
override fun createPath(size: Float, neighbors: Neighbors): Path = Path().apply {
addCircle(size/2f, size/2f, size/2, Path.Direction.CW)
}
}
Colors of QR code elements can be customized using android.graphics.Paint
.
For example, this is an implementation of sweep gradient:
class SweepGradient(
val colors: List<Pair<Float, Int>>
) : QrVectorColor {
override fun createPaint(width: Float, height: Float): Paint =
Paint().apply {
shader = android.graphics.SweepGradient(
width / 2, height / 2,
colors.map { it.second }.toIntArray(),
colors.map { it.first }.toFloatArray()
)
}
}
Deprecated (click to show)
You can easily implement your own shapes and coloring for QR Code in 2 ways: using math formulas or by drawing on canvas. Second way is usually slower and uses a lot of memory but provides more freedom.
For example:
- Using math formulas:
- By drawing on canvas:
drawShape
is a generic function that can be used only inside a shapes
or logo
scope
and only to create properties of QrElementsShapes
or QrLogoShape
.
Usage with other type-parameters will cause an exception.
QrOptions
with larger size!
This can cause shape quality issues.
You can also implement QrCanvasShape
and cast it so necessary shape:
object Ring : QrCanvasShape {
override fun draw(
canvas: Canvas, drawPaint: Paint, erasePaint: Paint
) {
// ...
}
}
val ring : QrPixelShape = Ring
.toShapeModifier(elementSize = 48)
.asPixelShape()
// or automatically determine size with DSL
val ringPixelOptions : QrOptions = createQrOptions(1024){
shapes {
darkPixel = drawShape(Ring::draw)
}
}
Using draw
function inside colors
scope you can colorize your code elements as you want.
It will be converted to a QrColor
.
This is QrOptions
of the code above:
val options = createQrOptions(1024, .2f) {
colors {
dark = QrColor.RadialGradient(
startColor = Color.GRAY,
endColor = Color.BLACK
)
ball = draw(CanvasColor::draw)
frame = draw {
withRotation(
180f, width / 2f,
height / 2f, CanvasColor::draw
)
}
symmetry = true
}
shapes {
darkPixel = QrPixelShape.RoundCorners()
frame = QrFrameShape.RoundCorners(
.25f, outer = false, inner = false
)
}
}
QrOptions
, QrVectorOptions
and QrData
can be serialized using kotlinx-serialization (actually any class
from style package can be serialized). All options and QrData classes have @Serializable
annotation.
Every class with interface preperties (or property interfaces themselve) have companion object with
defaultSerializersModule
property. It provides kotlinx-serialization SerializersModule
that
can be used to serialize it default instances.
There is global value QrSerializersModule
, that can be used to serialize any serializable class instance.
Example (requires org.jetbrains.kotlinx:kotlinx-serialization-json
dependency and kotlinx-serialization
plugin):
val options = createQrOptions(1024){
//...
}
val json = Json {
serializersModule = QrSerializersModule
}
val string = json.encodeToString(options)
val decoded = json.decodeFromString<QrOptions>(string)
assert(options == decoded) // true for default options only
If you want to serialize custom options, QrSerializersModule
must be extended:
@Serializable
class Custom : QrPixelShape {
override fun invoke(
i: Int, j: Int, elementSize: Int, neighbors: Neighbors
): Boolean {
//...
}
}
val options = createQrOptions(1024){
shapes {
darkPixel = Custom()
}
}
val json = Json {
serializersModule = SerializersModule {
include(QrSerializersModule)
polymorphic(QrPixelShape::class){
subclass(Custom::class)
}
}
}
val string = json.encodeToString(options)
val decoded = json.decodeFromString<QrOptions>(string)
assert(options == decoded) //true
Serialization can be useful for remote config QR code style changing or to store generated codes with their options for later modification (for ex, in QR code generator apps)
- Some combinations of shapes are not compatible.
- If you create custom shapes, always test if they corrupt your qr code.
- Choose contrast colors for your code and dont't use too many of them at once.
- If you are using logo, make it smaller or apply next advice.
- Set
errorCorrectionLevel
explicitly toQrErrorCorrectionLevel.High
See Issue #6
See Issue #13