Is there a way to convert compose `Painter` to a ...
# compose
f
Is there a way to convert compose
Painter
to a
Drawable
? I know that for the other way there is
painterResource()
and
rememberDrawablePainter()
in Accompanist, but I have not found anything about the inverse way.
We have a custom view that can accept drawables and I am trying to wrap it in compose with
AndroidView
like:
Copy code
CustomView(title: String, icon: Painter, margin: Dp, modifier: Modifier)
It can handle strings just fine and I can convert
Dp
to pixels easily, but I am for now stuck on the
Painter->Drawable
.
I could have the component accept
Drawable
, but that feels a bit weird in a compose world.
a
Maybe in that case you’d be better off passing it as an
Int
id? There’s nothing you can extract from the BitmapPainter/VectorPainter that the painterRessource creates
a
you can create a compose Canvas from the android.graphics.Canvas passed to Drawable.draw; extend Drawable and have it call your Painter to do its drawing
t
@Adam Powell Do you have some sample code about that? I'm trying to find a way to render ImageVector to bitmaps for widgets and avoid having duplicated VectorDrawable xmls.
f
Thanks for the pointer. Here is my first attempt:
Copy code
@Composable
fun Painter.toDrawable(
    density: Density = LocalDensity.current,
    layoutDirection: LayoutDirection = LocalLayoutDirection.current
): Drawable = object : Drawable() {

    private var alpha = 0xFF
    private var colorFilter: ColorFilter? = null

    override fun draw(canvas: android.graphics.Canvas) {
        val scope = CanvasDrawScope()
        scope.draw(density, layoutDirection, Canvas(canvas), intrinsicSize) {
            draw(intrinsicSize, alpha / 255f, colorFilter?.asComposeColorFilter())
        }
    }

    override fun getIntrinsicWidth(): Int = intrinsicSize.width.toInt()

    override fun getIntrinsicHeight(): Int = intrinsicSize.height.toInt()

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

    override fun getAlpha(): Int = alpha

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

    override fun getColorFilter(): ColorFilter? = colorFilter

    override fun getOpacity() = PixelFormat.UNKNOWN
}
At first sight it seems to do what I want. Is this ok, or is there something wrong - both logic and code style-wise? Would it maybe make sense to remember this?
c
Looks fine overall. A new nits: • That function doesn't need to be composable. Tbh it should be probably be a class:
class PainterDrawable(painter, density, layoutDirection): Drawable
+ optional
remember
function. • Cache the
Canvas(canvas)
and
CanvasDrawScope()
. There's no need to create one on every call if the canvas is the same. • Coerce the
alpha / 255f
within
0f,1f
. It could technically go out of that range with float ops. • You'll want to use
PixelFormat.TRANSPARENT
f
Thanks, so here is the drawable class:
Copy code
class PainterDrawable(
    private val painter: Painter,
    private val density: Density,
    private val layoutDirection: LayoutDirection
) : Drawable() {

    private val scope = CanvasDrawScope()
    private val canvasHolder = CanvasHolder()

    private var alpha = 0xFF
    private var colorFilter: ColorFilter? = null

    override fun draw(canvas: android.graphics.Canvas) = canvasHolder.drawInto(canvas) {
        scope.draw(density, layoutDirection, this, painter.intrinsicSize) {
            with(painter) {
                draw(
                    painter.intrinsicSize,
                    (alpha / 255f).coerceIn(0f, 1f),
                    colorFilter?.asComposeColorFilter()
                )
            }
        }
    }

    override fun getIntrinsicWidth(): Int = painter.intrinsicSize.width.toInt()

    override fun getIntrinsicHeight(): Int = painter.intrinsicSize.height.toInt()

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

    override fun getAlpha(): Int = alpha

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

    override fun getColorFilter(): ColorFilter? = colorFilter

    override fun getOpacity() = PixelFormat.TRANSPARENT
}
As for the remember fun, I just was not sure, so I remembered everything 🤣 :
Copy code
@Composable
fun rememberPainterDrawable(
    painter: Painter,
    density: Density = LocalDensity.current,
    layoutDirection: LayoutDirection = LocalLayoutDirection.current
): Drawable = remember(painter, density, layoutDirection) { PainterDrawable(painter, density, layoutDirection) }
c
Looks good to me!
🙏 2
👍🏼 1
👍 1