138

In my app I have a EditText with a search Icon on the right side. I used the code given below.

 <EditText
        android:id="@+id/search"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_margin="4dip"
        android:layout_weight="1"
        android:background="@drawable/textfield_search1"
        android:drawableLeft="@drawable/logo"
        android:drawableRight="@drawable/search_icon"
        android:hint="Search Anything..."
        android:padding="4dip"
        android:singleLine="true" />

I want to set the onClickListener for the search icon image assigned to the right drawable of EditText. How is it possible?

Cœur
  • 37,241
  • 25
  • 195
  • 267
user965071
  • 1,643
  • 5
  • 16
  • 16
  • 1
    dupplicate http://stackoverflow.com/questions/3554377/handling-click-events-on-a-drawable-within-an-edittext – Tobrun Oct 30 '12 at 08:54
  • 1
    Simplest solution using Android provided API http://stackoverflow.com/a/26269435/185022 :-) –  Dec 16 '14 at 03:37
  • This is the best answer http://stackoverflow.com/a/37032927/346309 – JPM Sep 27 '16 at 18:38
  • If you are using `TextInputLayout` see https://stackoverflow.com/a/65940540/9723204. It's simple and elegant. – Hawklike May 10 '22 at 09:51

5 Answers5

120
public class CustomEditText extends androidx.appcompat.widget.AppCompatEditText {

    private Drawable drawableRight;
    private Drawable drawableLeft;
    private Drawable drawableTop;
    private Drawable drawableBottom;

    int actionX, actionY;

    private DrawableClickListener clickListener;

    public CustomEditText (Context context, AttributeSet attrs) {
        super(context, attrs);
        // this Contructure required when you are using this view in xml
    }

    public CustomEditText(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);        
    }

    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
    }

    @Override
    public void setCompoundDrawables(Drawable left, Drawable top,
            Drawable right, Drawable bottom) {
        if (left != null) {
            drawableLeft = left;
        }
        if (right != null) {
            drawableRight = right;
        }
        if (top != null) {
            drawableTop = top;
        }
        if (bottom != null) {
            drawableBottom = bottom;
        }
        super.setCompoundDrawables(left, top, right, bottom);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Rect bounds;
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            actionX = (int) event.getX();
            actionY = (int) event.getY();
            if (drawableBottom != null
                    && drawableBottom.getBounds().contains(actionX, actionY)) {
                clickListener.onClick(DrawablePosition.BOTTOM);
                return super.onTouchEvent(event);
            }

            if (drawableTop != null
                    && drawableTop.getBounds().contains(actionX, actionY)) {
                clickListener.onClick(DrawablePosition.TOP);
                return super.onTouchEvent(event);
            }

            // this works for left since container shares 0,0 origin with bounds
            if (drawableLeft != null) {
                bounds = null;
                bounds = drawableLeft.getBounds();

                int x, y;
                int extraTapArea = (int) (13 * getResources().getDisplayMetrics().density  + 0.5);

                x = actionX;
                y = actionY;

                if (!bounds.contains(actionX, actionY)) {
                    /** Gives the +20 area for tapping. */
                    x = (int) (actionX - extraTapArea);
                    y = (int) (actionY - extraTapArea);

                    if (x <= 0)
                        x = actionX;
                    if (y <= 0)
                        y = actionY;

                    /** Creates square from the smallest value */
                    if (x < y) {
                        y = x;
                    }
                }

                if (bounds.contains(x, y) && clickListener != null) {
                    clickListener
                            .onClick(DrawableClickListener.DrawablePosition.LEFT);
                    event.setAction(MotionEvent.ACTION_CANCEL);
                    return false;

                }
            }

            if (drawableRight != null) {

                bounds = null;
                bounds = drawableRight.getBounds();

                int x, y;
                int extraTapArea = 13;

                /**
                 * IF USER CLICKS JUST OUT SIDE THE RECTANGLE OF THE DRAWABLE
                 * THAN ADD X AND SUBTRACT THE Y WITH SOME VALUE SO THAT AFTER
                 * CALCULATING X AND Y CO-ORDINATE LIES INTO THE DRAWBABLE
                 * BOUND. - this process help to increase the tappable area of
                 * the rectangle.
                 */
                x = (int) (actionX + extraTapArea);
                y = (int) (actionY - extraTapArea);

                /**Since this is right drawable subtract the value of x from the width 
                * of view. so that width - tappedarea will result in x co-ordinate in drawable bound. 
                */
                x = getWidth() - x;
                
                 /*x can be negative if user taps at x co-ordinate just near the width.
                 * e.g views width = 300 and user taps 290. Then as per previous calculation
                 * 290 + 13 = 303. So subtract X from getWidth() will result in negative value.
                 * So to avoid this add the value previous added when x goes negative.
                 */
                 
                if(x <= 0){
                    x += extraTapArea;
                }
                
                 /* If result after calculating for extra tappable area is negative.
                 * assign the original value so that after subtracting
                 * extratapping area value doesn't go into negative value.
                 */               
                 
                if (y <= 0)
                    y = actionY;                

                /**If drawble bounds contains the x and y points then move ahead.*/
                if (bounds.contains(x, y) && clickListener != null) {
                    clickListener
                            .onClick(DrawableClickListener.DrawablePosition.RIGHT);
                    event.setAction(MotionEvent.ACTION_CANCEL);
                    return false;
                }
                return super.onTouchEvent(event);
            }           

        }
        return super.onTouchEvent(event);
    }

    @Override
    protected void finalize() throws Throwable {
        drawableRight = null;
        drawableBottom = null;
        drawableLeft = null;
        drawableTop = null;
        super.finalize();
    }

    public void setDrawableClickListener(DrawableClickListener listener) {
        this.clickListener = listener;
    }

}

Also Create an Interface with

public interface DrawableClickListener {

    public static enum DrawablePosition { TOP, BOTTOM, LEFT, RIGHT };
    public void onClick(DrawablePosition target); 
    }

Still if u need any help, comment

Also set the drawableClickListener on the view in activity file.

editText.setDrawableClickListener(new DrawableClickListener() {
        
         
        public void onClick(DrawablePosition target) {
            switch (target) {
            case LEFT:
                //Do something here
                break;

            default:
                break;
            }
        }
        
    });
Simran Sharma
  • 852
  • 7
  • 16
Hardik4560
  • 3,202
  • 1
  • 20
  • 31
  • I would have also passed the Edittext as the parameter to the callback. But a good answer never the less. – st0le Oct 30 '12 at 09:02
  • I created the custom EditText. If i touch anywhere in the its going to the onTouchMovement method. but never goes to here "if (bounds.contains(x, y) && clickListener != null) { clickListener .onClick(DrawableClickListener.DrawablePosition.RIGHT); event.setAction(MotionEvent.ACTION_CANCEL); return false; }" – user965071 Oct 30 '12 at 10:47
  • 3
    Hi, I am not find CoreSystemContext.SCALE , so code shows Error. how I can get CoreSystemContext.SCALE. Please advise. – arefin May 21 '13 at 20:53
  • 3
    That is the variable holding device density. use getResources().getDisplayMetrics().density instead of that, you might need to create a static varaible in some other file. This to convert px to dp. A simple solution would be to remove that variable. – Hardik4560 May 22 '13 at 03:54
  • 1
    Please add the statement `playSoundEffect(android.view.SoundEffectConstants.CLICK);` after `if (event.getAction() == MotionEvent.ACTION_DOWN) {` in `onTouchEvent(MotionEvent event)` method so that user can get a click sound. – Sankar V Aug 21 '13 at 15:11
  • If you cant find CoreSystemContext.SCALE, just Change this line int extraTapArea = (int) (13 * getResources().getDisplayMetrics().density + 0.5); – Kokusho Jan 20 '15 at 17:29
  • Too much code. A simple setOnTouchListener will work with this. – V_J Oct 04 '16 at 12:29
  • @V-J can you comment a sample working code :D – Libin Thomas Feb 19 '18 at 07:10
42

This has been already answered but I tried a different way to make it simpler.

The idea is using putting an ImageButton on the right of EditText and having negative margin to it so that the EditText flows into the ImageButton making it look like the Button is in the EditText.

enter image description here

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <EditText
            android:id="@+id/editText"
            android:layout_weight="1"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:hint="Enter Pin"
            android:singleLine="true"
            android:textSize="25sp"
            android:paddingRight="60dp"
            />
        <ImageButton
            android:id="@+id/pastePin"
            android:layout_marginLeft="-60dp"
            style="?android:buttonBarButtonStyle"
            android:paddingBottom="5dp"
            android:src="@drawable/ic_action_paste"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>

Also, as shown above, you can use a paddingRight of similar width in the EditText if you don't want the text in it to be flown over the ImageButton.

I guessed margin size with the help of android-studio's layout designer and it looks similar across all screen sizes. Or else you can calculate the width of the ImageButton and set the margin programatically.

MeetM
  • 1,410
  • 17
  • 25
  • 1
    It's the simple stuff that works best. – DariusL Mar 17 '16 at 09:08
  • 1
    this is redundant work for single drawable – Anand Savjani May 03 '16 at 05:41
  • 1
    margin and padding issues will be there for variety of devices – SMR Jun 20 '17 at 07:16
  • 2
    Linter will give you a warning "inefficient layout tree, use compound drawable", also more code than drawableRight=""... – 最白目 Jul 18 '17 at 12:31
  • 1
    The problem with this approach is that falls apart as soon as you start changing the text size on the EditText. You might think that's just on the developer's side but it's not as far as the devices have text size in settings.You can avoid this using dp instead of sp on the EditText but it just makes things worse. The other problems are things like handling multiline EditTexts – Sotti Dec 17 '17 at 12:34
  • why not use simple FrameLayout ? – Alessandro Scarozza Feb 27 '19 at 21:19
  • `having negative margin to it` That! There goes a hack into it. I would always implement a cleaner solution. The hack is bound to bite you some time later in future. – JaydeepW Oct 23 '19 at 08:22
6

You don't have access to the right image as far my knowledge, unless you override the onTouch event. I suggest to use a RelativeLayout, with one editText and one imageView, and set OnClickListener over the image view as below:

<RelativeLayout
        android:id="@+id/rlSearch"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@android:drawable/edit_text"
        android:padding="5dip" >

        <EditText
            android:id="@+id/txtSearch"
            android:layout_width="match_parent"

            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_toLeftOf="@+id/imgSearch"
            android:background="#00000000"
            android:ems="10"/>

        <ImageView
            android:id="@+id/imgSearch"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:src="@drawable/btnsearch" />
    </RelativeLayout>
Daryl Bennett
  • 462
  • 10
  • 22
jeet
  • 29,001
  • 6
  • 52
  • 53
  • The problem with this approach is that falls apart as soon as you start changing the text size on the EditText. You might think that's just on the developer's side but it's not as far as the devices have text size in settings.You can avoid this using dp instead of sp on the EditText but it just makes things worse. The other problems are things like handling multiline EditTexts – Sotti Dec 17 '17 at 12:33
0

I know this is quite old, but I recently had to do something very similar, and came up with a much simpler solution.

It boils down to the following steps:

  1. Create an XML layout that contains the EditText and Image
  2. Subclass FrameLayout and inflate the XML layout
  3. Add code for the click listener and any other behavior you want... without having to worry about positions of the click or any other messy code.

See this post for the full example: Handling click events on a drawable within an EditText

Community
  • 1
  • 1
Justin
  • 6,564
  • 6
  • 37
  • 34
-2

Please use below trick:

  • Create an image button with your icon and set its background color to be transparent.
  • Put the image button on the EditText
  • Implement the 'onclic'k listener of the button to execute your function
Reporter
  • 3,897
  • 5
  • 33
  • 47
Chirag
  • 56,621
  • 29
  • 151
  • 198
  • what if the size of the editText increase ?, what if I have many editText which requires this, which usually happens. – Hardik4560 Oct 30 '12 at 09:07
  • You need to understand the question! He/she isn't asking an alternative solution. – Nizzy Apr 30 '13 at 21:34
  • The problem with this approach is that falls apart as soon as you start changing the text size on the EditText. You might think that's just on the developer's side but it's not as far as the devices have text size in settings.You can avoid this using dp instead of sp on the EditText but it just makes things worse. The other problems are things like handling multiline EditTexts – Sotti Dec 17 '17 at 12:34