This codelab is part of the Advanced Android Development training course, developed by the Google Developers Training team. You will get the most value out of this course if you work through the codelabs in sequence.

For complete details about the course, see the Advanced Android Development overview.

Introduction

Android offers a large set of View subclasses, such as Button, TextView, EditText, ImageView, CheckBox, or RadioButton. You can use these subclasses to construct a UI that enables user interaction and displays information in your app. If the View subclasses don't meet your needs, you can create a custom view that does.

After you create a custom view, you can add it to different layouts in the same way you would add a TextView or Button. This lesson shows you how to create and use custom views based on View subclasses.

What you should already know

You should be able to:

What you'll learn

What you'll do

The CustomEditText app demonstrates how to extend EditText to make a custom text-editing view. The custom view includes a clear (X) button for clearing text. After the custom view is created, you can use multiple versions of it in layouts, applying different EditText attributes as needed.

In this task, you create an app with a customized EditText view that includes a clear (X) button on the right side of the EditText. The user can tap the X to clear the text. Specifically, you will:

1.1 Create an app with an EditText view

In this step, you add two drawables for the clear (X) button:

You also change a TextView to an EditText with attributes for controlling its appearance. If the layout direction is set to a right-to-left (RTL) language, these attributes change the direction in which the user enters text. (For more about supporting RTL languages, see the lesson on localization.)

  1. Create an app named CustomEditText using the Empty Activity template. Make sure that Generate Layout File is selected so that the activity_main.xml layout file is generated.
  2. Edit the build.gradle (Module: app) file. Change the minimum SDK version to 17, so that you can support RTL languages and place drawables in either the left or right position in EditText views:
minSdkVersion 17
  1. Right-click the drawable/ folder and choose New > Vector Asset. Click the Android icon and choose the clear (X) icon. Its name changes to ic_clear_black_24dp. Click Next and Finish.
  2. Repeat step 3, choosing the clear (X) icon again, but this time drag the Opacity slider to 50% as shown below. Change the icon's name to ic_clear_opaque_24dp.

  1. In activity_main.xml, change the "Hello World" TextView to an EditText with the following attributes:

Attribute

Value

android:id

"@+id/my_edit_text"

android:layout_width

"wrap_content"

android:layout_height

"wrap_content"

android:textAppearance

"@style/Base.TextAppearance.AppCompat.Display1"

android:inputType

"textCapSentences"

android:layout_gravity

"start"

android:textAlignment

"viewStart"

app:layout_constraintBottom_toBottomOf

"parent"

app:layout_constraintLeft_toLeftOf

"parent"

app:layout_constraintRight_toRightOf

"parent"

app:layout_constraintTop_toTopOf

"parent"

android:hint

"Last name"

  1. Extract the string resource for "Last name" to last_name.
  2. Run the app. It displays an EditText field for entering text (a last name), and uses the textCapSentences attribute to capitalize the first letter.

1.2 Add a subclass that extends EditText

  1. Create a new Java class called EditTextWithClear with the superclass set to android.support.v7.widget.AppCompatEditText. AppCompatEditText is an EditText subclass that supports compatible features on older version of the Android platform.
  2. The editor opens EditTextWithClear.java. A red bulb appears a few moments after you click the class definition because the class is not complete—it needs constructors.
  3. Click the red bulb and select Create constructor matching super. Select all three constructors in the popup menu, and click OK.

The three constructors are:

1.3 Initialize the custom view

Create a helper method that initializes the view, and call that method from each constructor. That way, you don't have to repeat the same code in each constructor.

  1. Define a member variable for the drawable (the X button image).
Drawable mClearButtonImage;
  1. Create a private method called init(), with no parameters, that initializes the member variable to the drawable resource ic_clear_opaque_24dp.
private void init() {
    mClearButtonImage = ResourcesCompat.getDrawable(getResources(),
                             R.drawable.ic_clear_opaque_24dp, null);
    // TODO: If the clear (X) button is tapped, clear the text.
    // TODO: If the text changes, show or hide the clear (X) button.
}

The code includes two TODO comments for upcoming steps of this task.

  1. Add the init() method call to each constructor:
public EditTextWithClear(Context context) {
        super(context);
        init();
}
public EditTextWithClear(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
}
public EditTextWithClear(Context context, 
                             AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
}

1.4 Show or hide the X button

If the user enters text, the EditTextWithClear custom view shows the clear (X) button. If there is no text in the field, the EditTextWithClear custom view hides the clear (X) button.

To show or hide the button, use the TextWatcher interface, whose methods are called if the text changes. Follow these steps:

  1. Open EditTextWithClear and create two private methods with no parameters, showClearButton() and hideClearButton(). In these methods, use setCompoundDrawablesRelativeWithIntrinsicBounds() to show or hide the clear (X) button.
/**
* Shows the clear (X) button.
*/
private void showClearButton() {
        setCompoundDrawablesRelativeWithIntrinsicBounds
                (null,                      // Start of text.
                        null,               // Above text.
                        mClearButtonImage,  // End of text.
                        null);              // Below text.
}

/**
* Hides the clear button.
*/
private void hideClearButton() {
        setCompoundDrawablesRelativeWithIntrinsicBounds
                (null,             // Start of text.
                        null,      // Above text.
                        null,      // End of text.
                        null);     // Below text.
}

In showClearButton(), the setCompoundDrawablesRelativeWithIntrinsicBounds() method sets the drawable mClearButtonImage to the end of the text. The method accommodates right-to-left (RTL) languages by using the arguments as "start" and "end" positions rather than "left" and "right" positions.

Use null for positions that should not show a drawable. In hideClearButton(), the setCompoundDrawablesRelativeWithIntrinsicBounds() method replaces the drawable with null in the end position.

The setCompoundDrawablesRelativeWithIntrinsicBounds() method returns the exact size of the drawable. This method requires a minimum Android API level 17 or newer. Be sure to edit your build.gradle (Module: app) file to use minSdkVersion 17.

  1. In EditTextWithClear, add a TextWatcher() inside the init() method, replacing the second TODO comment (TODO: If the text changes, show or hide the clear (X) button). Let Android Studio do the work for you: start by entering addText:
// If the text changes, show or hide the clear (X) button.
addText
  1. After entering addText, choose the suggestion that appears for addTextChangedListener(TextWatcher watcher). The code changes to the following, and a red bulb appears as a warning.
addTextChangedListener()
  1. In the code shown above, enter new T inside the parentheses:
addTextChangedListener(new T)
  1. Choose the TextWatcher{...} suggestion that appears. Android Studio creates the beforeTextChanged(), onTextChanged(), and afterTextChanged() methods inside the addTextChangedListener() method, as shown in the code below:
addTextChangedListener(new TextWatcher() {
    @Override
    public void beforeTextChanged(CharSequence s, int start, 
                                            int count, int after) {
    }

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

    @Override
    public void afterTextChanged(Editable s) {
    }
});
  1. In the onTextChanged() method, call the showClearButton() method for showing the clear (X) button. You implement only onTextChanged() in this practical, so leave beforeTextChanged() and afterTextChanged() alone, or just add comments to them.
public void onTextChanged(CharSequence s, int start, 
                                            int before, int count) {
    showClearButton();
}

1.5 Add touch and text listeners

Other behaviors of the EditTextWithClear custom view are to:

To detect the tap and clear the text, use the View.OnTouchListener interface. The interface's onTouch() method is called when a touch event occurs with the button.

Tip: To learn more about event listeners, see Input Events.

You should design the EditTextWithClear class to be useful in both left-to-right (LTR) and right-to-left (RTL) language layouts. However, the button is on the right side in an LTR layout, and on the left side of an RTL layout. The code needs to detect whether the touch occurred on the button itself. It checks to see if the touch occurred after the start location of the button. The start location of the button is different in an RTL layout than it is in an LTR layout, as shown in the figure.

In the figure above:

  1. The start location of the button in an LTR layout. Moving to the right, the touch must occur after this location on the screen and before the right edge.
  2. The start location of the button in an RTL layout. Moving to the left, the touch must occur after this location on the screen and before the left edge.

Tip: To learn more about reporting finger movement events, see MotionEvent.

Follow these steps to use the View.OnTouchListener interface:

  1. In the init() method, replace the first TODO comment (TODO: If the clear (X) button is tapped, clear the text) with the following code. If the clear (X) button is visible, this code sets a touch listener that responds to events inside the bounds of the button.
// If the clear (X) button is tapped, clear the text.
setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        return false;
    }
});
  1. In the onTouch() method, replace the single return false statement with the following:
    if ((getCompoundDrawablesRelative()[2] != null)) {
        float clearButtonStart; // Used for LTR languages
        float clearButtonEnd;  // Used for RTL languages
        boolean isClearButtonClicked = false;
        // TODO: Detect the touch in RTL or LTR layout direction.
        // TODO: Check for actions if the button is tapped.
    }
    return false;

In the previous step, you set the location of the clear (X) button using setCompoundDrawablesRelativeWithIntrinsicBounds():

In the previous step, you used the getCompoundDrawablesRelative()[2] expression, which uses getCompoundDrawablesRelative() to return the drawable at the end of the text [2]. If no drawable is present, the expression returns null. The code executes only if that location is not null—which means that the clear (X) button is in that location. Otherwise, the code returns false.

1.6 Recognize the user's tap

To recognize the user's tap on the clear (X) button, you need to get the intrinsic bounds of the button and compare it with the touch event.

Follow these steps:

  1. Use getLayoutDirection() to get the current layout direction. Use the MotionEvent getX() method to determine whether the touch occurred after the start of the button in an LTR layout, or before the end of the button in an RTL layout. In the onTouch() method, replace the first TODO comment (TODO: Detect the touch in RTL or LTR layout direction):
      // Detect the touch in RTL or LTR layout direction.
      if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
          // If RTL, get the end of the button on the left side.
          clearButtonEnd = mClearButtonImage
                                .getIntrinsicWidth() + getPaddingStart();
          // If the touch occurred before the end of the button,
          // set isClearButtonClicked to true.
          if (event.getX() < clearButtonEnd) {
              isClearButtonClicked = true;
          }
      } else {
          // Layout is LTR.
          // Get the start of the button on the right side.
          clearButtonStart = (getWidth() - getPaddingEnd()
                                - mClearButtonImage.getIntrinsicWidth());
          // If the touch occurred after the start of the button,
          // set isClearButtonClicked to true.
          if (event.getX() > clearButtonStart) {
              isClearButtonClicked = true;
          }
      }
  1. Check for actions if the clear (X) button is tapped. On ACTION_DOWN, you want to show the black version of the button as a highlight. On ACTION_UP, you want to switch back to the default version of the button, clear the text, and hide the button. In the onTouch() method, replace the second TODO comment (TODO: Check for actions if the button is tapped):
      // Check for actions if the button is tapped.
      if (isClearButtonClicked) {
          // Check for ACTION_DOWN (always occurs before ACTION_UP).
          if (event.getAction() == MotionEvent.ACTION_DOWN) {
              // Switch to the black version of clear button.
              mClearButtonImage = 
                    ResourcesCompat.getDrawable(getResources(),
                    R.drawable.ic_clear_black_24dp, null);
              showClearButton();
          }
          // Check for ACTION_UP.
          if (event.getAction() == MotionEvent.ACTION_UP) {
              // Switch to the opaque version of clear button.
              mClearButtonImage =
                    ResourcesCompat.getDrawable(getResources(),
                    R.drawable.ic_clear_opaque_24dp, null);
              // Clear the text and hide the clear button.
              getText().clear();
              hideClearButton();
              return true;
          }
      } else {
          return false;
      }

The first touch event is ACTION_DOWN. Use it to check if the clear (X) button is touched (ACTION_DOWN). If it is, switch the clear button to the black version.

The second touch event, ACTION_UP occurs when the gesture is finished. Your code can then clear the text, hide the clear (X) button, and return true. Otherwise the code returns false.

1.7 Change the EditText view to the custom view

The EditTextWithClear class is now ready to be used in place of the EditText view in the layout:

  1. In activity_main.xml, change the EditText tag for the my_edit_text element to com.example.android.customedittext.EditTextWithClear.

The EditTextWithClear class inherits the attributes defined for the original EditText, so there is no need to change any of them for this step.

If you see the message "classes missing" in the preview, click the link to rebuild the project.

  1. Run the app. Enter text, and then tap the clear (X) button to clear the text.

1.8 Run the app with an RTL language

To test an RTL language, you can add Hebrew in the Translations Editor, switch the device or emulator to Hebrew, and run the app. Follow these steps:

  1. Open the strings.xml file, and click the Open editor link in the top right corner to open the Translations Editor.
  2. Click the globe button in the top left corner of the Translations Editor pane, and select Hebrew (iw) in Israel (IL) in the dropdown menu.

After you choose a language, a new column with blank entries appears in the Translations Editor for that language, and the keys that have not yet been translated appear in red.

  1. Enter the Hebrew translation of "Last name" for the last_name key by selecting the key's cell in the column for the language (Hebrew), and entering the translation in the Translation field at the bottom of the pane. (For instructions on using the Translations Editor, see the chapter on localization.) When finished, close the Translations Editor.
  2. On your device or emulator, find the Languages & input settings in the Settings app. For devices using Android Oreo (8) or newer, the Languages & input choice is under System.

Be sure to remember the globe icon for the Languages & input choice, so that you can find it again if you switch to a language you do not understand.


  1. Choose Languages (or Language on Android 6 or older), which is easy to find because it is the first choice on the Languages & input screen.
  2. For devices and emulators running Android 6 or older, select עִברִית for Hebrew. For devices and emulators running Android 7 or newer, click Add a language, select עִברִית, select ( עברית (ישראל for the locale, and then use the move icon on the right side of the Language preferences screen to drag the language to the top of the list.
  3. Run the app. The EditTextWithClear element should be reversed for an RTL language, with the clear (X) button on the left side, as shown below.
  4. Put a finger on the clear (X) button, or if you're using a mouse, click and hold on the clear button. Then drag away from the clear button. The button changes from gray to black, indicating that it is still touched.

  1. To change back from Hebrew to English, repeat Steps 4-6 above with the selection English for language and United States for locale.

Android Studio project: CustomEditText

The related concept documentation is 10.1 Custom views.

Android developer documentation:

Videos:

To see all the codelabs in the Advanced Android Development training course, visit the Advanced Android Development codelabs landing page.