androidkotlinxml-layout

Android XML layout to BMP Issues with width and rendering


I'm having a tough time getting this XML layout to BMP, found similar post here: Converting a view to Bitmap without displaying it in Android?

But nothing in there has helped me; my view.measuredWidth tends to always be 0. I'm running this code from inside an Activity, passing applicationContext. I dont want to take a screenshot of activity loading the layout because i need this to scale properly since it will be printed as well.

val v = LayoutInflater.from(applicationContext).inflate(R.layout.invoice_generated_layout, null)
v.layoutParams = ViewGroup.LayoutParams(
                        ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT
                 )
v.measure(
       View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
       View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
)
v.layout(0, 0, v.measuredWidth, v.measuredHeight)
val b = Bitmap.createBitmap(v.width, v.height, Bitmap.Config.ARGB_8888)
val c = Canvas(b)
v.draw(c)

Here is XML for invoice_generated_layout (over simplified for debugging)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/root_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FFFFFF"
    android:orientation="vertical"
    android:textAlignment="center">

    <ImageView
        android:id="@+id/activity_invlayoutgen_dynamiclogo"
        android:layout_width="wrap_content"
        android:layout_height="65dp"
        android:layout_gravity="center|center_horizontal|center_vertical"
        android:visibility="visible"/>

    <TextView
        android:id="@+id/textView97"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fontFamily="@font/ocraextended"
        android:gravity="center"
        android:includeFontPadding="true"
        android:letterSpacing="-0.01"
        android:text="Hello World, this is just a test."
        android:textAlignment="center"
        android:textColor="#000000"
        android:textSize="12sp"
        android:textStyle="bold" />

</LinearLayout>

I also tested with this code and it does generate bmp but now its blank.

   val view = LayoutInflater.from(applicationContext).inflate(R.layout.invoice_generated_layout, null)
        view.measure(
            View.MeasureSpec.makeMeasureSpec(500, View.MeasureSpec.EXACTLY),
            View.MeasureSpec.makeMeasureSpec(300, View.MeasureSpec.EXACTLY)
        )
        val width = view.measuredWidth
        val height = view.measuredHeight

        val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
        val canvas = Canvas(bitmap)
        view.draw(canvas)

Is it possible that my layout size on the layout designer is bad? Any ideas on what could be happening here?

Note that I did get it to render something when trying different changes to code, but all i got was a long image with only 2px in width...


Solution

  • I figured this out, since i'm not rendering this layout in the activity itself i was trying to generate the bitmap on a layout not yet loaded.

    The new changes listen on ViewTreeObserver to ensure that the layout process is done before attempting to get the measurement off the layout.

    Here is the new code that generates BMP off a view that is not loaded into an activity.

    // Inflate the XML layout
    val inflater = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
    val view = inflater.inflate(R.layout.invoice_generated_layout, null)
    
    // Create a bitmap with arbitrary dimensions for now
    var bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
    
    // Create a canvas with the bitmap
    val canvas = Canvas(bitmap)
    
    // Add the view to a parent layout so that it gets laid out and measured properly
    val parentLayout = FrameLayout(applicationContext)
    parentLayout.addView(view)
    
    // Add a ViewTreeObserver and lets wait for the layout to complete w/e it needs to do...
    parentLayout.viewTreeObserver.addOnGlobalLayoutListener(
        object : ViewTreeObserver.OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                // Remove the listener to prevent multiple calls...
                parentLayout.viewTreeObserver.removeOnGlobalLayoutListener(this)
    
                // Measure and layout the view
                view.measure(
                    View.MeasureSpec.makeMeasureSpec(parentLayout.width, View.MeasureSpec.EXACTLY),
                    View.MeasureSpec.makeMeasureSpec(parentLayout.height, View.MeasureSpec.EXACTLY)
                )
                view.layout(0, 0, view.measuredWidth, view.measuredHeight)
    
                // Resize the bitmap with the measured dimensions
                bitmap.recycle()
                bitmap =
                    Bitmap.createBitmap(
                        view.measuredWidth,
                        view.measuredHeight,
                        Bitmap.Config.ARGB_8888
                    )
    
                // Draw the view onto the canvas
                canvas.setBitmap(bitmap)
                view.draw(canvas)
    
                // Extra code for me to save bitmap to device.
                saveBitmap(bitmap)
            }
        }
    )