16

I have created an EditText for search, which contains on the left side a search icon and on the right side of icon:

<EditText
    android:id="@+id/Search"
    android:layout_width="250dp"
    android:layout_height="wrap_content"
    android:drawableLeft="@android:drawable/ic_menu_search"
    android:drawableRight="@android:drawable/ic_delete"
    android:hint="Search Product .." >
</EditText>

I want to know how can I clear the content of EditText when I click the cross button.

Thank you in advance.

Pankaj Lilan
  • 4,245
  • 1
  • 29
  • 48
Sarra
  • 353
  • 2
  • 3
  • 12
  • Refer this link http://stackoverflow.com/questions/13135447/setting-onclicklistner-for-the-drawable-right-of-an-edittext?rq=1 – Hariharan Apr 20 '14 at 15:26
  • I have a problem when i use this code, The method setDrawableClickListener(new DrawableClickListener(){}) is undefined for the type EditText. – Sarra Apr 20 '14 at 15:48
  • I can't add any comment in this link – Sarra Apr 20 '14 at 16:02

5 Answers5

17

An improved answer by @aristo_sh from Handling click events on a drawable within an EditText

    mQueryEditText.setOnTouchListener(new OnTouchListener() {
        final int DRAWABLE_LEFT = 0;
        final int DRAWABLE_TOP = 1;
        final int DRAWABLE_RIGHT = 2;
        final int DRAWABLE_BOTTOM = 3;
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_UP) {
                int leftEdgeOfRightDrawable = mQueryEditText.getRight() 
                      - mQueryEditText.getCompoundDrawables()[DRAWABLE_RIGHT].getBounds().width();
                // when EditBox has padding, adjust leftEdge like
                // leftEdgeOfRightDrawable -= getResources().getDimension(R.dimen.edittext_padding_left_right);
                if (event.getRawX() >= leftEdgeOfRightDrawable) {
                    // clicked on clear icon
                    mQueryEditText.setText("");
                    return true;
                }
            }
            return false;
        }
    });
Community
  • 1
  • 1
Paul Verest
  • 60,022
  • 51
  • 208
  • 332
  • 1
    Only change you made is adding a variable `leftEdgeOfRightDrawable`. – Sufian Mar 03 '15 at 06:42
  • For some reason, (some?) Samsung devices aren't reporting the correct value for .getRight() – Evan Leis Mar 15 '16 at 17:43
  • Correction! This does not take padding into account, you must subtract the right padding from leftEdgeOfRightDrawable – Evan Leis Mar 15 '16 at 18:33
  • The clicks aren't registered in some cases (don't know when exactly), but can be fixed by always returning true instead of false. The drawablePadding - as mentioned by Evan - is also an issue. I've added an improved example in [my answer](https://stackoverflow.com/a/58163977/2660216) below. – P Kuijpers Aug 24 '20 at 12:34
4

I prefer using another custome Edittex as below clearable edittext. You can use it in xml as normal edittext. To listen to clear event, you can setListener for your ClearableEdittext

/** Copyright 2014 Alex Yanchenko
 * To change clear icon, set
 * <p/>
 * <pre>
 * android:drawableRight="@drawable/custom_icon"
 * </pre>
 */
public class ClearableEditText extends EditText implements OnTouchListener,
        OnFocusChangeListener, TextWatcherAdapter.TextWatcherListener {

    public interface Listener {
        void didClearText();
    }

    public void setListener(Listener listener) {
        this.listener = listener;
    }

    private Drawable xD;
    private Listener listener;

    public ClearableEditText(Context context) {
        super(context);
        init();
    }

    public ClearableEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

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

    @Override
    public void setOnTouchListener(OnTouchListener l) {
        this.l = l;
    }

    @Override
    public void setOnFocusChangeListener(OnFocusChangeListener f) {
        this.f = f;
    }

    private OnTouchListener l;
    private OnFocusChangeListener f;

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (getCompoundDrawables()[2] != null) {
            boolean tappedX = event.getX() > (getWidth() - getPaddingRight() - xD
                    .getIntrinsicWidth());
            if (tappedX) {
                if (event.getAction() == MotionEvent.ACTION_UP) {
                    setText("");
                    if (listener != null) {
                        listener.didClearText();
                    }
                }
                return true;
            }
        }
        if (l != null) {
            return l.onTouch(v, event);
        }
        return false;
    }

    @Override
    public void onFocusChange(View v, boolean hasFocus) {
        if (hasFocus) {
            setClearIconVisible(!TextUtils.isEmpty(getText()));
        } else {
            setClearIconVisible(false);
        }
        if (f != null) {
            f.onFocusChange(v, hasFocus);
        }
    }

    @Override
    public void onTextChanged(EditText view, String text) {
        if (isFocused()) {
            setClearIconVisible(!TextUtils.isEmpty(text));
        }
    }

    private void init() {
        xD = getCompoundDrawables()[2];
        if (xD == null) {
            xD = getResources()
                    .getDrawable(android.R.drawable.presence_offline);
        }
        xD.setBounds(0, 0, xD.getIntrinsicWidth(), xD.getIntrinsicHeight());
        setClearIconVisible(false);
        super.setOnTouchListener(this);
        super.setOnFocusChangeListener(this);
        addTextChangedListener(new TextWatcherAdapter(this, this));
    }

    protected void setClearIconVisible(boolean visible) {
        Drawable x = visible ? xD : null;
        setCompoundDrawables(getCompoundDrawables()[0],
                getCompoundDrawables()[1], x, getCompoundDrawables()[3]);
    }
}

Edit: I forgot TextWatcherAdapter, actually it's only custome TextWatcher:

public class TextWatcherAdapter implements TextWatcher {

public interface TextWatcherListener {

    void onTextChanged(EditText view, String text);

}

private final EditText view;
private final TextWatcherListener listener;

public TextWatcherAdapter(EditText editText, TextWatcherListener listener) {
    this.view = editText;
    this.listener = listener;
}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
    listener.onTextChanged(view, s.toString());
}

@Override
public void beforeTextChanged(CharSequence s, int start, int count,
                              int after) {
    // pass
}

@Override
public void afterTextChanged(Editable s) {
    // pass
}
}
Kingfisher Phuoc
  • 8,052
  • 9
  • 46
  • 86
  • I am unable to find TextWatcherAdapter.TextWatcherListener. Eclipse gives compile time exception "TextWatcherAdapter cannot be resolved to a type". My build target is Android 5.0.1 and min sdk is 8. – prateek Jan 19 '15 at 13:03
  • @prateek I forgot add TextWatcher class. – Kingfisher Phuoc Jan 20 '15 at 07:39
3

Try this:

activity_main.xml

<FrameLayout
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="9dp"
    android:padding="5dp">

    <EditText
        android:id="@+id/Search"
        android:layout_width="250dp"
        android:layout_height="wrap_content"
        android:drawableLeft="@android:drawable/ic_menu_search"
        android:hint="Search Product .." >
    </EditText>

    <Button
        android:id="@+id/clearText"
        android:layout_width="23dp"
        android:layout_height="23dp"
        android:layout_marginRight="10dp"
        android:layout_gravity="right|bottom"
        android:layout_marginBottom="10dp"
        android:background="@android:drawable/ic_delete"
        android:onClick="clear"/>

</FrameLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {

    EditText mEditText;
    Button mClearText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mEditText = (EditText) findViewById(R.id.Search);
        mClearText = (Button) findViewById(R.id.clearText);

        //initially clear button is invisible
        mClearText.setVisibility(View.INVISIBLE);

        //clear button visibility on text change
        mEditText.addTextChangedListener(new TextWatcher() {

            @Override
            public void afterTextChanged(Editable s) {
                //do nothing
            }

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                //do nothing
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                if(s.length() != 0) {
                    mClearText.setVisibility(View.VISIBLE);
                } else {
                    mClearText.setVisibility(View.GONE);
                }
            }
        });

    }

    //clear button onclick
    public void clear(View view) {
        mEditText.setText("");
        mClearText.setVisibility(View.GONE);
    }

}
Ashwin
  • 7,277
  • 1
  • 48
  • 70
1

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.
P Kuijpers
  • 1,593
  • 15
  • 27
0

Exemple I made:

mPasswordView = (EditText) findViewById(R.id.password);
mPasswordView.setOnTouchListener(new View.OnTouchListener() {

        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            if (motionEvent.getAction() == MotionEvent.ACTION_UP){
                // 100 is a fix value for the moment but you can change it
                // according to your view
                if (motionEvent.getX()>(view.getWidth()-100)){
                    ((EditText)view).setText("");
                }
            }
            return false;
        }
    });

    mPasswordView.addTextChangedListener(new TextWatcher() {

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {

            if(s.toString().trim().length()==0){
                mPasswordView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
            } else {
                mPasswordView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_close_black_24dp, 0);
            }
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count,
                                      int after) {
            // TODO Auto-generated method stub
            mPasswordView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);

        }

        @Override
        public void afterTextChanged(Editable s) {
            // TODO Auto-generated method stub
            if (s.toString().trim().length() == 0) {
                mPasswordView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
            } else {
                mPasswordView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_close_black_24dp, 0);
            }
        }
    });