Giving your Edit Texts the All Clear

It’s a very common UX pattern to have an input field that has an ‘x’ at the end, which resets the text in the field back to empty. On mobile phones, where the auto correct often lets you enter a word that you didn’t actually want, this is pretty essential.

It therefore amazes me that there is no standard Android UI component that does this for us. Instead, the poor user is left screaming “Gosh, darnit! That isn’t what I wanted to type.” at their phone and then has to press and hold the delete button, or long-press on the word to select it and then press delete. A simple, single-touch ‘x’ control makes this action easy and is very simple to implement for all your edit texts.

So let’s look at the requirements for this control:

  1. The ‘x’ should only be visible when the edit text has some content and is focussed.
  2. The ‘x’ should appear within the edit text itself.
  3. Pressing the ‘x’ should clear the entire contents of the view.
  4. The colour of the ‘x’ should be consistent with the theme of the edit text.

So what do each of those requirements mean for our custom EditText? The first requirement says that we need a TextWatcher so that we can see when the content of the field changes and that we need to implement an onFocusChangeListener. The second implies that we should implement the ‘x’ as a compound drawable and — since there is no onClick method on a compound drawable — we need to use an OnTouch listener to meet requirement 3. For the fourth requirement I’ve decided that the ‘x’ should be the same as the hint text colour. This is a control that shouldn’t shout “I’m here! Press me all the time”. Instead it should calmly state “I’m here if you need me”. This is a different approach to the ‘x’ in Google’s SearchView widget (which adopts the primary text colour) so feel free to change it if you disagree with my reasoning. It almost definitely should not take the accent or primary colour — that is way too loud.

Building our EditText

So first off let’s build our new class with the relevant listeners. I’ve subclassed AppCompatEditText rather than EditText to ensure that we always get a tinted control for pre-Lollipop devices:

public class ClearableEditText extends AppCompatEditText implements View.OnTouchListener, View.OnFocusChangeListener, TextWatcher {

We want to include all the standard constructors for edit texts and then we need to do a bit of initialisation. So they look like this:

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

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

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

Now let’s look at the init method. In this we want to define the drawable for the clear icon, so that we don’t have to keep doing it, and enable all our listeners. Since we’re using the support library, we’ve already got a clear icon (abc_ic_clear_mtrl_alpha) with the correct dimensions that suits our purposes but it’s white so we need to tint this in such a way that it will work on pre-Lollipop devices too. Here we use the DrawableCompat class to do all this work for us. Here’s the complete init method:

private void init(final Context context) {
final Drawable drawable = ContextCompat.getDrawable(context, R.drawable.abc_ic_clear_mtrl_alpha);
final Drawable wrappedDrawable = DrawableCompat.wrap(drawable); //Wrap the drawable so that it can be tinted pre Lollipop
DrawableCompat.setTint(wrappedDrawable, getCurrentHintTextColor());
mClearTextIcon = wrappedDrawable;
mClearTextIcon.setBounds(0, 0, mClearTextIcon.getIntrinsicHeight(), mClearTextIcon.getIntrinsicHeight());
setClearIconVisible(false);
super.setOnTouchListener(this);
super.setOnFocusChangeListener(this);
addTextChangedListener(this);
}

You can see that I’ve also created another new method called setClearIconVisible and that when the EditText is first instantiated I’m setting it invisible. This is because we’ll rely on the focus changes and text watchers to set the visibility to true. That method looks like this:

private void setClearIconVisible(final boolean visible) {
mClearTextIcon.setVisible(visible, false);
final Drawable[] compoundDrawables = getCompoundDrawables();
setCompoundDrawables(
compoundDrawables[0],
compoundDrawables[1],
visible ? mClearTextIcon : null,
compoundDrawables[3]);
}

This does of course mean that when using this control that you shouldn’t be setting your own right compound drawable since it will always be overwritten by the ‘x’ or nothing when the text or focus changes. If you’re using EditText.setError you will see the alert drawable briefly but any further changes to the text will make the error and the alert drawable disappear. Since you can now surround the EditText in a text input layout and display the error in a much more pleasant way, I’d suggest setting the error on the TextInputLayout instead or not using this control for those scenarios. If you’re setting the top, left or bottom drawable on the EditText those will be maintained.

You’ll also notice that I’m setting the visibility of the clear icon drawable. This does not have any impact on the actual drawable itself but is a hint that we’ll use later in the onTouch listener.

Listening to other controls

The observant amongst you may have noticed that I called the super class for the Touch and Focus listeners in our init method. The reason for this is that I want to override the standard setters so that we can capture those listeners first and either apply our logic or apply the logic of the set listeners. In this way, if you’ve surrounded your EditText with a TextInputLayout then the focus listener that this sets on the EditText can also still be fired. We don’t need to do this for the text watchers because you can already have multiple text watchers on an edit text.

@Override
public void setOnFocusChangeListener(final OnFocusChangeListener onFocusChangeListener) {
mOnFocusChangeListener = onFocusChangeListener;
}

@Override
public void setOnTouchListener(final OnTouchListener onTouchListener) {
mOnTouchListener = onTouchListener;
}

We’ve now set all the fields that we need for our new class so obviously we need to define them in our class too for the code to eventually compile:

private Drawable mClearTextIcon;
private OnFocusChangeListener mOnFocusChangeListener;
private OnTouchListener mOnTouchListener;

And finally…

Now let’s implement our three listeners. Let’s look at focus first:

@Override
public void onFocusChange(final View view, final boolean hasFocus) {
if (hasFocus) {
setClearIconVisible(getText().length() > 0);
} else {
setClearIconVisible(false);
}
if (mOnFocusChangeListener != null) {
mOnFocusChangeListener.onFocusChange(view, hasFocus);
}
}

So for our focus changed, if the edit text is focussed and the text is not empty we want to show the icon, otherwise we set it to null. In either case we want to call any other focus changed listener to ensure that its logic is carried out too.

Now the onTouch:

@Override
public boolean onTouch(final View view, final MotionEvent motionEvent) {
final int x = (int) motionEvent.getX();
if (mClearTextIcon.isVisible() && x > getWidth() - getPaddingRight() - mClearTextIcon.getIntrinsicWidth()) {
if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
setText("");
}
return true;
}
return mOnTouchListener != null && mOnTouchListener.onTouch(view, motionEvent);
}

Here, we first check if the clear icon is actually visible (otherwise you could click where the ‘x’ icon is normally shown and still clear the field), then when we’re clicking in the right area we should always consume the event — we don’t want other touch listeners picking that up. Finally, if the user is lifting their finger up from the ‘x’ control area, we want to clear the text. If the ‘x’ isn’t visible or the onTouch event isn’t within the bounds of the right drawable then we should just handle any other touch listeners that have been applied to the EditText instead.

And lastly, here is our very simple TextWatcher:

@Override
public final void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
if (isFocused()) {
setClearIconVisible(s.length() > 0);
}
}

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

@Override
public void afterTextChanged(Editable s) {
}

And that’s it. You now have a fully-functional edit text with a clearable ‘x’ control that you can include in your layouts instead of a normal edit text. You can find an example of the code with the full implementation of ClearableEditText and a working example for a login screen on GitHub here. If you are also using AutoCompleteTextView you can also make an exact copy of the ClearableEditText and just change the class declaration e.g.

public class ClearableAutoCompleteTextView extends AppCompatAutoCompleteTextView implements View.OnTouchListener, View.OnFocusChangeListener, TextWatcher {

We’re hiring!

Do you want to join the Depop family? We’re looking for Android engineers who can help change the way people buy and sell from their mobile using the latest android UI, coding and testing practices.

Find our vacancies here and get in touch with a copy of your CV.