I'm working on a application which asks for OTP when user want to reset his password for which I need a text like the one in attached Image... What I thought to proceed with is individual editText for each of the letter, All of them arranged in linear layout of horizontal orientation with some margin and max length as 1 so only one letter can be entered in each editText... Is that a right Approach?? Any Suggestions??
-
Yes that's the right approach! – Paresh Mayani Aug 10 '16 at 11:47
-
only one letter per EditText, textChangeListner for each. Once letter is entered, focus on the next EditText – Sreehari Aug 10 '16 at 12:06
-
3I believe this question has been answered, but apps like Uber use a single EditText for the OTP. Am I missing something here ? N EditTexts implies N views to handle focus changes. I also reckon managing the soft keyboard must be a lot more challenging with multiple EditTexts if UX is a real priority. – user1841702 Aug 10 '16 at 12:44
-
You can get some hint from this [answer](https://stackoverflow.com/a/51178601/8387091). – Kaishu Sahu Jul 04 '18 at 19:03
25 Answers
After all of these answers, I didn't find what I wanted as considering the UI/UX, the deletion of element was flawed in such a way that to go back to previous EditText
, current EditText should not be empty.
Here's the solution I've implemented in Kotlin which works for Deletion by the Delete Key, pressed on the keyboard. Also, the delete function is implemented as such that when the current EditText
is empty and Delete key is pressed, it switches back to previous EditText
and delete its element also.
Call the functions as such:
//GenericTextWatcher here works only for moving to next EditText when a number is entered //first parameter is the current EditText and second parameter is next EditText editText1.addTextChangedListener(GenericTextWatcher(editText1, editText2)) editText2.addTextChangedListener(GenericTextWatcher(editText2, editText3)) editText3.addTextChangedListener(GenericTextWatcher(editText3, editText4)) editText4.addTextChangedListener(GenericTextWatcher(editText4, null)) //GenericKeyEvent here works for deleting the element and to switch back to previous EditText //first parameter is the current EditText and second parameter is previous EditText editText1.setOnKeyListener(GenericKeyEvent(editText1, null)) editText2.setOnKeyListener(GenericKeyEvent(editText2, editText1)) editText3.setOnKeyListener(GenericKeyEvent(editText3, editText2)) editText4.setOnKeyListener(GenericKeyEvent(editText4,editText3))
Now, paste these two classes in your current class
class GenericKeyEvent internal constructor(private val currentView: EditText, private val previousView: EditText?) : View.OnKeyListener{ override fun onKey(p0: View?, keyCode: Int, event: KeyEvent?): Boolean { if(event!!.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_DEL && currentView.id != R.id.editText1 && currentView.text.isEmpty()) { //If current is empty then previous EditText's number will also be deleted previousView!!.text = null previousView.requestFocus() return true } return false } } class GenericTextWatcher internal constructor(private val currentView: View, private val nextView: View?) : TextWatcher { override fun afterTextChanged(editable: Editable) { // TODO Auto-generated method stub val text = editable.toString() when (currentView.id) { R.id.editText1 -> if (text.length == 1) nextView!!.requestFocus() R.id.editText2 -> if (text.length == 1) nextView!!.requestFocus() R.id.editText3 -> if (text.length == 1) nextView!!.requestFocus() //You can use EditText4 same as above to hide the keyboard } } override fun beforeTextChanged( arg0: CharSequence, arg1: Int, arg2: Int, arg3: Int ) { // TODO Auto-generated method stub } override fun onTextChanged( arg0: CharSequence, arg1: Int, arg2: Int, arg3: Int ) { // TODO Auto-generated method stub } }
Further, to disable the visible cursor, you can either use android:cursorVisible="false"
in your EditText
tag in the Layout or can use the java function setCursorVisible(false)
.
Edit: I'm using stock widget EditTexts
so if you want to display a box around them, just create a drawable layout and set it as background of EditTexts
and give them a padding of 5dp. This will create a box and will make it look cooler.

- 5,953
- 2
- 26
- 50
-
2
-
But if the user pastes the OTP then all of it goes in the first box only rather than spreading in all of them...Is there any way to resolve it? – Shaurya Goyal Sep 28 '21 at 14:02
-
1@ShauryaGoyal Intercept the pasted text in the `OnTextChangedListener` as mentioned in [this answer](https://stackoverflow.com/a/36458328/8244632) and programmatically set respective digit of the pasted OTP to the respective `EditText`. – Lalit Fauzdar Sep 28 '21 at 15:09
-
Could you please add it in your code, as I am very new to all this, Please. – Shaurya Goyal Sep 29 '21 at 00:53
-
@Lalit Fauzdar Please tell me how can I implement the pasting OTP method? – Shaurya Goyal Oct 01 '21 at 18:21
-
@ShauryaGoyal I've already told you the solution, you just have to implement it. It's not one task, it's a series of sub-tasks achieving the output you require, setting the listener, reading the pasted code in first EditText and parsing the pasted value into single digits and setting text of EditText programmatically. Everything related to this has already been asked here, you just have to find the answers and implement it. – Lalit Fauzdar Oct 01 '21 at 18:30
-
-
-
@Lalit Fauzdar it's working fine when i'm writing number by number, but when i try to past the code on the first edit text only that edit text is changed. i want to past the rest of the code into the other edit text as well . and thank you – kamal douma Nov 25 '22 at 15:16
-
@kamaldouma That is something you've to configure, see the above comment posted by me, it lists all what you've to do for that. – Lalit Fauzdar Nov 25 '22 at 17:22
-
Delete autofocus to previous view is not working for me inside of fragment. (`val editText1 = view.findViewById
(R.id.et_1)` ... ) – C.F.G Jan 24 '23 at 12:12
OtpEditText.java (Custom EditText):
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.text.Editable;
import android.util.AttributeSet;
import android.view.ActionMode;
import android.view.View;
import androidx.appcompat.widget.AppCompatEditText;
public class OtpEditText extends AppCompatEditText {
private float mSpace = 24; //24 dp by default, space between the lines
private float mNumChars = 4;
private float mLineSpacing = 8; //8dp by default, height of the text from our lines
private int mMaxLength = 4;
private float mLineStroke = 2;
private Paint mLinesPaint;
private OnClickListener mClickListener;
public OtpEditText(Context context) {
super(context);
}
public OtpEditText(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public OtpEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
float multi = context.getResources().getDisplayMetrics().density;
mLineStroke = multi * mLineStroke;
mLinesPaint = new Paint(getPaint());
mLinesPaint.setStrokeWidth(mLineStroke);
mLinesPaint.setColor(getResources().getColor(R.color.colorPrimaryDark));
setBackgroundResource(0);
mSpace = multi * mSpace; //convert to pixels for our density
mLineSpacing = multi * mLineSpacing; //convert to pixels for our density
mNumChars = mMaxLength;
super.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// When tapped, move cursor to end of text.
setSelection(getText().length());
if (mClickListener != null) {
mClickListener.onClick(v);
}
}
});
}
@Override
public void setOnClickListener(OnClickListener l) {
mClickListener = l;
}
@Override
public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
throw new RuntimeException("setCustomSelectionActionModeCallback() not supported.");
}
@Override
protected void onDraw(Canvas canvas) {
int availableWidth = getWidth() - getPaddingRight() - getPaddingLeft();
float mCharSize;
if (mSpace < 0) {
mCharSize = (availableWidth / (mNumChars * 2 - 1));
} else {
mCharSize = (availableWidth - (mSpace * (mNumChars - 1))) / mNumChars;
}
int startX = getPaddingLeft();
int bottom = getHeight() - getPaddingBottom();
//Text Width
Editable text = getText();
int textLength = text.length();
float[] textWidths = new float[textLength];
getPaint().getTextWidths(getText(), 0, textLength, textWidths);
for (int i = 0; i < mNumChars; i++) {
canvas.drawLine(startX, bottom, startX + mCharSize, bottom, mLinesPaint);
if (getText().length() > i) {
float middle = startX + mCharSize / 2;
canvas.drawText(text, i, i + 1, middle - textWidths[0] / 2, bottom - mLineSpacing, getPaint());
}
if (mSpace < 0) {
startX += mCharSize * 2;
} else {
startX += mCharSize + mSpace;
}
}
}
}
Use this customised EditText in your XML like below:
<OtpEditText
android:id="@+id/et_otp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:cursorVisible="false"
android:digits="1234567890"
android:inputType="number"
android:maxLength="4"
android:textIsSelectable="false"
android:textSize="20sp"/>
Reference:
Article: https://medium.com/@ali.muzaffar/building-a-pinentryedittext-in-android-5f2eddcae5d3
Sample Code: https://gist.github.com/alphamu/0d3055e0233c5749b8d6

- 1,237
- 11
- 19
-
-
5To change text color use `getPaint().setColor(Color.parseColor("#FFFFFF"))`. – grrigore Oct 30 '19 at 09:30
-
For input type number password this widget not showing stars instead of digits. Can you please suggest how to achieve this? – Sachin Mandhare Jan 15 '20 at 13:39
-
-
2
-
2`android:cursorVisible="true"`... It does not shows the cursor blinking.. Any help. tried with `android:textCursorDrawable="@drawable/color_cursor"` .... but no success – Aditya Patil Jun 05 '20 at 14:11
-
-
1
-
-
-
Custom blinking cursor in method onDraw: `if (getText().length() == i && blinkOn && isFocused()) { canvas.drawLine(startX + mCharSize / 2, mLineStrokeSelected + dy, startX + mCharSize / 2, bottom - dy, mLinesPaint); }` and `private void blinking() { Handler handler = new Handler(); handler.postDelayed ({ @Override public void run() { blinkOn = !blinkOn; invalidate(); blinking(); } }, 500); }` – Aleksey Melnikov Sep 12 '21 at 19:40
You can try this, by making TextWatcher more Generic, so its easy to use and understand
Use below class:
public class GenericTextWatcher implements TextWatcher
{
private View view;
private GenericTextWatcher(View view)
{
this.view = view;
}
@Override
public void afterTextChanged(Editable editable) {
// TODO Auto-generated method stub
String text = editable.toString();
switch(view.getId())
{
case R.id.editText1:
if(text.length()==1)
et2.requestFocus();
break;
case R.id.editText2:
if(text.length()==1)
et3.requestFocus();
else if(text.length()==0)
et1.requestFocus();
break;
case R.id.editText3:
if(text.length()==1)
et4.requestFocus();
else if(text.length()==0)
et2.requestFocus();
break;
case R.id.editText4:
if(text.length()==0)
et3.requestFocus();
break;
}
}
@Override
public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
// TODO Auto-generated method stub
}
@Override
public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
// TODO Auto-generated method stub
}
}
How to use above class
et1.addTextChangedListener(new GenericTextWatcher(et1));
et2.addTextChangedListener(new GenericTextWatcher(et2));
et3.addTextChangedListener(new GenericTextWatcher(et3));
et4.addTextChangedListener(new GenericTextWatcher(et4));
Here et1,et2,et3 and et4 are your EditTexts, I know its bad naming convention as per Java Standard, but you can replace it with yours.
P.S You can find the xml design for this here GitHub some other, sample design xml for reference

- 2,631
- 1
- 19
- 22
-
-
31
-
-
-
2This has a drawback that is it doesn't take you to previous `EditText` unless you enter a value in the current `EditText` and then press the delete key. – Lalit Fauzdar Feb 28 '20 at 13:07
I implemented the following code based on other answers.
I wanted this code to be very simple, optimized and understandable for changes.
Don't use android:maxLength="1"
in your xml.
//package your package
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
public class PinActivity extends AppCompatActivity {
private EditText editText1, editText2, editText3, editText4;
private EditText[] editTexts;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pin);
editText1 = (EditText) findViewById(R.id.otpEdit1);
editText2 = (EditText) findViewById(R.id.otpEdit2);
editText3 = (EditText) findViewById(R.id.otpEdit3);
editText4 = (EditText) findViewById(R.id.otpEdit4);
editTexts = new EditText[]{editText1, editText2, editText3, editText4};
editText1.addTextChangedListener(new PinTextWatcher(0));
editText2.addTextChangedListener(new PinTextWatcher(1));
editText3.addTextChangedListener(new PinTextWatcher(2));
editText4.addTextChangedListener(new PinTextWatcher(3));
editText1.setOnKeyListener(new PinOnKeyListener(0));
editText2.setOnKeyListener(new PinOnKeyListener(1));
editText3.setOnKeyListener(new PinOnKeyListener(2));
editText4.setOnKeyListener(new PinOnKeyListener(3));
}
public class PinTextWatcher implements TextWatcher {
private int currentIndex;
private boolean isFirst = false, isLast = false;
private String newTypedString = "";
PinTextWatcher(int currentIndex) {
this.currentIndex = currentIndex;
if (currentIndex == 0)
this.isFirst = true;
else if (currentIndex == editTexts.length - 1)
this.isLast = true;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
newTypedString = s.subSequence(start, start + count).toString().trim();
}
@Override
public void afterTextChanged(Editable s) {
String text = newTypedString;
/* Detect paste event and set first char */
if (text.length() > 1)
text = String.valueOf(text.charAt(0)); // TODO: We can fill out other EditTexts
editTexts[currentIndex].removeTextChangedListener(this);
editTexts[currentIndex].setText(text);
editTexts[currentIndex].setSelection(text.length());
editTexts[currentIndex].addTextChangedListener(this);
if (text.length() == 1)
moveToNext();
else if (text.length() == 0)
moveToPrevious();
}
private void moveToNext() {
if (!isLast)
editTexts[currentIndex + 1].requestFocus();
if (isAllEditTextsFilled() && isLast) { // isLast is optional
editTexts[currentIndex].clearFocus();
hideKeyboard();
}
}
private void moveToPrevious() {
if (!isFirst)
editTexts[currentIndex - 1].requestFocus();
}
private boolean isAllEditTextsFilled() {
for (EditText editText : editTexts)
if (editText.getText().toString().trim().length() == 0)
return false;
return true;
}
private void hideKeyboard() {
if (getCurrentFocus() != null) {
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
}
}
}
public class PinOnKeyListener implements View.OnKeyListener {
private int currentIndex;
PinOnKeyListener(int currentIndex) {
this.currentIndex = currentIndex;
}
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN) {
if (editTexts[currentIndex].getText().toString().isEmpty() && currentIndex != 0)
editTexts[currentIndex - 1].requestFocus();
}
return false;
}
}
}

- 269
- 3
- 8
public class GenericTextWatcher implements TextWatcher {
private EditText etPrev;
private EditText etNext;
public GenericTextWatcher(EditText etNext, EditText etPrev) {
this.etPrev = etPrev;
this.etNext = etNext;
}
@Override
public void afterTextChanged(Editable editable) {
String text = editable.toString();
if (text.length() == 1)
etNext.requestFocus();
else if (text.length() == 0)
etPrev.requestFocus();
}
@Override
public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
}
@Override
public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
}
}
Next, we add the addTextChangedListener on each edittext.
e1.addTextChangedListener(new GenericTextWatcher(e2, e1))
e2.addTextChangedListener(new GenericTextWatcher(e3, e1))
e3.addTextChangedListener(new GenericTextWatcher(e4, e2))
e4.addTextChangedListener(new GenericTextWatcher(e5, e3))
e5.addTextChangedListener(new GenericTextWatcher(e6, e4))
e6.addTextChangedListener(new GenericTextWatcher(e6, e5))

- 921
- 2
- 12
- 23
-
1This just works without any issues. Stripped version of `TextWatcher` just works fine! – sud007 Nov 01 '20 at 05:55
-
Shortest! a tiny issue: When all fields are filled, delete a middle ET then click on it and press delete key you'll see that the delete key will not work. – C.F.G Jan 24 '23 at 15:33
Solution with Insertion and deletion support (ViewBinding)
private class GenericTextWatcher implements TextWatcher {
private EditText currentView;
private EditText nextView;
private GenericTextWatcher(EditText currentView, EditText nextView) {
this.currentView = currentView;
this.nextView = nextView;
}
@Override
public void afterTextChanged(Editable editable) {
// TODO Auto-generated method stub
String text = editable.toString();
if (nextView != null && text.length() == 1) {
nextView.requestFocus();
}
if(text.length() >1){
currentView.setText(String.valueOf(text.charAt(text.length() - 1)));
currentView.setSelection(1);
}
}
@Override
public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
// TODO Auto-generated method stub
}
@Override
public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
// TODO Auto-generated method stub
}
}
private class GenericKeyEvent implements View.OnKeyListener {
private EditText currentView;
private EditText previousView;
public GenericKeyEvent(EditText currentView, EditText previousView) {
this.currentView = currentView;
this.previousView = previousView;
}
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_DEL && currentView.getText().toString().isEmpty()) {
if (previousView != null) {
previousView.requestFocus();
}
return true;
}
return false;
}
}
private void attachTextWatchers() {
binding.editText1.addTextChangedListener(new GenericTextWatcher(binding.editText1, binding.editText2));
binding.editText2.addTextChangedListener(new GenericTextWatcher(binding.editText2, binding.editText3));
binding.editText3.addTextChangedListener(new GenericTextWatcher(binding.editText3, binding.editText4));
binding.editText4.addTextChangedListener(new GenericTextWatcher(binding.editText4, null));
binding.editText2.setOnKeyListener(new GenericKeyEvent(binding.editText2, binding.editText1));
binding.editText3.setOnKeyListener(new GenericKeyEvent(binding.editText3, binding.editText2));
binding.editText4.setOnKeyListener(new GenericKeyEvent(binding.editText4, binding.editText3));
}

- 3,019
- 1
- 25
- 54
-
This is probably the best answer here. It has 2 important functionalities. 1. Deleting works more elegantaly. It deletes the digit on that edit that and does not immediately moves to previous edit text. You have to again press delete. 2. You can replace a digit on an edit text without deleting it per se. – Khay Jan 02 '23 at 18:31
With Kotlin you can use this method:
fun configOtpEditText(vararg etList: EditText) {
val afterTextChanged = { index: Int, e: Editable? ->
val view = etList[index]
val text = e.toString()
when (view.id) {
// first text changed
etList[0].id -> {
if (text.isNotEmpty()) etList[index + 1].requestFocus()
}
// las text changed
etList[etList.size - 1].id -> {
if (text.isEmpty()) etList[index - 1].requestFocus()
}
// middle text changes
else -> {
if (text.isNotEmpty()) etList[index + 1].requestFocus()
else etList[index - 1].requestFocus()
}
}
false
}
etList.forEachIndexed { index, editText ->
editText.doAfterTextChanged { afterTextChanged(index, it) }
}
}
To config as many EditText`s as you need like this:
configOtpEditText(
binding.et1,
binding.et2,
binding.et3,
binding.et4,
binding.et5,
binding.et6
)

- 131
- 1
- 2
You can try this if you want to add some extra logic for delete the OTP, i create it based on the answer from A.R.
make sure you set maxlength = 2
for all edittext
public class GenericTextWatcher implements TextWatcher
{
private View view;
private GenericTextWatcher(View view)
{
this.view = view;
}
@Override
public void afterTextChanged(Editable editable) {
// TODO Auto-generated method stub
String text = editable.toString();
switch (view.getId()) {
case R.id.etOTP1:
if (text.length() > 1) {
etOTP1.setText(String.valueOf(text.charAt(0)));
etOTP2.setText(String.valueOf(text.charAt(1)));
etOTP2.requestFocus();
etOTP2.setSelection(etOTP2.getText().length());
}
break;
case R.id.etOTP2:
if (text.length() > 1){
etOTP2.setText(String.valueOf(text.charAt(0)));
etOTP3.setText(String.valueOf(text.charAt(1)));
etOTP3.requestFocus();
etOTP3.setSelection(etOTP3.getText().length());
}
if (text.length() == 0){
etOTP1.requestFocus();
etOTP1.setSelection(etOTP1.getText().length());
}
break;
case R.id.etOTP3:
if (text.length() > 1){
etOTP3.setText(String.valueOf(text.charAt(0)));
etOTP4.setText(String.valueOf(text.charAt(1)));
etOTP4.requestFocus();
etOTP4.setSelection(etOTP4.getText().length());
}
if (text.length() == 0){
etOTP2.requestFocus();
etOTP2.setSelection(etOTP2.getText().length());
}
break;
case R.id.etOTP4:
if (text.length() == 0){
etOTP3.requestFocus();
etOTP3.setSelection(etOTP3.getText().length());
}
break;
}
}
@Override
public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
}
@Override
public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
}
}

- 91
- 1
- 3
-
I have done something similar to this, but added `FocusChange` on all of the EditTexts, if the EditText length is 0, focus on the previous EditText – Pierre Oct 15 '18 at 10:37
-
@Rian Thanks, You have done a great work . I changed only last edit text's xml to `maxlength =1` and it is working fine . – xaif Jun 13 '19 at 06:04
-
I created a simple library for this purpose. Check it out.
https://github.com/hexdecimal16/EditTextPin
Usage
- Layout XML:
<com.dhairytripathi.library.EditTextPin
android:id="@+id/editTextPin"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
- JAVA file:
EditTextPin editTextPin = findViewById(R.id.editTextPin);
String pin = editTextPin.getPin(); //To get the current entered pin
- Additional styling:
app:underlineColor="" <!-- To change underline color-->

- 197
- 2
- 14
-
-
-
-
@DhairyTripathi library is not working. getting error in xml file Class referenced in the layout file, com.dhairytripathi.library.EditTextPin, was not found in the project or the libraries – Himani Jun 23 '21 at 12:58
-
Use 4 different EditText.Use the below code to change the focus after subsequent entry.
private EditText editText1;
private EditText editText2;
private EditText editText3;
private EditText editText4;
editText1.addTextChangedListener(new TextWatcher() {
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (editText1.getText().toString().length() == 1) //size as per your requirement
{
editText2.requestFocus();
}
}
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
}
public void afterTextChanged(Editable s) {
}
});
editText2.addTextChangedListener(new TextWatcher() {
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (editText2.getText().toString().length() == 1) //size as per your requirement
{
editText3.requestFocus();
}
}
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
}
public void afterTextChanged(Editable s) {
}
});
and so on...
Concatenate the text from all the EditText.

- 1,450
- 12
- 16
i made a generic TextWatcher for 6 digit OTP:
public class GenericTextWatcher implements TextWatcher {
private View view;
GenericTextWatcher(View view) {
this.view = view;
}
@Override
public void afterTextChanged(Editable s) {
boolean allOtherFilled = false;
EditText nextEdit = null;
EditText previousEdit = null;
switch (view.getId()) {
case R.id.otp_et1:
allOtherFilled = otpEdit2.getText().length() == 1
&& otpEdit3.getText().length() == 1
&& otpEdit4.getText().length() == 1
&& otpEdit5.getText().length() == 1
&& otpEdit6.getText().length() == 1;
nextEdit = otpEdit2;
break;
case R.id.otp_et2:
allOtherFilled = otpEdit1.getText().length() == 1
&& otpEdit3.getText().length() == 1
&& otpEdit4.getText().length() == 1
&& otpEdit5.getText().length() == 1
&& otpEdit6.getText().length() == 1;
nextEdit = otpEdit3;
previousEdit = otpEdit1;
break;
case R.id.otp_et3:
allOtherFilled = otpEdit1.getText().length() == 1
&& otpEdit2.getText().length() == 1
&& otpEdit4.getText().length() == 1
&& otpEdit5.getText().length() == 1
&& otpEdit6.getText().length() == 1;
nextEdit = otpEdit4;
previousEdit = otpEdit2;
break;
case R.id.otp_et4:
allOtherFilled = otpEdit1.getText().length() == 1
&& otpEdit2.getText().length() == 1
&& otpEdit3.getText().length() == 1
&& otpEdit5.getText().length() == 1
&& otpEdit6.getText().length() == 1;
nextEdit = otpEdit5;
previousEdit = otpEdit3;
break;
case R.id.otp_et5:
allOtherFilled = otpEdit1.getText().length() == 1
&& otpEdit2.getText().length() == 1
&& otpEdit3.getText().length() == 1
&& otpEdit4.getText().length() == 1
&& otpEdit6.getText().length() == 1;
nextEdit = otpEdit6;
previousEdit = otpEdit4;
break;
case R.id.otp_et6:
allOtherFilled = otpEdit1.getText().length() == 1
&& otpEdit2.getText().length() == 1
&& otpEdit3.getText().length() == 1
&& otpEdit4.getText().length() == 1
&& otpEdit5.getText().length() == 1;
previousEdit = otpEdit5;
break;
}
if (s.length() == 1) {
if (allOtherFilled) {
//if next 2 edit texts are filled , enable the pay button
enableDisableButton(continueButton, true);
KeyboardUtils.hideKeyboard(LoginActivity.this, (EditText) view);
}
} else if (s.length() > 1) {
if (allOtherFilled) {
//if all next edit texts are filled , enable the pay button
enableDisableButton(continueButton, true);
KeyboardUtils.hideKeyboard(LoginActivity.this, (EditText) view);
} else if (nextEdit != null) {
if (nextEdit.getText().length() == 0) {
//if next edit is not filled, move to next edit and set the second digit
moveToNextEdit(nextEdit, (EditText) view);
} else {
//if any other edit is not filled, stay in current edit
enableDisableButton(continueButton, false);
stayOnCurrentEdit((EditText) view);
}
}
} else if (s.length() < 1) {
if (null != previousEdit)
moveToPreviousEdit(previousEdit);
enableDisableButton(continueButton, false);
}
}
@Override
public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
}
@Override
public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
}
private void stayOnCurrentEdit(EditText editText) {
editText.setText(editText.getText().toString().substring(0, 1));
editText.setSelection(editText.getText().length());
}
private void moveToPreviousEdit(EditText editText) {
editText.setSelection(editText.getText().length());
editText.requestFocus();
}
private void moveToNextEdit(EditText editText2, EditText editText1) {
editText2.setText(editText1.getText().toString().substring(1, 2));
editText2.requestFocus();
editText2.setSelection(editText2.getText().length());
editText1.setText(editText1.getText().toString().substring(0, 1));
}
}
You can add this textWatcher to all your edit texts like this :
this.otpEdit1.addTextChangedListener(new GenericTextWatcher(otpEdit1));

- 91
- 9
In Kotlin, You may use bellow like.. It is working fine
editText1.setOnKeyListener(View.OnKeyListener { v, keyCode, event ->
if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN) {
//Perform Code
if(editText1.hasFocus()){
editText1.setText("")
editText1.requestFocus()
return@OnKeyListener true
}
//return@OnKeyListener true
}
false
})
editText2.setOnKeyListener(View.OnKeyListener { v, keyCode, event ->
if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN) {
//Perform Code
if(editText2.hasFocus()){
//txtOTP_2.requestFocus()
editText2.setText("")
editText1.requestFocus()
return@OnKeyListener true
}
// return@OnKeyListener true
}
false
})
editText3.setOnKeyListener(View.OnKeyListener { v, keyCode, event ->
if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN) {
//Perform Code
if(editText3.hasFocus()){
//txtOTP_2.requestFocus()
editText3.setText("")
editText2.requestFocus()
return@OnKeyListener true
}
//return@OnKeyListener true
}
false
})
editText4.setOnKeyListener(View.OnKeyListener { v, keyCode, event ->
if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN) {
//Perform Code
if(editText4.hasFocus()){
// txtOTP_4.requestFocus()
editText4.setText("")
editText3.requestFocus()
return@OnKeyListener true
}
// return@OnKeyListener true
}
false
})

- 4,789
- 1
- 37
- 50
You can make a custom Editext and add it in your xml file, find below a custom class
public class CustomEntryEdittext extends LinearLayout {
public int entryCount = 0; //count of boxes to be created
private int currentIndex = 0;
private static int EDITTEXT_MAX_LENGTH = 1; //character size of each editext
private static int EDITTEXT_WIDTH = 40;
private static int EDITTEXT_TEXTSIZE = 20; //textsize
private boolean disableTextWatcher = false, backKeySet = false;
private TextWatcher txtWatcher;
private onFinishListerner mListerner;
public CustomEntryEdittext(Context context) {
super(context, null);
}
public CustomEntryEdittext(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomEntryEdittext(Context context, AttributeSet attrs, int defStyle) {
this(context, attrs, defStyle, 0);
}
public CustomEntryEdittext(Context context, AttributeSet attrs, int defStyle, int defStyleRes) {
super(context, attrs);
init(context, attrs);
}
public void setOnFinishListerner(onFinishListerner listerner) {
this.mListerner = listerner;
}
public interface onFinishListerner {
void onFinish(String enteredText);
}
private void init(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CustomEntryEdittext, 0, 0);
entryCount = a.getInteger(R.styleable.CustomEntryEdittext_editextCount, 0);
a.recycle();
setOrientation(LinearLayout.HORIZONTAL);
setGravity(Gravity.CENTER_VERTICAL);
for (int i = 0; i < entryCount; i++) {
//creates edittext based on the no. of count
addView(initialiseAndAddChildInLayout(i, context), i);
}
}
//method focuses of previous editext
private void getPreviousEditext(int index) {
if (index > 0) {
EditText edtxt = (EditText) getChildAt(index - 1);
disableTextWatcher = true;
edtxt.setText("");
edtxt.requestFocus();
disableTextWatcher = false;
}
}
//method focuses of previous editext
private void getPreviousEditextFocus(int index) {
if (index > 0) {
EditText edtxt = (EditText) getChildAt(index - 1);
disableTextWatcher = true;
edtxt.requestFocus();
disableTextWatcher = false;
}
}
//method to focus on next edittext
private void getNextEditext(int index) {
if (index < entryCount - 1) {
EditText edtxt = (EditText) getChildAt(index + 1);
edtxt.requestFocus();
}
}
private View initialiseAndAddChildInLayout(int index, Context context) {
final EditText editext = new EditText(context);
editext.setMaxWidth(1);
editext.setTag(index);
editext.setGravity(Gravity.CENTER);
editext.setTextSize(EDITTEXT_TEXTSIZE);
editext.setInputType(EditorInfo.TYPE_CLASS_NUMBER);
editext.setFilters(new InputFilter[]{new InputFilter.LengthFilter(EDITTEXT_MAX_LENGTH)});
LayoutParams param = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1);
editext.setLayoutParams(param);
editext.addTextChangedListener(txtWatcher = 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) {
currentIndex = Integer.parseInt(editext.getTag().toString());
if (editext.getText().toString().length() == 1 && !disableTextWatcher) {
getNextEditext(currentIndex);
} else if (editext.getText().toString().length() == 0 && !disableTextWatcher) {// && !isFirstTimeGetFocused && !backKeySet) {
getPreviousEditext(currentIndex);
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
editext.setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DEL) {
currentIndex = Integer.parseInt(editext.getTag().toString());
if (editext.getText().toString().length() == 0 && !disableTextWatcher) {
getPreviousEditextFocus(currentIndex);
} else {
disableTextWatcher = true;
editext.setText("");
disableTextWatcher = false;
}
backKeySet = true;
}
return true;
}
});
editext.setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
if(currentIndex==entryCount-1 && getEnteredText().length()==entryCount)
{
mListerner.onFinish(getEnteredText());
}
}
return false;
}
});
return editext;
}
public String getEnteredText() {
String strEnteredValue = "";
for (int i = 0; i < getChildCount(); i++) {
EditText editText = (EditText) getChildAt(i);
if (editText.getText() != null && editText.getText().toString().length() > 0)
strEnteredValue = strEnteredValue + editText.getText().toString();
}
return strEnteredValue;
}
public void clearCustomEntryEdittext() {
for (int i = 0; i < getChildCount(); i++) {
EditText editText = (EditText) getChildAt(i);
editText.setText("");
}
EditText editText = (EditText) getChildAt(0);
editText.requestFocus();
}
}
//and add it in your xml file
<com.custom.widget.CustomEntryEdittext
android:id=”@+id/custom_unique_edittext”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:layout_alignParentLeft=”true”
android:layout_centerInParent=”true”
app:editextCount=”6″>
</com.custom.widget.CustomEntryEdittext>
For reference check below link

- 873
- 8
- 12
SOLUTION 1
You can subclass a TextWatcher and implement your own logic.
public class OTPTextWatcher implements TextWatcher {
private EditText view;
private List<EditText> otpDigitViews;
private OTPCompleteListener otpListener;
private static int lastOtpLength;
public OTPTextWatcher(EditText otpView, List<EditText> otpDigitViews, OTPCompleteListener listener) {
view = otpView;
this.otpDigitViews = otpDigitViews;
this.otpListener = listener;
}
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
String digit1 = otpDigitViews.get(0).getText().toString();
String digit2 = otpDigitViews.get(1).getText().toString();
String digit3 = otpDigitViews.get(2).getText().toString();
String digit4 = otpDigitViews.get(3).getText().toString();
String currentDigit = editable.toString();
final String inputValue = digit1 + digit2 + digit3 + digit4;
if (inputValue.length() == 4) {
otpListener.onOTPFilled(inputValue);
} else {
if (currentDigit.length() >= 1
&& view != otpDigitViews.get(3)) {
if (view != null)
view.focusSearch(View.FOCUS_RIGHT).requestFocus();
} else {
if (currentDigit.length() <= 0 && view.getSelectionStart() <= 0) {
try {
view.focusSearch(View.FOCUS_LEFT).requestFocus();
} catch (NullPointerException e) {
LogHelper.printErrorLog("There is no view left to current edit text");
}
}
}
if (OTPTextWatcher.lastOtpLength == 4) {
otpListener.onOTPIncomplete();
}
}
OTPTextWatcher.lastOtpLength = inputValue.length();
}
public interface OTPCompleteListener {
void onOTPFilled(String otp);
void onOTPIncomplete();
}
}
Implementaion :
protected void setEventListeners() {
OTPTextWatcher.OTPCompleteListener otpCompleteListener = new OTPTextWatcher.OTPCompleteListener() {
@Override
public void onOTPFilled(String otp) {
showLoading();
verifyOTP(otp);
}
@Override
public void onOTPIncomplete() {
}
};
for (EditText etOTP : otpViewList) {
etOTP.addTextChangedListener(new OTPTextWatcher(etOTP, otpViewList, otpCompleteListener));
}
}
SOLUTION 2
integrate android-otpview-pinview to your application.

- 1,524
- 1
- 22
- 32
The below solution takes into consideration:
Auto focusing to the next edit text on entering one digit of OTP in the focussed edit text.
Auto focusing to the previous edit text on deleting one digit of OTP in the focussed edit text.
The combination of work in onTextChanged() and afterTextChanged() helps in achieving the same.
private EditText firstDigitOtpEdt, secondDigitOtpEdt, thirdDigitOtpEdt, fourthDigitOtpEdt;
firstDigitOtpEdt.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
if (firstDigitOtpEdt.getText().toString().length() == 1) {
secondDigitOtpEdt.requestFocus();
}
}
});
secondDigitOtpEdt.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
if (secondDigitOtpEdt.getText().toString().length() == 0) {
firstDigitOtpEdt.requestFocus();
}
}
@Override
public void afterTextChanged(Editable editable) {
if (secondDigitOtpEdt.getText().toString().length() == 1) {
thirdDigitOtpEdt.requestFocus();
}
}
});
thirdDigitOtpEdt.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
if (thirdDigitOtpEdt.getText().toString().length() == 0) {
secondDigitOtpEdt.requestFocus();
}
}
@Override
public void afterTextChanged(Editable editable) {
if (thirdDigitOtpEdt.getText().toString().length() == 1) {
fourthDigitOtpEdt.requestFocus();
}
}
});
fourthDigitOtpEdt.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
if (fourthDigitOtpEdt.getText().toString().length() == 0) {
thirdDigitOtpEdt.requestFocus();
}
}
@Override
public void afterTextChanged(Editable editable) {
// We can call api to verify the OTP here or on an explicit button click
}
});

- 109
- 6
using DataBinding :
class EnterOTPDialogFragment extends Fragment {
FragmentEnterOtpdialogBinding binding;
GenericTextWatcher watcher1;
GenericTextWatcher watcher2;
GenericTextWatcher watcher3;
GenericTextWatcher watcher4;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_enter_otpdialog, container, false);
watcher1 = new GenericTextWatcher(binding.optDigit1);
watcher2 = new GenericTextWatcher(binding.optDigit2);
watcher3 = new GenericTextWatcher(binding.optDigit3);
watcher4 = new GenericTextWatcher(binding.optDigit4);
binding.optDigit1.addTextChangedListener(watcher1);
binding.optDigit1.setOnKeyListener(watcher1);
binding.optDigit2.addTextChangedListener(watcher2);
binding.optDigit2.setOnKeyListener(watcher2);
binding.optDigit3.addTextChangedListener(watcher3);
binding.optDigit3.setOnKeyListener(watcher3);
binding.optDigit4.addTextChangedListener(watcher4);
binding.optDigit4.setOnKeyListener(watcher4);
return binding.getRoot();
}
public class GenericTextWatcher implements TextWatcher, View.OnKeyListener {
private View view;
String previousText = "";
private GenericTextWatcher(View view) {
this.view = view;
}
@Override
public void afterTextChanged(Editable editable) {
// TODO Auto-generated method stub
String text = editable.toString();
switch (view.getId()) {
case R.id.optDigit1:
if (text.length() == 1) {
if (previousText.length() > 0) {
binding.optDigit1.removeTextChangedListener(watcher1);
binding.optDigit1.setText(previousText);
binding.optDigit1.addTextChangedListener(watcher1);
binding.optDigit2.removeTextChangedListener(watcher2);
binding.optDigit2.setText(text);
binding.optDigit2.addTextChangedListener(watcher2);
}
binding.optDigit2.requestFocus();
}
break;
case R.id.optDigit2:
if (text.length() == 1) {
if (previousText.length() > 0) {
binding.optDigit2.removeTextChangedListener(watcher2);
binding.optDigit2.setText(previousText);
binding.optDigit2.addTextChangedListener(watcher2);
binding.optDigit3.removeTextChangedListener(watcher3);
binding.optDigit3.setText(text);
binding.optDigit3.addTextChangedListener(watcher3);
}
binding.optDigit3.requestFocus();
} else if (text.length() == 0)
binding.optDigit1.requestFocus();
break;
case R.id.optDigit3:
if (text.length() == 1) {
if (previousText.length() > 0) {
binding.optDigit3.removeTextChangedListener(watcher3);
binding.optDigit3.setText(previousText);
binding.optDigit3.addTextChangedListener(watcher3);
binding.optDigit4.removeTextChangedListener(watcher4);
binding.optDigit4.setText(text);
binding.optDigit4.addTextChangedListener(watcher4);
}
binding.optDigit4.requestFocus();
} else if (text.length() == 0)
binding.optDigit2.requestFocus();
break;
case R.id.optDigit4:
if (text.length() == 0) {
binding.optDigit3.requestFocus();
} else {
try {
final InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getView().getWindowToken(), 0);
Log.e(TAG, "afterTextChanged: hide keyboard");
} catch (Exception e) {
Log.e(TAG, "afterTextChanged: " + e.toString());
}
}
break;
}
}
@Override
public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
// TODO Auto-generated method stub
Log.d(TAG, "beforeTextChanged: " + arg0);
if (arg0.length() > 0) {
previousText = arg0.toString();
}
}
@Override
public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
// TODO Auto-generated method stub
}
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
previousText = "";
Log.d(TAG, "onKey: keyCode = " + keyCode + ", event = " + event.toString());
if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KEYCODE_DEL) {
switch (view.getId()) {
case R.id.optDigit2:
if (binding.optDigit2.getText().toString().trim().length() == 0)
binding.optDigit1.requestFocus();
break;
case R.id.optDigit3:
if (binding.optDigit3.getText().toString().trim().length() == 0)
binding.optDigit2.requestFocus();
break;
case R.id.optDigit4:
if (binding.optDigit4.getText().toString().trim().length() == 0)
binding.optDigit3.requestFocus();
else if (binding.optDigit4.getText().toString().trim().length() == 1)
try {
((BaseActivity) getActivity()).hideSoftKeyboard();
} catch (Exception e) {
Log.e(TAG, "afterTextChanged: " + e.toString());
}
break;
}
}
return false;
}
}
}

- 27,060
- 21
- 118
- 148

- 91
- 1
- 2
For the question asked by @SachinMandhare on https://stackoverflow.com/a/57337907/8765580
Just modify the code to
String pattern = "";
for (int j = 0; j < getText().toString().length(); j++) {
pattern += "*"; //Any Character that you want to display
}
for (int i = 0; i < mNumChars; i++) {
canvas.drawLine(startX, bottom, startX + mCharSize, bottom, mLinesPaint);
if (getText().length() > i) {
float middle = startX + mCharSize / 2;
canvas.drawText(pattern, i, i + 1, middle - textWidths[0] / 2, bottom - mLineSpacing, getPaint());
}
if (mSpace < 0) {
startX += mCharSize * 2;
} else {
startX += mCharSize + mSpace;
}
}

- 285
- 2
- 12
When you want something similar to this with less efforts, just use PinEntryEditText Library.

- 13,761
- 4
- 85
- 82
using DataBinding layout:
public class EnterOTPActivity extends AppCompatActivity {
private ActivityEnterOtpBinding binding;
private Context mContext;
private int currentEditIndex;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_enter_otp);
mContext = this;
binding.et1.addTextChangedListener(new MyTextChangeWatcher(1));
binding.et2.addTextChangedListener(new MyTextChangeWatcher(2));
binding.et3.addTextChangedListener(new MyTextChangeWatcher(3));
binding.et4.addTextChangedListener(new MyTextChangeWatcher(4));
binding.et5.addTextChangedListener(new MyTextChangeWatcher(5));
binding.et6.addTextChangedListener(new MyTextChangeWatcher(6));
binding.et1.setOnKeyListener(keyListener);
binding.et2.setOnKeyListener(keyListener);
binding.et3.setOnKeyListener(keyListener);
binding.et4.setOnKeyListener(keyListener);
binding.et5.setOnKeyListener(keyListener);
binding.et6.setOnKeyListener(keyListener);
}
private View.OnKeyListener keyListener = new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if ((((EditText) v).getText().toString() == null || ((EditText) v)
.getText().toString().isEmpty())
&& keyCode == KeyEvent.KEYCODE_DEL
&& event.getAction() == KeyEvent.ACTION_DOWN) {
if (currentEditIndex == 6)
currentEditIndex = 5;
if (currentEditIndex > 0) {
EditText editText = getEditTextFromIndex(currentEditIndex);
editText.setText("");
editText.requestFocusFromTouch();
currentEditIndex--;
}
}
return false;
}
};
class MyTextChangeWatcher implements TextWatcher {
private int index;
public MyTextChangeWatcher(int index) {
super();
this.index = index;
}
@Override
public void afterTextChanged(Editable s) {
if (s != null && s.length() == 1) {
if (index < 7) {
if (index < 6) {
EditText editText = getEditTextFromIndex(index);
editText.clearFocus();
getEditTextFromIndex(index + 1).requestFocusFromTouch();
}
currentEditIndex = index;
} else {
}
} else {
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
}
}
private EditText getEditTextFromIndex(int index) {
switch (index) {
case 1:
return binding.et1;
case 2:
return binding.et2;
case 3:
return binding.et3;
case 4:
return binding.et4;
case 5:
return binding.et5;
case 6:
return binding.et6;
default:
break;
}
return null;
}
}

- 543
- 7
- 14
I created a gist here https://gist.github.com/ShivamPokhriyal/8d0cf4aef062e6c59d00c04c53e03158 which you can simply copy paste in your project.
It creates a custom OTPEditText class which handles shifting the focus to next or previous edittext when user types in and also handles the paste event when user long presses and pastes the otp in the editText. All this can be done in the xml only. No need to pollute your activity with these stuff.
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* This class handles otp input in multiple edittexts.
* It will move focus to next edittext, if available, when user enters otp.
* And it will move focus to the previous edittext, if available, when user deletes otp.
* It will also delegate the paste option, if user long presses and pastes a string into the otp input.
*
* <b>XML attributes</b>
*
* @attr ref your_package_name.R.styleable#OTPView_nextView
* @attr ref your_package_name.R.styleable#OTPView_prevView
*
* @author $|-|!˅@M
*/
public class OTPEditText extends androidx.appcompat.widget.AppCompatEditText {
@Nullable
private View nextView;
@Nullable
private View previousView;
// Unfortunately getParent returns null inside the constructor. So we need to store the IDs.
private int nextViewId;
private int previousViewId;
@Nullable
private Listener listener;
private static final int NO_ID = -1;
public interface Listener {
void onPaste(String s);
}
public OTPEditText(@NonNull Context context) {
super(context);
}
public OTPEditText(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public OTPEditText(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
public void setListener(Listener listener) {
this.listener = listener;
}
/**
* Called when a context menu option for the text view is selected. Currently
* this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
* {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
*
* @return true if the context menu item action was performed.
*/
@Override
public boolean onTextContextMenuItem(int id) {
if (id == android.R.id.paste) {
ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
// Examines the item on the clipboard. If getText() does not return null, the clip item contains the
// text. Assumes that this application can only handle one item at a time.
ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0);
// Gets the clipboard as text.
CharSequence pasteData = item.getText();
if (listener != null && pasteData != null) {
listener.onPaste(pasteData.toString());
return true;
}
}
return super.onTextContextMenuItem(id);
}
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
// If we've gotten focus here
if (focused && this.getText() != null) {
this.setSelection(this.getText().length());
}
}
private void init(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.OTPView, 0, 0);
nextViewId = typedArray.getResourceId(R.styleable.OTPView_nextView, NO_ID);
previousViewId = typedArray.getResourceId(R.styleable.OTPView_prevView, NO_ID);
typedArray.recycle();
this.setOnKeyListener((v, keyCode, event) -> {
if (event.getAction()!= KeyEvent.ACTION_DOWN) {
return true;
}
//You can identify which key pressed by checking keyCode value with KeyEvent.KEYCODE_
if(keyCode == KeyEvent.KEYCODE_DEL) {
// Back pressed. If we have a previous view. Go to it.
if (getPreviousView() != null) {
getPreviousView().requestFocus();
return true;
}
}
return false;
});
this.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) {
if (s.length() == 1 && getNextView() != null) {
getNextView().requestFocus();
} else if (s.length() == 0 && getPreviousView() != null) {
getPreviousView().requestFocus();
}
}
});
// Android 3rd party keyboards show the copied text into the suggestion box for the user.
// Users can then simply tap on that suggestion to paste the text on the edittext.
// But I don't know of any API that allows handling of those paste actions.
// Below code will try to tell those keyboards to stop showing those suggestion.
this.setInputType(EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS | EditorInfo.TYPE_CLASS_NUMBER);
}
private View getNextView() {
if (nextView != null) {
return nextView;
}
if (nextViewId != NO_ID && getParent() instanceof View) {
nextView = ((View) getParent()).findViewById(nextViewId);
return nextView;
}
return null;
}
private View getPreviousView() {
if (previousView != null) {
return previousView;
}
if (previousViewId != NO_ID && getParent() instanceof View) {
previousView = ((View) getParent()).findViewById(previousViewId);
return previousView;
}
return null;
}
}
The gist also includes the xml and java code that you can directly add to your activity.

- 1,044
- 11
- 26
-
Thanks for sharing. Unfortunately, deleting doesn't work as expected. Any idea why? – Royz Jan 06 '21 at 11:57
First make a Text watcher Class so that if you have multiple screens which require nextFocus can be done through this So TextWatcher class
public class GenericTextWatcher implements TextWatcher { private final EditText etPrev,etNext;
private View view;
public GenericTextWatcher(EditText etNext ,EditText etPrev)
{
this.etPrev=etPrev;
this.etNext=etNext;
}
@Override
public void afterTextChanged(Editable editable) {
String text = editable.toString();
if(text.length()==1){
etNext.requestFocus();
}else{
etPrev.requestFocus();
}
}
@Override
public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
}
@Override
public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
}
}
and code for EditText nextFocus automation by
et1.addTextChangedListener(new GenericTextWatcher(et2,et1));
et2.addTextChangedListener(new GenericTextWatcher(et3,et1));
et3.addTextChangedListener(new GenericTextWatcher(et4,et2));
et4.addTextChangedListener(new GenericTextWatcher(et4,et3));

- 9
- 1
If you want to achieve this functionality with reusable code. You can create an extension function of edit text box.
fun EditText.moveToNext(next: EditText?, prev: EditText?, function: (isEnabled:Boolean) -> Unit) {
this.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun afterTextChanged(p0: Editable?) {
val number = p0.toString()
if (next != null) {
if (number.length == 1) {
next.requestFocus()
}
} else {
function(true)
}
prev?.let { prev ->
if (number.isEmpty()) {
prev.requestFocus()
function(false)
}
}
}
})
}
To call the above extension function you can use the below code.
binding.one.moveToNext(next = binding.two, prev = null) {
binding.verifyBtn.isEnabled = it
}
binding.two.moveToNext(next = binding.three, prev = binding.one) {
binding.verifyBtn.isEnabled = it
}
binding.three.moveToNext(next = binding.four, prev = binding.two) {
binding.verifyBtn.isEnabled = it
}
binding.four.moveToNext(next = null, prev = binding.three) {
binding.verifyBtn.isEnabled = it
}
binding.one is first Edit Text box.
binding.two is next Edit text box.
prev is the previous edit text box.
next is the next edit text box.
lambda function to control the buttons visibility or button is enabled or disabled.

- 131
- 1
- 7
For Kotlin use this simple class with all required functions
This function remove OTP 1 by 1
class GenericKeyEvent internal constructor(private val currentView: EditText, private val previousView: EditText?,val list:List<View>) : View.OnKeyListener{
override fun onKey(p0: View, keyCode: Int, event: KeyEvent): Boolean {
if(event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_DEL && currentView.id != list.get(0).id && currentView.text.isEmpty()) {
//If current is empty then previous EditText's number will also be deleted
previousView?.text = null
previousView?.requestFocus()
return true
}
return false
}
}
This Function add OTP 1 by 1
class GenericTextWatcher internal constructor(private val currentView: View, private val nextView: View?,val list: List<View>) : TextWatcher {
override fun afterTextChanged(editable: Editable) {
list.indexOf(currentView).let {
nextView?.requestFocus()
if (it==list.size-1 && editable.toString().isNotEmpty()){
currentView.let {
val imm=it.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(it.windowToken,0)
}
return
}
}
}
override fun beforeTextChanged(
arg0: CharSequence,
arg1: Int,
arg2: Int,
arg3: Int
) {
}
override fun onTextChanged(
arg0: CharSequence,
arg1: Int,
arg2: Int,
arg3: Int
) {
}
}
Call This Extention with list of Edit texts
fun List<EditText>.makeCustomOTP(){
for (i in 0 until this.size){
this.get(i).addTextChangedListener(GenericTextWatcher(this.get(i),this.getOrNull(i+1),this))
}
for (i in this.size-1 downTo 0){
this.get(i).setOnKeyListener(GenericKeyEvent(this.get(i), this.getOrNull(i-1),this))
}
}

- 139
- 5
Bad and Ugly but Simplest solution:
I don't like the following trick because it is hard to create a custom drawable background for it but it is simplest as possible as.
Add an EditText
to your layout and just set android:letterSpacing="2.5"
Example:
<EditText
android:id="@+id/et_pin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:maxLength="5"
android:textSize="20sp"
android:letterSpacing="2.5"
android:inputType="number"
android:hint="12345"/>

- 817
- 8
- 16
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// move cursor backward
if textField == textField4 && string == "" {
if let text = textField4.text, text.count == 1 {
textField4.text = nil
textField3.becomeFirstResponder()
return false
}
} else if textField == textField3 && string == "" {
if let text = textField3.text, text.count == 1 {
textField3.text = ""
textField2.becomeFirstResponder()
return false
}
} else if textField == textField2 && string == "" {
if let text = textField2.text, text.count == 1 {
textField2.text = ""
textField1.becomeFirstResponder()
return false
}
}
}

- 2,324
- 26
- 22
- 31
-
Your answer could be improved by adding more information on what the code does and how it helps the OP. – Tyler2P Mar 29 '23 at 18:54