10.1A: Creating a custom view from a View subclass

Contents:

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:

  • Create and run apps in Android Studio.
  • Use the Layout Editor to create a UI.
  • Edit a layout in XML.
  • Use touch, text, and click listeners in your code.

What you will LEARN

You will learn how to:

  • Extend the View subclass EditText to create a custom text-editing view.
  • Use listeners to handle user interaction with the custom view.
  • Use a custom view in a layout.

What you will DO

  • Add a new class that extends the EditText class to create a custom view.
  • Use listeners to provide the custom view's behavior.
  • Add the custom view to a layout.

App overview

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.  The CustomEditText app shows a custom EditText view with an X on the right side for clearing the text.

Task 1. Customize an EditText view

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:

  • Create an app with an EditText view as a placeholder.
  • Add layout attributes to position the view, and to support right-to-left (RTL) languages for text input.
  • Extend the EditText class to create a custom view.
  • Initialize the custom view with a drawable that appears at the end of the EditText.
  • Use a text listener to show the drawable only when text is entered into the EditText.
  • Use a touch listener to clear the text if the user taps the drawable.
  • Replace the placeholder EditText with the custom view in the layout.

1.1 Create an app with an EditText view

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

  • An opaque version  Opaque icon that appears when the user enters text.

  • A black version  Black icon that appears while the user is tapping the X.

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
    
  3. 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.
  4. 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.  Add the clear icon with 50% opacity for the

  5. 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"
  6. Extract the string resource for "Last name" to last_name.

  7. Run the app. It displays an EditText field for entering text (a last name), and uses the textCapSentences attribute to capitalize the first letter.  The CustomEditText app before implementing custom view.

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:

  • AppCompatEditText(context:Context): Required for creating an instance of a view programmatically.
  • AppCompatEditText(context:Context, attrs:AttributeSet): Required to inflate the view from an XML layout and apply XML attributes.
  • AppCompatEditText(context:Context, attrs:AttributeSet, defStyleAttr:int): Required to apply a default style to all UI elements without having to specify it in each layout file.

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;
    
  2. 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.

  3. 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. For more about supporting RTL languages, see the lesson on localization.

    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.

  2. 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
    
  3. 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()
    
  4. In the code shown above, enter new T inside the parentheses:
     addTextChangedListener(new T)
    
  5. 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) {
         }
     });
    
  6. 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:

  • Clear the text from the field if the user taps the clear (X) button.
  • Render the clear (X) button as opaque before the user taps it, and black while the user is tapping it.

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.  Locating the start of the button in an LTR or RTL layout.

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;
         }
     });
    
  2. 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():

    • Location 0: Start of text (set to null).
    • Location 1: Top of text (set to null).
    • Location 2: End of text (set to mClearButtonImage).
    • Location 3: Bottom of text (set to null).

    In the above step, you use 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.

  • For an LTR language, the clear (X) button starts on the right side of the field. Any touch occurring after the start point is a touch on the button itself.
  • For an RTL language, the clear (X) button ends on the left side of the field. Any touch occurring before the endpoint is a touch on the button itself.

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;
               }
           }
    
  2. 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.

  2. Run the app. Enter text, and then tap the clear (X) button to clear the text.  The CustomEditText app shows a custom EditText view with an X to the right of the text for clearing 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.

  3. 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.

  4. 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.  Globe icon for the Languages & input choice

  5. 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.

  6. 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.
  7. 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.
  8. 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.  The CustomEditText app with an RTL language (Hebrew).

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

Solution code

Android Studio project: CustomEditText

Summary

  • To create a custom view that inherits the look and behavior of a View subclass such as EditText, add a new class that extends the subclass (such as EditText), and make adjustments by overriding some of the subclass methods.
  • Add listeners such as View.OnClickListener to the custom view to define the view's interactive behavior.
  • Add the custom view to an XML layout file with attributes to define the view's appearance, as you would with other UI elements.

Tip: View the different methods of the View subclasses, such as TextView, Button, and ImageView, to see how you can modify a View subclass by overriding these methods. For example, you can override the setCompoundDrawablesRelativeWithIntrinsicBounds() method of a TextView (or an EditText, which is a subclass of TextView) to set a drawable to appear to the start of, above, to the end of, and below the text.

The related concept documentation is Custom views.

Learn more

Android developer documentation:

Video:

results matching ""

    No results matching ""