5

I am extending Chip class to perform some drawing over it for my lib , my use case is more complex but for simplicity let's say i am just drawing a diagonal line

my code

class MyChip (context: Context,attributeSet: AttributeSet) : Chip(context,attributeSet){

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    //just want to draw a diagonal line
    canvas.drawLine(0f,0f,width/1f,height/1f,paint)
  }
 }

xml

<com.abhinav.chouhan.loaderchipdemo.MyChip
    android:layout_width="200dp"
    android:layout_height="50dp"
    android:textAlignment="center"
    android:text="SOME TEXT"/>

when i don't have attribute android:textAlignment="center" everything works fine , but with that attribute we can not draw anything on chip. I tried everything but couldn't figure out why is it happening.

Please Help

Abhinav Chauhan
  • 1,304
  • 1
  • 7
  • 24

1 Answers1

3

The Chip widget adheres strongly to the Material Design guidelines and is not (IMO) amenable to change. I suggest that you look at other widgets - maybe a MaterialButton to see if something else may suit your needs.

The attribute you reference, textAlignment, is available in TextView which is a class that Chip is based on. Chips expects wrap_content and also expect for text to be aligned to the start. Somewhere in the mix, your over-draw is lost. I doubt that something like that has ever been tested as it does not adhere to the Material guidelines.

However, if you must use a Chip, here is a way to do so with a custom view:

class MyChip @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : Chip(context, attrs, defStyleAttr) {

    private val mLinePaint = Paint().apply {
        color = Color.BLUE
        strokeWidth = 10f
    }

    init {
        doOnNextLayout {
            // Turn off center alignment if specified. We will handle centering ourselves.
            if (isTextAlignmentCenter()) {
                textAlignment = View.TEXT_ALIGNMENT_GRAVITY
                // Get the length of all the stuff before the text.
                val lengthBeforeText =
                    if (isChipIconVisible) iconStartPadding + chipIconSize + iconEndPadding else 0f
                val chipCenter = width / 2
                // Chips have only one line, so we can get away with this.
                val textWidth = layout.getLineWidth(0)
                val newTextStartPadding = chipCenter - (textWidth / 2) - lengthBeforeText
                textStartPadding = max(0f, newTextStartPadding)
                textEndPadding = 0f
            }
        }
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        //just want to draw a diagonal line
        canvas.drawLine(0f, 0f, width.toFloat(), height.toFloat(), mLinePaint)
    }

    private fun isTextAlignmentCenter() = textAlignment == View.TEXT_ALIGNMENT_CENTER
}

A sample layout:

activity_main.xml

    <com.example.myapplication.MyChip
        android:id="@+id/chip"
        android:layout_width="300dp"
        android:layout_height="50dp"
        android:layout_gravity="center"
        android:text="Some Chip"
        android:textAlignment="center"
        app:chipIcon="@drawable/ic_baseline_cloud_download_24"
        app:chipIconVisible="true"
        app:closeIcon="@drawable/ic_baseline_clear_24"
        app:closeIconVisible="true" />
</LinearLayout>

And the result:

enter image description here

I looked into the issue a little more deeply. For some reason, canvas operations such a drawLine(), drawRect(), etc. do not function. However, I can draw text on the canvas and fill it with a paint or a color.


Update: So, I tracked the problem down to the bringTextIntoView() method of TextView. For a reason that is not clear to me, the view is scrolled quite a few places (large, like 10's of thousands) in the positive direction. It is in this scrolled position that the text is written. To draw on the Chip, we must also scroll to this position. This scroll explains why I could fill the canvas with a color but could not draw on it so it would be visible.

The following will capture the "bad" scroll position and scroll to that position before drawing on the Chip. The screen capture looks the same as the above image.

MyChip.kt

class MyChip @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : Chip(context, attrs, defStyleAttr)/*, View.OnScrollChangeListener*/ {

    private val mLinePaint = Paint().apply {
        color = Color.BLUE
        strokeWidth = 10f
    }

    private var mBadScroll = 0f

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        //just want to draw a diagonal line
        canvas.withTranslation(x = mBadScroll) {
            canvas.drawLine(0f, 0f, this@MyChip.width.toFloat(), height.toFloat(), mLinePaint)
        }
    }

    private fun isTextAlignmentCenter() = textAlignment == View.TEXT_ALIGNMENT_CENTER

    override fun scrollTo(x: Int, y: Int) {
        super.scrollTo(x, y)
        if (isTextAlignmentCenter()) {
            mBadScroll = scrollX.toFloat()
        }
    }
}

Update: Horizontal scrolling is still the issue. In onMeasure() method of TextView, the wanted width is set to VERY_WIDE if horizontal scrolling is enabled for the view. (And it is for Chip.) VERY_WIDE is 1024*1024 or one megabyte. This is the source of the large scroll that we see later on. This problem with Chip can be replicated directly with TextView if we call setHorizontallyScrolling(true) in the TextView.

Chips have only one line and, probably, shouldn't scroll. Calling setHorizontallyScrolling(true) doesn't make the Chip or TextView scrollable anyway. The above code now just becomes:

MyChip.kt

class MyChip @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : Chip(context, attrs, defStyleAttr) {

    private val mLinePaint = Paint().apply {
        color = Color.BLUE
        strokeWidth = 10f
    }

    init {
        setHorizontallyScrolling(false)
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        //just want to draw a diagonal line
        canvas.drawLine(0f, 0f, this@MyChip.width.toFloat(), height.toFloat(), mLinePaint)
    }
}

Horizontal scrolling is set to "true" in the constructor for Chip.

Cheticamp
  • 61,413
  • 10
  • 78
  • 131
  • thanks for the answer , I know about hack of `textStartPadding`, `chipStartPadding` , I want know why `textAlignment=center` is eating the drawing while the drawing code executes , and I can draw if it is not present , I want my users to use `textAlignment` with `center` , u said `attribute` belongs to `TextView` but it doesn't cause that problem with `TextView` or any other super class of `Chip` I tried all of them , so there is something wrong in the `Chip`'s code , would be really helpful if you can tell me how to detect if user used `textAligment=center` so i can use hacks internally. – Abhinav Chauhan Jul 14 '21 at 12:48
  • @AbhinavChauhan There is an issue either in the Chip code or the ChipDrawable code or both. Everything works fine if ChipDrawable draws the text itself. It is only when ChipDrawable hands over the text drawing to TextView that there is an issue. (Unfortunately, this is the default and I don't see a way of doing otherwise.) `textAlignment` is actually an attribute of _View_, so _TextView_ inherits it. So, you are looking for a way to detect if `textAlignment=center` has been specified so you can turn it off and do the hack? Is that right? – Cheticamp Jul 14 '21 at 13:54
  • yeah I want user to have consistent behaviour i want to draw on chip with `textAlignment=center` , i can check the alignment with `textAlignment` and set default alignment to get rid of drawing problem and tried to center text myself but still facing some problems – Abhinav Chauhan Jul 14 '21 at 15:57
  • It is working great thanks alot , without fixing this issue other feature of lib like `loadingText` could not work , I read code of chip , chipdrawable and other super classes but couldn't figure out, I really appreciate that , you fixed the bug left by google :) , and this solution is much cleaner than the hacks , see https://github.com/AbhinavChauhan97/LoaderChip/blob/master/README.md do you suggest any other features that we can add – Abhinav Chauhan Jul 15 '21 at 02:33
  • @AbhinavChauhan See updated answer which gets more to the root of the problem. – Cheticamp Jul 15 '21 at 12:14
  • great , you're awesome , any idea why did chip's author set horizontal scrolling true? – Abhinav Chauhan Jul 16 '21 at 04:10
  • 1
    @AbhinavChauhan It helps to ensure that chips have only one line and can scroll if need be. – Cheticamp Jul 16 '21 at 08:18