OTP视图具有多个编辑文本和软键盘删除操作

时间:2017-01-16 08:30:58

标签: android

我正在为不同长度的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();
                }
            }
        }
    }

}

2 个答案:

答案 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代码。