因為專案的需求,需要動態的實現任意多個數量的方形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 如果有什麼建議或意見,歡迎大家提出改善。