我正在为不同长度的OTP实现自定义视图,最多6位数。我扩展了一个LinearLayout并使用多个编辑文本作为其子视图。每个编辑文本都包含一位数。我想从软键盘为上面的自定义视图实现删除操作。以下是OTP自定义视图的代码。
public class OTPEditText extends LinearLayout {
private int mDigitSpacing = 8; // Space between digits
private int mDigitNumber = 6; // Number of digits
private int mDigitSize = 28; // Font size of the digits
private ArrayList<EditText> mEditTexts; // List of edit text each holding one digit
private OnCompleteListener mCompleteListener; //when all the edit text gets one digit each
public OTPEditText(Context context) {
super(context);
}
public OTPEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public OTPEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* Add the required number of Edit Texts
* @param number - number of digits
*/
public void setDigitNumber(int number){
this.mDigitNumber = number;
addViews();
}
public void setOnCompleteListener(OnCompleteListener listener) {
this.mCompleteListener = listener;
}
private void addViews() {
removeAllViews();
mEditTexts = new ArrayList<>();
for(int i = 0; i < mDigitNumber; i++){
EditText editText = new EditText(getContext());
//Set the necessary attributes
editText.addTextChangedListener(new GenericTextWatcher(i));
mEditTexts.add(editText);
addView(editText);
}
requestLayout();
if(mEditTexts.size() > 0) {
mEditTexts.get(0).requestFocus();
}
}
/**
* similar to setText of an edit text, but
* set one digit each to the edit text
* @param s - string for the edit text
*/
public void setText(String s){
if(s.length() > mDigitNumber){
s = s.substring(0, mDigitNumber);
}
int i;
for(i = 0; i < s.length(); i++){
mEditTexts.get(i).setText(s.charAt(i));
}
for(; i < mEditTexts.size(); i++){
mEditTexts.get(i).setText("");
}
}
/**
* Similar to the getText of an edit text,
* concatenates the text from each edit text
* @return - concatenated string from each edit text
*/
public String getText() {
String text = "";
if(!Utils.isEmptyList(mEditTexts)) {
for (EditText editText : mEditTexts){
text += editText.getText().toString();
}
}
return text;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event){
return true;
}
/**
* Called whenever onClick of the View is called. Simulates the click event of
* the required edit text.
*/
public void doClick() {
if(!Utils.isEmptyList(mEditTexts)){
for(EditText editText : mEditTexts){
if(editText.getText().toString().equals("")){
editText.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN , 0, 0, 0));
editText.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(), MotionEvent.ACTION_UP , 0, 0, 0));
return;
}
}
mEditTexts.get(mEditTexts.size()-1).dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN ,
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, mDigitSize,
getResources().getDisplayMetrics()), 0, 0));
mEditTexts.get(mEditTexts.size()-1).dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(), MotionEvent.ACTION_UP ,
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, mDigitSize,
getResources().getDisplayMetrics()), 0, 0));
}
}
public interface OnCompleteListener {
void onComplete();
}
// Generic edit text watcher
public class GenericTextWatcher implements TextWatcher {
private int index;
public GenericTextWatcher(int index){
this.index = index;
}
@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.toString().length() >= 1){
if(index +1 < mEditTexts.size()){
mEditTexts.get(index + 1).requestFocus();
} else if(index == mEditTexts.size() - 1 && mCompleteListener != null){
mCompleteListener.onComplete();
}
}
}
}
}
答案 0 :(得分:0)
edOtp1.addTextChangedListener(new OtpTextWatcher(edOtp1));
edOtp2.addTextChangedListener(new OtpTextWatcher(edOtp2));
edOtp3.addTextChangedListener(new OtpTextWatcher(edOtp3));
edOtp4.addTextChangedListener(new OtpTextWatcher(edOtp4));
创建此类,以便在添加或删除时处理文本。
private class OtpTextWatcher implements TextWatcher
{
private View view;
OtpTextWatcher(View view) {
this.view = view;
}
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
String text = charSequence.toString();
switch (view.getId()) {
case R.id.edOtp1:
if (text.length() == 1)
edOtp2.requestFocus();
edOtp2.setSelection(edOtp2.getText().length());
else{
edOtp1.requestFocus();
}
break;
case R.id.edOtp2:
if (text.length() == 0) {
edOtp1.requestFocus();
edOtp1.setSelection(edOtp1.getText().length());
}
break;
case R.id.edOtp3:
if (text.length() == 0) {
edOtp2.requestFocus();
edOtp2.setSelection(edOtp2.getText().length());
}
break;
case R.id.edOtp4:
if (text.length() == 0) {
edOtp3.requestFocus();
edOtp3.setSelection(edOtp3.getText().length());
}
break;
default:
break;
}
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
String text = editable.toString();
switch (view.getId()) {
case R.id.edOtp1:
if (text.length() == 1)
edOtp2.requestFocus();
else
edOtp1.setSelection(edOtp1.getText().length());
break;
case R.id.edOtp2:
if (text.length() == 1)
edOtp3.requestFocus();
else if (text.length() == 0) {
edOtp1.requestFocus();
edOtp1.setSelection(edOtp1.getText().length());
}
break;
case R.id.edOtp3:
if (text.length() == 1)
edOtp4.requestFocus();
else if (text.length() == 0) {
edOtp2.requestFocus();
edOtp2.setSelection(edOtp2.getText().length());
}
break;
case R.id.edOtp4:
if (text.length() == 0) {
edOtp3.requestFocus();
edOtp3.setSelection(edOtp3.getText().length());
}
break;
default:
break;
}
}
}
答案 1 :(得分:0)
我在https://gist.github.com/ShivamPokhriyal/8d0cf4aef062e6c59d00c04c53e03158处创建了要点,您只需在项目中复制粘贴即可。
它将创建一个自定义的OTPEditText类,该类可在用户键入或删除时处理将焦点移至下一个或上一个Edittext,并在用户长按otp并将其粘贴到editText时处理粘贴事件。所有这些只能在xml中完成。无需用这些东西污染您的活动。
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;
}
}
要点还包括可以直接添加到活动中的xml和java代码。