Search code examples
androidandroid-layoutcanvasbitmaptextview

Getting bitmap from layout gets the layout incomplete


I am using a custom TextView that draws the text vertical, a graph from MPAndroidCharts library and a textview on top of the graph. While it looks great on the screen, when I try to get a Bitmap of it so it can be shared as an image, the vertical text views get are not drawn completely.

My XML of the View is the following:

<androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:id="@+id/clWeatherCharts">
        <TextView
            android:id="@+id/txtWeatherTitle"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:layout_margin="@dimen/margin_16dp"
            android:text="@string/temperature_registers"
            android:textAlignment="center"/>

            <com.shadows.naylapp.utils.VerticalTextView
                android:id="@+id/llYEnvironment"
                android:textColor="@color/colorBlack"
                android:textSize="14sp"
                android:layout_width="20dp"
                android:layout_height="0dp"
                android:text="Temperatura Superficial del Mar (Cº)"
                android:layout_margin="@dimen/padding_8dp"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="@id/lineWeatherChart"
                app:layout_constraintBottom_toBottomOf="@id/lineWeatherChart"
                />

        <com.github.mikephil.charting.charts.LineChart
            android:id="@+id/lineWeatherChart"
            android:layout_marginTop="@dimen/margin_16dp"
            android:layout_width="0dp"
            android:layout_height="300dp"
            app:layout_constraintEnd_toStartOf="@id/llYWindEnvironment"
            app:layout_constraintStart_toEndOf="@id/llYEnvironment"
            app:layout_constraintTop_toBottomOf="@id/txtWeatherTitle" />
        <LinearLayout
            android:id="@+id/llYWindEnvironment"
            android:layout_width="20dp"
            android:layout_height="0dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="@id/lineWeatherChart"
            app:layout_constraintBottom_toBottomOf="@id/lineWeatherChart">

            <com.shadows.naylapp.utils.VerticalTextView
                android:textColor="@color/colorBlack"
                android:textSize="14sp"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:text="Velocidad del Viento (Km/hr)"
                />
        </LinearLayout>

    </androidx.constraintlayout.widget.ConstraintLayout>

I am using an extension function to create the bitmap which is the following:

fun View.getBitmapFromView(): Bitmap? {
val bitmap = Bitmap.createBitmap(this.width, this.height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
 canvas.drawColor(Color.WHITE)
 this.draw(canvas)
return bitmap
}

and I call it like this: clWeatherCharts.getBitmapFromView()

the following is an image of the screen on the app screen on the app with vertical text views complete

And the following is the bitmap which gets the vertical textviews cropped: screen with cropped vertical text views

The code for the vertical text view is the following:

class VerticalTextView(context: Context, attrs: AttributeSet) : AppCompatTextView(context, attrs) {
private val topDown = gravity.let { g ->
    !(Gravity.isVertical(g) && g.and(Gravity.VERTICAL_GRAVITY_MASK) == Gravity.TOP)
}
private val metrics = BoringLayout.Metrics()
private var padLeft = 0
private var padTop = 0

private var layout1: Layout? = null

override fun setText(text: CharSequence, type: BufferType) {
    super.setText(text, type)
    layout1 = null
}

private fun makeLayout(): Layout {
    if (layout1 == null) {
        metrics.width = height
        paint.color = currentTextColor
        paint.drawableState = drawableState
        layout1 = BoringLayout.make(text, paint, metrics.width, Layout.Alignment.ALIGN_NORMAL, 2f, 0f, metrics, false, TextUtils.TruncateAt.MIDDLE, height - compoundPaddingLeft - compoundPaddingRight)
        padLeft = compoundPaddingLeft
        padTop = extendedPaddingTop
    }
    return layout1!!
}

override fun onDraw(c: Canvas) {
    //      c.drawColor(0xffffff80); // TEST
    if (layout == null)
        return
    c.withSave {
        if (topDown) {
            val fm = paint.fontMetrics
            translate(textSize - (fm.bottom + fm.descent), 0f)
            rotate(90f)
        } else {
            translate(textSize, height.toFloat())
            rotate(-90f)
        }
        translate(padLeft.toFloat(), padTop.toFloat())
        makeLayout().draw(this)
    }
}
}

I have already tried putting some padding on the textviews, or centering the text and it gets even more cropped when getting the bitmap.

Any help is appreciated!


Solution

  • Solution

    You can go through each child in a ConstraintLayout, then draw the child's content by using Bitmap.drawBitmap(Bitmap, int, int, Paint).

    // Get bitmap from a View
    fun View.getBitmapFromView(): Bitmap? {
        val bitmap = Bitmap.createBitmap(this.width, this.height, Bitmap.Config.ARGB_8888)
        val canvas = Canvas(bitmap)
        draw(canvas)
        return bitmap
    }
    
    // Get bitmap from a ConstraintLayout
    fun ConstraintLayout.getBitmapFromView(): Bitmap {
        val bitmap = Bitmap.createBitmap(this.width, this.height, Bitmap.Config.ARGB_8888)
        val canvas = Canvas(bitmap)
        canvas.drawColor(Color.WHITE)
    
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            val childBitmap = child.getBitmapFromView()
            if (childBitmap != null) {
                canvas.drawBitmap(childBitmap, child.left.toFloat(), child.top.toFloat(), null)
            }
        }
    
        return bitmap
    }
    

    Using from the code

    clWeatherCharts.getBitmapFromView()