Edit:
My previous example didn't properly work (1). The following kotlin extension should work for all start & end drawables, takes the TextView paddings into account, and is independent of any views it is wrapped in.
private const val START = 0
private const val END = 2
fun TextView.onDrawableClicked(
onDrawableStartClicked: () -> Unit = {},
onDrawableEndClicked: () -> Unit = {}
) {
setOnTouchListener(View.OnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_UP -> {
compoundDrawables[START]?.let { startDrawable ->
val clickableAreaStart = paddingStart
val clickableAreaEnd = startDrawable.bounds.width() + paddingStart
if (event.x >= clickableAreaStart && event.x <= clickableAreaEnd) {
onDrawableStartClicked()
return@OnTouchListener true
}
}
compoundDrawables[END]?.let { endDrawable ->
val startOfDrawable = width - endDrawable.bounds.width() - paddingEnd
val endOfDrawable = width - paddingEnd
if (event.x >= startOfDrawable && event.x <= endOfDrawable) {
onDrawableEndClicked()
return@OnTouchListener true
}
}
}
}
true
})
}
Implement like this for end drawable:
my_view.onDrawableClicked(onDrawableEndClicked = {
showPilotGroupPopup()
})
Android advices to use a minimum of 48dp touch size. To enable this in the extension, use the code below instead (you can always decide to ignore the additional touch area per view). Of course it won't enable clicks all around the drawable, only horizontally inside the view itself, but it's better than nothing!
private const val START = 0
private const val END = 2
fun TextView.onDrawableClicked(
onDrawableStartClicked: () -> Unit = {},
onDrawableEndClicked: () -> Unit = {},
applyMinimalTouchSize: Boolean = true
) {
setOnTouchListener(View.OnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_UP -> {
compoundDrawables[START]?.let { startDrawable ->
val startDrawableWidth = startDrawable.bounds.width()
var clickableAreaStart = paddingStart
var clickableAreaEnd = startDrawableWidth + paddingStart
if (applyMinimalTouchSize) {
clickableAreaStart -= getTouchSizeCorrection(resources, startDrawableWidth)
clickableAreaEnd += getTouchSizeCorrection(resources, startDrawableWidth)
}
if (event.x >= clickableAreaStart && event.x <= clickableAreaEnd) {
onDrawableStartClicked()
return@OnTouchListener true
}
}
compoundDrawables[END]?.let { endDrawable ->
val endDrawableWidth = endDrawable.bounds.width()
var clickableAreaStart = width - endDrawableWidth - paddingEnd
var clickableAreaEnd = width - paddingEnd
if (applyMinimalTouchSize) {
clickableAreaStart -= getTouchSizeCorrection(resources, endDrawableWidth)
clickableAreaEnd += getTouchSizeCorrection(resources, endDrawableWidth)
}
if (event.x >= clickableAreaStart && event.x <= clickableAreaEnd) {
onDrawableEndClicked()
return@OnTouchListener true
}
}
}
}
true
})
}
private fun getTouchSizeCorrection(resources: Resources, visibleSize: Int): Int {
// R.dimen.touch_size = 48dp
return ((resources.getDimensionPixelSize(R.dimen.touch_size) - visibleSize) / 2).coerceAtLeast(0)
}
(1) The original answer didn't properly work in all cases because of incorrect calculations. Additionally it returned false, which sometimes prevented triggering ACTION_UP.
Original answer:
The answer of Paul Verest isn't bad, but doesn't take padding into account.
Here's an example (in Kotlin) for left and right clicks, including padding of the editText (which shifts the drawable relative from the sides of the editText).
editText.setOnTouchListener(View.OnTouchListener { _, event ->
val DRAWABLE_LEFT = 0
val DRAWABLE_RIGHT = 2
if (event.action == MotionEvent.ACTION_UP) {
if (event.x <= editText.compoundDrawables[DRAWABLE_LEFT].bounds.width() + (2 * editText.paddingStart)) {
// Left drawable clicked
return@OnTouchListener true
}
if (event.x >= editText.right - editText.compoundDrawables[DRAWABLE_RIGHT].bounds.width() - (2 * editText.paddingEnd)) {
// Right drawable clicked
return@OnTouchListener true
}
}
false
})
Notes:
- I use
event.x
instead of event.rawX
to get the click location. event.rawX
is the raw X coordinate on the screen, it doesn't take the location of the editText into account (eg. margins that precent the editText from clipping the screen sides). event.x
is the x coordinate relative to the left bound of the editText itself, which makes the calculations easier to understand.
- Always take padding into account when making elements clickable! Of course, if you shift the drawable too far using the editText padding, then it doesn't make sense to make the complete padding clickable. In my case it works well to use the
editText.paddingStart
twice, so it's clickable for both sides of the drawable. You might consider using this padding once for the left side of the starting-drawable only, and add the editText.compoundDrawablePadding
- which is the padding between drawable and text - for the right side padding. Of course you can also use some constant dp values, depending on your own preference. The concept of clickable padding is explained very well in this blog.