Home > Software design >  How to define behavior from the object instance
How to define behavior from the object instance

Time:02-26

I have an EditText that I overrode in order to detect clicks on the compound drawables.

class MyEditText
constructor(
    context: Context,
    attrs: AttributeSet
) : androidx.appcompat.widget.AppCompatEditText( context, attrs) {

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        val result = super.onTouchEvent(event)
        if (event != null) {
            // All advice says to use ACTION_UP but that never gets here
            if( event.action == MotionEvent.ACTION_DOWN ){
                if(event.x <= this.totalPaddingStart){
                    onStartDrawableClick()
                }
                if(event.x >= this.width - this.totalPaddingEnd){
                    onEndDrawableClick()
                }
                performClick()
                return true
            }
        }
        return result
    }

    // Overriding this avoids an accessibility warning 
    override fun performClick(): Boolean {
        return super.performClick()
    }

    public fun onEndDrawableClick(){
        Log.e(TAG, "onEndDrawableClick: ")
    }

    public fun onStartDrawableClick(){
        Log.e(TAG, "onStartDrawableClick: ")
    }
}

This works but I want to be able to define what happens in onEndDrawableClick() from the MyEditText object instance, not in the class. I cant pass a closure to the constructor since its a view with params for xml instantiation. Is there an elegant way to do this?

(Extra bonus points for figuring out why ACTION_UP is never seen)

CodePudding user response:

Apologies for my rusty Kotlin, but something like this should work:

  var endClickHandler: View.OnClickListener?

  public fun onEndDrawableClick(){
    Log.e(TAG, "onEndDrawableClick: ")
    endClickHandler?.onClick(this)
  }

CodePudding user response:

You can define callback properties that can be set from outside the class. Also, you can make the MotionEvent parameter non-nullable since the Android function will never pass you a null value. Then you don't have to do the null check.

Also, if you don't want other click events to happen (like if you set a click listener on the TextView) when you click on this item, you should not call super when the icon is clicked. And you should call through when the touch misses the icon instead of returning true. Example of rearranging the logic like this below.

class MyEditText(
    context: Context,
    attrs: AttributeSet
) : androidx.appcompat.widget.AppCompatEditText(context, attrs) {

    var onEndDrawableClick: (()->Unit)? = null
    var onStartDrawableClick: (()->Unit)? = null

    override fun onTouchEvent(event: MotionEvent): Boolean {
        if( event.action == MotionEvent.ACTION_DOWN ){
            if(event.x <= this.totalPaddingStart){
                onStartDrawableClick?.invoke()
                performClick()
                return true
            }
            if(event.x >= this.width - this.totalPaddingEnd){
                onEndDrawableClick?.invoke()
                performClick()
                return true
            }
        }
        return super.onTouchEvent(event)
    }

    // Overriding this avoids an accessibility warning 
    override fun performClick(): Boolean {
        return super.performClick()
    }

}

Handling a click that behaves as users are probably accustomed, i.e. has visual/audible feedback on touch down, is cancellable by moving your finger off the target before releasing, and firing only if released over the touch target; is not trivial. You might want to build this UI component out of a RelativeLayout or ConstraintLayout that contains an EditText and two Buttons in it to provide a nicer experience.

  • Related