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
or saved to file. 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