10.1A: Creating a custom view from a View subclass
Contents:
- What you should already KNOW
- What you will LEARN
- What you will DO
- App overview
- Task 1. Customize an EditText view
- Solution code
- Summary
- Related concept
- Learn more
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
subclassEditText
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.
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 theEditText
. - Use a text listener to show the
drawable
only when text is entered into theEditText
. - 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 that appears when the user enters text.
A black version 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.)
- Create an app named
CustomEditText
using the Empty Activity template. Make sure that Generate Layout File is selected so that theactivity_main.xml
layout file is generated. - Edit the
build.gradle (Module: app)
file. Change the minimum SDK version to 17, so that you can support RTL languages and placedrawables
in either the left or right position inEditText
views:minSdkVersion 17
- Right-click the
drawable/
folder and choose New > Vector Asset. Click the Android icon and choose the clear (X) icon. Its name changes toic_clear_black_24dp
. Click Next and Finish. 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
.In
activity_main.xml
, change the "Hello World"TextView
to anEditText
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"
Extract the string resource for "Last name" to
last_name
.- Run the app. It displays an
EditText
field for entering text (a last name), and uses thetextCapSentences
attribute to capitalize the first letter.
1.2 Add a subclass that extends EditText
- Create a new Java class called
EditTextWithClear
with the superclass set toandroid.support.v7.widget.AppCompatEditText
.AppCompatEditText
is anEditText
subclass that supports compatible features on older version of the Android platform. - 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. - 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.
- Define a member variable for the drawable (the X button image).
Drawable mClearButtonImage;
Create a
private
method calledinit()
, with no parameters, that initializes the member variable to thedrawable
resourceic_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.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:
Open
EditTextWithClear
and create twoprivate
methods with no parameters,showClearButton()
andhideClearButton()
. In these methods, usesetCompoundDrawablesRelativeWithIntrinsicBounds()
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()
, thesetCompoundDrawablesRelativeWithIntrinsicBounds()
method sets thedrawable
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 adrawable
. InhideClearButton()
, thesetCompoundDrawablesRelativeWithIntrinsicBounds()
method replaces thedrawable
withnull
in the end position.The
setCompoundDrawablesRelativeWithIntrinsicBounds()
method returns the exact size of thedrawable
. This method requires a minimum Android API level 17 or newer. Be sure to edit yourbuild.gradle (Module: app)
file to useminSdkVersion 17
.In
EditTextWithClear
, add aTextWatcher()
inside theinit()
method, replacing the secondTODO
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
- 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()
- In the code shown above, enter new T inside the parentheses:
addTextChangedListener(new T)
Choose the TextWatcher{...} suggestion that appears. Android Studio creates the
beforeTextChanged()
,onTextChanged()
, andafterTextChanged()
methods inside theaddTextChangedListener()
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) { } });
- In the
onTextChanged()
method, call theshowClearButton()
method for showing the clear (X) button. You implement onlyonTextChanged()
in this practical, so leavebeforeTextChanged()
andafterTextChanged()
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.
In the figure above:
- 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.
- 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:
- In the
init()
method, replace the firstTODO
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; } });
In the
onTouch()
method, replace the singlereturn 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 usesgetCompoundDrawablesRelative()
to return thedrawable
at the end of the text[2]
. If nodrawable
is present, the expression returnsnull
. The code executes only if that location is notnull
—which means that the clear (X) button is in that location. Otherwise, the code returnsfalse
.- Location 0: Start of text (set to
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:
- Use
getLayoutDirection()
to get the current layout direction. Use theMotionEvent
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 theonTouch()
method, replace the firstTODO
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; } }
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. OnACTION_UP
, you want to switch back to the default version of the button, clear the text, and hide the button. In theonTouch()
method, replace the secondTODO
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 returntrue
. Otherwise the code returnsfalse
.
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:
In
activity_main.xml
, change theEditText
tag for themy_edit_text
element tocom.example.android.customedittext.EditTextWithClear
.The
EditTextWithClear
class inherits the attributes defined for the originalEditText
, 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.
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:
- Open the
strings.xml
file, and click the Open editor link in the top right corner to open the Translations Editor. 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.
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.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.
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.
- 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.
- 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. 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.
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 asEditText
, add a new class that extends the subclass (such asEditText
), 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.
Related concept
The related concept documentation is Custom views.
Learn more
Android developer documentation:
- Creating Custom Views
- Custom Components
View
- Input Events
onDraw()
Canvas
drawCircle()
drawText()
Paint
Video: