nick isra
nick isra

Reputation: 99

How to fix drawable bubble incorrect on api below 23

Here is the drawable code:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >

    <item android:top="0dp" android:bottom="10dp">
        <shape xmlns:android="http://schemas.android.com/apk/res/android"
            android:shape="rectangle">

            <solid android:color="@android:color/holo_blue_light" />
            <stroke
                android:width="1dp"
                android:color="#40adc2" />
            <corners android:radius="4dp" />
        </shape>
    </item>

    <item android:gravity="center_horizontal|bottom">
        <rotate
            android:fromDegrees="45"
            android:toDegrees="45"
            android:pivotX="75%"
            android:pivotY="47%">
            <shape android:shape="rectangle">
                <solid android:color="@android:color/holo_blue_light" />
                <size
                    android:height="20dp"
                    android:width="15dp"/>
            </shape>
        </rotate>
    </item>
</layer-list>

And here are two pictures showing the issue:enter image description here enter image description here

The first image is the incorrect one in API 19 and the second image is the correct one in android 9

Tried searching everywhere couldn't find a solution.

Thanks in advance

Upvotes: 4

Views: 230

Answers (1)

B&#246; macht Blau
B&#246; macht Blau

Reputation: 13019

The layer-list drawable looks bad for API level 19 because

<size android:height="20dp"
      android:width="15dp"/> 

for the small rectangle is ignored. (You can test this if you remove the rotation and let the rectangle with the rounded corners have a transparent color)

For older API levels you have to set values for android:top, android:bottom, android:left and android:right. If you know the dimensions of the TextView in advance, you can calculate the values as follows (unit is always dp):

  • top = (textview height) - 20
  • left = (50% of textview width) - 10
  • right = (50% of textview width) - 10
  • bottom = 0

For example if your TextView has a width of 200dp and a height of 80dp:

<item 
    android:top="60dp" android:bottom="0dp"
    android:left="90dp" android:right="90dp">
    <rotate
        android:fromDegrees="45"
        android:toDegrees="45"
        android:pivotX="75%"
        android:pivotY="47%">
        <shape android:shape="rectangle">
            <solid android:color="@android:color/holo_blue_light" />
        </shape>
    </rotate>
</item>

Please note that if you use this approach you don't need to set the size or the gravity.

Sometimes one can't be sure about the size of the TextView (e.g. because its content may vary across different languages or on different screens). In this case you can create a custom TextView which will draw the bubble as its background:

class BubbleTextView(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : TextView(context, attrs, defStyleAttr) {

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val rectPath = Path()
    private val trianglePath = Path()

    private val rectF = RectF()
    private val triangleSize = resources.getDimensionPixelSize(R.dimen.triangle_size_20dp).toFloat()
    private val cornerRadius = resources.getDimensionPixelSize(R.dimen.corner_radius_4dp).toFloat()

    constructor(context: Context?):this(context, null, 0)
    constructor(context: Context?, attrs: AttributeSet?):this(context, attrs, 0)


    init{
        paint.style = Paint.Style.FILL
        paint.color = Color.CYAN
    }

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        val myWidth = (right - left).toFloat()
        val myHeight = (bottom - top).toFloat()
        val centerX = myWidth / 2f
        val lowerEdgeY = myHeight * 0.8f

        rectF.set(0f, 0f, myWidth, lowerEdgeY)
        rectPath.addRoundRect(rectF,cornerRadius, cornerRadius, Path.Direction.CW )

        val delta = triangleSize * 0.5f
        trianglePath.moveTo(centerX - delta, lowerEdgeY)
        trianglePath.lineTo(centerX + delta, lowerEdgeY)
        trianglePath.lineTo(centerX, myHeight)
        trianglePath.close()
    }

    override fun onDraw(canvas: Canvas?) {
        canvas?.drawPath(rectPath, paint)
        canvas?.drawPath(trianglePath, paint)
        super.onDraw(canvas)
    }
}

Upvotes: 1

Related Questions