Getting bitmap from layout gets the layout incomplete

Viewed 397

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!

2 Answers

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()

The onDraw() function of VerticalTextView.kt is

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)
    }
}

The height in translate(textSize, height.toFloat()) is the height of the canvas and not the height of the view (which it should be). Why? Because this within the c.withSave{} is the canvas and not the custom view. This is causing the custom view to shift. These types of problems can be hard to track down.

Change that translate to the following to pick up the height of the view (which doesn't include the margins) and all will work.

translate(textSize, this@VerticalTextView.height.toFloat())
Related