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 wi
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;
}
}
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
I created a simple library for this purpose. Check it out.
https://github.com/hexdecimal16/EditTextPin
Usage
<com.dhairytripathi.library.EditTextPin
android:id="@+id/editTextPin"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
EditTextPin editTextPin = findViewById(R.id.editTextPin);
String pin = editTextPin.getPin(); //To get the current entered pin
app:underlineColor="" <!-- To change underline color-->
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;
}
}
}
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;
}
}
}
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
https://madoverandroid.wordpress.com/2017/07/11/edittext-with-separate-box-for-each-letter-customentryedittext/