Search code examples
androidimagedesign-patternsandroid-jetpack-composetile

Android Compose - How to tile/repeat a bitmap/vector?


What is the Android Compose approach to tile an image to fill my background with a small pattern?

A naive approach for Bitmaps without rotation could be like this:

@Composable
fun TileImage() {
    val pattern = ImageBitmap.imageResource(R.drawable.pattern_bitmap)

    Canvas(modifier = Modifier.fillMaxSize()) {
//    rotate(degrees = -15f) { // The rotation does not produce the desired effect
        val totalWidth = size.width / pattern.width
        val totalHeight = size.height / pattern.height

        var x = 0f
        var y = 0f
        for (i in 0..totalHeight.toInt()) {
            y = (i * pattern.height).toFloat()
            for (j in 0..totalWidth.toInt()) {
                x = (j * pattern.width).toFloat()

                drawImage(
                    pattern,
                    colorFilter = giftColorFilter,
                    topLeft = Offset(x, y)
                )
            }
        }
//    }
    }
}

In Android XML you can easily create XML to repeat a bitmap

<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/pattern_bitmap" 
android:tileMode="repeat" />

Or if you need to tile a vector you can use a custom Drawable class to achieve your goal

TileDrawable(AppCompatResources.getDrawable(context, R.drawable.pattern_vector), Shader.TileMode.REPEAT)

class TileDrawable(drawable: Drawable, tileMode: Shader.TileMode, private val angle: Float? = null) : Drawable() {

    private val paint: Paint = Paint().apply {
        shader = BitmapShader(getBitmap(drawable), tileMode, tileMode)
    }

    override fun draw(canvas: Canvas) {
        angle?.let {
            canvas.rotate(it)
        }
        canvas.drawPaint(paint)
    }

    override fun setAlpha(alpha: Int) {
        paint.alpha = alpha
    }

    override fun getOpacity() = PixelFormat.TRANSLUCENT

    override fun setColorFilter(colorFilter: ColorFilter?) {
        paint.colorFilter = colorFilter
    }

    private fun getBitmap(drawable: Drawable): Bitmap {
        if (drawable is BitmapDrawable) {
            return drawable.bitmap
        }
        val bmp = Bitmap.createBitmap(
            drawable.intrinsicWidth, drawable.intrinsicHeight,
            Bitmap.Config.ARGB_8888
        )
        val c = Canvas(bmp)
        drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
        drawable.draw(c)
        return bmp
    }

}

Solution

  • If you want to use native canvas you can do something like this in jetpack compose.

    
        Canvas(
            modifier = Modifier
                .fillMaxSize()
        ) {
        
            val paint = Paint().asFrameworkPaint().apply {
                isAntiAlias = true
                shader = ImageShader(pattern, TileMode.Repeated, TileMode.Repeated)
            }
        
            drawIntoCanvas {
                it.nativeCanvas.drawPaint(paint)
            }
            paint.reset()
        }
    
    

    And If you want to limit your repetition to a certain height and width you can use the clip modifier in canvas like below otherwise it will fill the entire screen.

    
        Canvas(
            modifier = Modifier
                .width(300.dp)
                .height(200.dp)
                .clip(RectangleShape)
        ) {
            ----
        }