實現指定任意數量的方塊EditText容器BlockEditTextViewGroup

明月春秋發表於2018-03-31

因為專案的需求,需要動態的實現任意多個數量的方形EditText的容器,限制一個字元輸入,並且焦點自動向後移動,在有字元時,刪除時焦點會自動向前移動,單獨點選EditText時,會清空內容獲取焦點,所以自定義了控制元件。

一、控制元件的使用

在工程的build.gradle檔案中新增

allprojects {
		repositories {
			...
			maven { url 'https://jitpack.io' }
		}
	}
複製程式碼

2.在專案build.gradle中新增依賴

dependencies {
	        compile 'com.github.MingYueChunQiu:BlockEditTextViewGroup:1.0'
	}
複製程式碼

如果報Failed to resolve:com.android.support:appcompat-v7:這樣的錯,將依賴改寫成這樣

compile ("com.github.MingYueChunQiu:BlockEditTextViewGroup:1.0"){
        exclude group:'com.android.support'
    }
複製程式碼

這裡寫圖片描述

二、控制元件程式碼

public class BlockEditTextViewGroup extends LinearLayoutCompat {

    private int mCount;//方塊個數
    private int mBlockSideLength;//方塊邊長
    private int mMargin, mMarginLeft, mMarginRight, mMarginTop, mMarginBottom;
    private int mTextSize;
    private OnFocusChangeListener mFocusChangeListener;
    private TextWatcher mTextWatcher;
    private List<AppCompatEditText> mBlockList;
    private View vFocused;//記錄獲取焦點的控制元件
    private List<String> mInputList;//儲存所有輸入集合
    private OnCompleteAllInputListener mListener;

    public BlockEditTextViewGroup(Context context) {
        this(context, null);
    }

    public BlockEditTextViewGroup(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BlockEditTextViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private BlockEditTextViewGroup(Builder builder) {
        this(builder.context);
        setCount(builder.count);
        setBlockSideLength(builder.blockSideLength);
        if (builder.margin > 0) {
            setMargin(builder.margin);
        } else {
            setMarginLeft(builder.marginLeft);
            setMarginRight(builder.marginRight);
            setMarginTop(builder.marginTop);
            setMarginBottom(builder.marginBottom);
        }
        setTextSize(builder.textSize);
        setOnFocusChangeListener(builder.onFocusChangeListener);
        setTextWatcher(builder.textWatcher);
        setOnCompleteAllInputListener(builder.onCompleteAllInputListener);
    }

    public OnCompleteAllInputListener getOnCompleteAllInputListener() {
        return mListener;
    }

    public void setOnCompleteAllInputListener(OnCompleteAllInputListener listener) {
        mListener = listener;
    }

    public int getCount() {
        return mCount;
    }

    public void setCount(int count) {
        if (count < 0) {
            return;
        }
        mCount = count;
        initializeInputList();
        setBlockViews(getContext());
    }

    public int getBlockSideLength() {
        return mBlockSideLength;
    }

    public void setBlockSideLength(int blockSideLength) {
        if (mBlockSideLength < 0) {
            return;
        }
        mBlockSideLength = blockSideLength;
    }

    public int getMargin() {
        return mMargin;
    }

    public void setMargin(int margin) {
        mMargin = margin;
        setBlockMargin(margin, margin, margin, margin);
    }

    public int getMarginLeft() {
        return mMarginLeft;
    }

    public void setMarginLeft(int marginLeft) {
        mMarginLeft = marginLeft;
        setBlockMargin(mMarginLeft, getMarginTop(), getPaddingRight(), getMarginBottom());
    }

    public int getMarginRight() {
        return mMarginRight;
    }

    public void setMarginRight(int marginRight) {
        mMarginRight = marginRight;
        setBlockMargin(getMarginLeft(), getMarginTop(), mMarginRight, getMarginBottom());
    }

    public int getMarginTop() {
        return mMarginTop;
    }

    public void setMarginTop(int marginTop) {
        mMarginTop = marginTop;
        setBlockMargin(getMarginLeft(), mMarginTop, getPaddingRight(), getMarginBottom());
    }

    public int getMarginBottom() {
        return mMarginBottom;
    }

    public void setMarginBottom(int marginBottom) {
        mMarginBottom = marginBottom;
        setBlockMargin(getMarginLeft(), getMarginTop(), getPaddingRight(), mMarginBottom);
    }

    public int getTextSize() {
        return mTextSize;
    }

    public void setTextSize(int textSize) {
        if (textSize < 0) {
            return;
        }
        mTextSize = textSize;
        boolean isMeasured = false;
        for (AppCompatEditText appCompatEditText : mBlockList) {
            isMeasured = setBlockPadding(isMeasured, appCompatEditText);
            appCompatEditText.setTextSize(TypedValue.COMPLEX_UNIT_SP, mTextSize);
        }
    }

    /**
     * 設定每個輸入框所佔的平均寬度,並根據寬度,設定其橫向外邊距值
     *
     * @param itemWidth 輸入框寬度
     */
    public void setItemWidth(int itemWidth) {
        if (itemWidth < mBlockList.get(0).getLayoutParams().width) {
            return;
        }
        int marginHorizontal = (itemWidth - mBlockList.get(0).getLayoutParams().width) / 2;
        for (AppCompatEditText appCompatEditText : mBlockList) {
            ((LayoutParams) appCompatEditText.getLayoutParams()).setMargins(marginHorizontal,
                    mMarginTop, marginHorizontal, mMarginBottom);
        }
    }

    public List<String> getInputList() {
        if (mInputList.size() > 0) {
            mInputList.clear();
        }
        for (AppCompatEditText appCompatEditText : mBlockList) {
            mInputList.add(appCompatEditText.getText().toString().trim());
        }
        return mInputList;
    }

    public OnFocusChangeListener getOnFocusChangeListener() {
        return mFocusChangeListener;
    }

    public void setOnFocusChangeListener(OnFocusChangeListener onFocusChangeListener) {
        if (onFocusChangeListener == null) {
            return;
        }
        mFocusChangeListener = onFocusChangeListener;
        for (AppCompatEditText appCompatEditText : mBlockList) {
            appCompatEditText.setOnFocusChangeListener(mFocusChangeListener);
        }
    }

    public TextWatcher getTextWatcher() {
        return mTextWatcher;
    }

    public void setTextWatcher(TextWatcher textWatcher) {
        if (textWatcher == null) {
            return;
        }
        for (AppCompatEditText appCompatEditText : mBlockList) {
            appCompatEditText.removeTextChangedListener(mTextWatcher);
            appCompatEditText.addTextChangedListener(textWatcher);
        }
        mTextWatcher = textWatcher;
    }

    private void init(Context context, AttributeSet attrs) {
        setOrientation(HORIZONTAL);
        setGravity(Gravity.CENTER);
        //讓容器先獲取焦點,避免已進入介面時,就有EditText獲取到焦點
        setFocusable(true);
        setFocusableInTouchMode(true);
        initAttributes(context, attrs);
        initializeInputList();
        mFocusChangeListener = new OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (hasFocus) {
                    vFocused = v;
                    /**
                     * 當有多個EditText時,選中其中一個EditText,會先發生上上個獲取過焦點的EditText,
                     * 獲取到焦點又失去焦點的爭搶事件,所以要判斷是被按下去的那個EditText已經有字元時,
                     * 才去清空,否則上上個EditText中的內容也會被清空
                     */
                    if (((AppCompatEditText) v).getText().length() >= 1 && v.isPressed()) {
                        ((AppCompatEditText) v).setText("");
                        v.requestFocus();
                    }
                }
            }
        };
        mTextWatcher = 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 == null || vFocused == null) {
                    return;
                }
                int index = mBlockList.indexOf(vFocused);
                //如果是刪除,則自動前進到前一個對話方塊
                if (s.length() == 0) {
                    goForward(index);
                }
                //如果是輸入則自動往後一個對話方塊
                if (s.length() > 0) {
                    goBack(s, index);
                }
            }
        };
        setBlockViews(context);
    }

    /**
     * 焦點向前移動
     *
     * @param index 當前焦點處於第幾個輸入框
     */
    private void goForward(int index) {
        if (index > 0) {
            AppCompatEditText appCompatEditText = mBlockList.get(index - 1);
            appCompatEditText.requestFocus();
            //如果是從任意一個有值的輸入框(A)開始輸入到最後,再倒退刪除,當游標前進到
            //A輸入框時,游標會停留在字元的前面,所以要手動將游標進行定位
            appCompatEditText.setSelection(appCompatEditText.getText().length());
        }
    }

    /**
     * 焦點向後移動
     *
     * @param s     輸入框改變後的內容
     * @param index 當前焦點處於第幾個輸入框
     */
    private void goBack(Editable s, int index) {
        //當使用者直接任意選取一個有值得輸入框時,注意s是指已經更改後的文字
        if (s.length() > 1) {
            mBlockList.get(index).setText(s.subSequence(s.length() - 1, s.length()));
        }
        mInputList.set(index, s.toString());
        if (index < mBlockList.size() - 1) {
            mBlockList.get(index + 1).setText("");
            mBlockList.get(index + 1).requestFocus();
        } else if (index == mBlockList.size() - 1) {
            if (mListener != null) {
                boolean isAllInputted = true;
                for (AppCompatEditText appCompatEditText : mBlockList) {
                    if (TextUtils.isEmpty(appCompatEditText.getText().toString().trim())) {
                        isAllInputted = false;
                        break;
                    }
                }
                if (isAllInputted) {
                    mListener.onCompleteAllInput(mInputList);
                }
            }
        }
    }

    /**
     * 初始化輸入集合
     */
    private void initializeInputList() {
        if (mInputList == null) {
            mInputList = new ArrayList<>(mCount);
        } else {
            mInputList.clear();
        }
        for (int i = 0; i < mCount; i++) {
            mInputList.add("");
        }
    }

    /**
     * 設定方塊輸入框
     *
     * @param context
     */
    private void setBlockViews(Context context) {
        if (mCount <= 0) {
            return;
        }
        resetBlockList();
        boolean isMeasured = false;
        for (int i = 0; i < mCount; i++) {
            AppCompatEditText appCompatEditText = new AppCompatEditText(context);
            appCompatEditText.setBackgroundResource(R.drawable.et_block_shape);
            appCompatEditText.setGravity(Gravity.CENTER);
            appCompatEditText.setTextSize(TypedValue.COMPLEX_UNIT_SP, mTextSize);
            if (i == mCount - 1) {
                appCompatEditText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(1)});
            }
            LayoutParams lpEditText = new LayoutParams(mBlockSideLength, mBlockSideLength);
            appCompatEditText.setLayoutParams(lpEditText);
            int padding = (int) (mBlockSideLength * 0.1);
            appCompatEditText.setPadding(padding, padding, padding, padding);
            isMeasured = setBlockPadding(isMeasured, appCompatEditText);
            if (mMargin > 0) {
                lpEditText.setMargins(mMargin, mMargin, mMargin, mMargin);
            } else {
                lpEditText.setMargins(mMarginLeft, mMarginTop, mMarginRight, mMarginBottom);
            }
            appCompatEditText.setOnFocusChangeListener(mFocusChangeListener);
            appCompatEditText.addTextChangedListener(mTextWatcher);
            mBlockList.add(appCompatEditText);
            addView(appCompatEditText);
        }
    }

    /**
     * 設定方塊邊距與內邊距
     *
     * @param isMeasured        方塊內文字高度是否已經測量過
     * @param appCompatEditText 輸入框控制元件
     * @return 返回測量結果
     */
    private boolean setBlockPadding(boolean isMeasured, AppCompatEditText appCompatEditText) {
        if (!isMeasured) {
            Paint paint = new Paint();
            paint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, mTextSize,
                    getResources().getDisplayMetrics()));
            Paint.FontMetrics fontMetrics = paint.getFontMetrics();
            float textHeight = fontMetrics.bottom - fontMetrics.top;
            if (mBlockSideLength < textHeight) {
                mBlockSideLength = (int) textHeight;
            }
            isMeasured = true;
        }
        if (isMeasured) {
            appCompatEditText.getLayoutParams().width = mBlockSideLength;
            appCompatEditText.getLayoutParams().height = mBlockSideLength;
            int padding = (int) (mBlockSideLength * 0.1);
            appCompatEditText.setPadding(padding, padding, padding, padding);
        }
        return isMeasured;
    }

    /**
     * 初始化屬性資源
     *
     * @param context
     * @param attrs
     */
    private void initAttributes(Context context, AttributeSet attrs) {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BlockEditTextViewGroup);
        if (a != null) {
            mCount = a.getInteger(R.styleable.BlockEditTextViewGroup_blockCount, 0);
            mBlockSideLength = a.getDimensionPixelSize(R.styleable.BlockEditTextViewGroup_blockSideLength, 10);
            mMargin = a.getDimensionPixelSize(R.styleable.BlockEditTextViewGroup_blockMargin, 0);
            mMarginLeft = a.getDimensionPixelSize(R.styleable.BlockEditTextViewGroup_blockMarginLeft, 0);
            mMarginRight = a.getDimensionPixelSize(R.styleable.BlockEditTextViewGroup_blockMarginRight, 0);
            mMarginTop = a.getDimensionPixelSize(R.styleable.BlockEditTextViewGroup_blockMarginTop, 0);
            mMarginBottom = a.getDimensionPixelSize(R.styleable.BlockEditTextViewGroup_blockMarginBottom, 0);
            mTextSize = a.getDimensionPixelSize(R.styleable.BlockEditTextViewGroup_blockTextSize, 20);
            a.recycle();
        }
    }

    /**
     * 設定輸入框的邊距
     *
     * @param marginLeft
     * @param marginTop
     * @param marginRight
     * @param marginBottom
     */
    private void setBlockMargin(int marginLeft, int marginTop, int marginRight, int marginBottom) {
        for (AppCompatEditText appCompatEditText : mBlockList) {
            LayoutParams lpEditText = (LayoutParams) appCompatEditText.getLayoutParams();
            lpEditText.setMargins(marginLeft, marginTop, marginRight, marginBottom);
        }
    }

    /**
     * 重置方塊集合並移除所有方塊view
     */
    private void resetBlockList() {
        if (mBlockList == null) {
            mBlockList = new ArrayList<>();
        } else if (mBlockList.size() > 0) {
            //如果還新增有其他view,使用這種方式移除view
//            for (AppCompatEditText appCompatEditText: mBlockList){
//                removeView(appCompatEditText);
//            }
            removeAllViews();
            mBlockList.clear();
        }
    }

    /**
     * 完成所有輸入監聽器
     */
    public interface OnCompleteAllInputListener {

        /**
         * 當完成最後一個輸入框
         *
         * @param list
         */
        void onCompleteAllInput(List<String> list);

    }

    /**
     * Builder設計模式
     */
    public static class Builder {

        private Context context;
        private int count;//方塊個數
        private int blockSideLength;//方塊邊長
        private int margin, marginLeft, marginRight, marginTop, marginBottom;
        private int textSize;
        private OnFocusChangeListener onFocusChangeListener;
        private TextWatcher textWatcher;
        private OnCompleteAllInputListener onCompleteAllInputListener;

        public Builder(Context context) {
            this.context = context;
        }

        public BlockEditTextViewGroup build() {
            return new BlockEditTextViewGroup(this);
        }

        public Builder setCount(int count) {
            this.count = count;
            return this;
        }

        public Builder setBlockSideLength(int blockSideLength) {
            this.blockSideLength = blockSideLength;
            return this;
        }

        public Builder setMargin(int margin) {
            this.margin = margin;
            return this;
        }

        public Builder setMarginLeft(int marginLeft) {
            this.marginLeft = marginLeft;
            return this;
        }

        public Builder setMarginRight(int marginRight) {
            this.marginRight = marginRight;
            return this;
        }

        public Builder setMarginTop(int marginTop) {
            this.marginTop = marginTop;
            return this;
        }

        public Builder setMarginBottom(int marginBottom) {
            this.marginBottom = marginBottom;
            return this;
        }

        public Builder setTextSize(int textSize) {
            this.textSize = textSize;
            return this;
        }

        public Builder setOnFocusChangeListener(OnFocusChangeListener onFocusChangeListener) {
            this.onFocusChangeListener = onFocusChangeListener;
            return this;
        }

        public Builder setTextWatcher(TextWatcher textWatcher) {
            this.textWatcher = textWatcher;
            return this;
        }

        public void setOnCompleteAllInputListener(OnCompleteAllInputListener onCompleteAllInputListener) {
            this.onCompleteAllInputListener = onCompleteAllInputListener;
        }

    }

}
複製程式碼

三、控制元件的使用

在程式碼中使用時,可以使用builder進行建立,也可以直接使用建構函式

        BlockEditTextViewGroup blockEditTextViewGroup = new BlockEditTextViewGroup.Builder(this)
                .setCount(3)
                .setTextSize(50)
                .setMargin(20)
                .setItemWidth(200)
                .setOnCompleteAllInputListener(new BlockEditTextView.OnCompleteAllInputListener() {
                    @Override
                    public void onCompleteAllInput(List<String> list) {
                        LogUtil.d("完成", list.size() + "");
                    }
                }).build();
        ((LinearLayoutCompat)view).addView(blockEditTextViewGroup);
複製程式碼

可以給控制元件直接設定EditText個數,外邊距,文字大小,每個EditText所佔的item寬度居中顯示,可以監聽所有EditText都完成輸入的回撥事件。

四、專案地址

最後給出專案的GitHub地址 https://github.com/MingYueChunQiu/BlockEditTextViewGroup.git 碼雲地址:https://gitee.com/MingYueChunQiu/BlockEditTextViewGroup.git 如果有什麼建議或意見,歡迎大家提出改善。

相關文章