Two days ago, I received a project. In the project, I need to customize an activation code input box, which is similar to the square input box of a drip taxi. Then I thought about how to do this input box. I finally got it today. The input box receives the number of customized input boxes, the width of the input box, the partition lines between the input boxes, the color of the input box, the size of the input box, and the focus of the input box. The border of a point, the number of words per input box, and the transparency of the input box
Effect display
Development thinking
We need to accept our input through the EditText control, which is obviously not visible in the interface, so we need to make the control transparent. Then we set the input we received to our TextView. To satisfy the square style, you just need to set a background for TextView. In the development process, we found that the number of validation codes is not fixed, there are four digits, there are six digits. In order to achieve low coupling reusability. We need a custom property to meet this requirement. Other specific ideas will be discussed when they are realized.
Realization
- Layout Implementation of Composite Control
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/container_et"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:gravity="center_vertical"
android:orientation="horizontal"
android:showDividers="middle">
</LinearLayout>
<EditText
android:id="@+id/et"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:inputType="number" />
</RelativeLayout>
This is the way that Linear Layout stores TextVeiw, android: show Dividers = "middle" to set our intervals.
- Design custom properties
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="ActivationCodeView"> <attr name="icv_et_number" format="integer" /> <! - Width of input box - > <attr name="icv_et_width" format="dimension|reference" /> <! - The partition line between input boxes - > <attr name="icv_et_divider_drawable" format="reference" /> <! - Input box text color - > <attr name="icv_et_text_color" format="color|reference" /> <! - Input Box Text Size - > <attr name="icv_et_text_size" format="dimension|reference" /> <! - The border when the input box gets the focus - > <attr name="icv_et_bg_focus" format="reference" /> <! - When the input box has no focus, the border - > <attr name="icv_et_bg_normal" format="reference" /> <! - Number of words in each input box - > <attr name="icv_text_count_num" format="integer" /> <! - Background Transparency of Input Box - > <attr name="icv_text_bg_alpha" format="integer" /> </declare-styleable> </resources>
The annotations are clear enough that they are no longer explained.
- Get custom properties
/**
* Initialization
* @param context
* @param attrs
* @param defStyleAttr
*/
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
LayoutInflater.from(context).inflate(R.layout.layout_activation_code, this);
mContainerLl = (LinearLayout) this.findViewById(R.id.container_et);
mInputEt = (EditText) this.findViewById(R.id.et);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ActivationCodeView, defStyleAttr, 0);
mEtNumber = typedArray.getInteger(R.styleable.ActivationCodeView_icv_et_number, 1);
mTvCountNumber = typedArray.getInteger(R.styleable.ActivationCodeView_icv_text_count_num, 1);
mAlpha = typedArray.getInteger(R.styleable.ActivationCodeView_icv_text_bg_alpha,255);
mEtWidth = typedArray.getDimensionPixelSize(R.styleable.ActivationCodeView_icv_et_width, 42);
mEtDividerDrawable = typedArray.getDrawable(R.styleable.ActivationCodeView_icv_et_divider_drawable);
mEtTextSize = typedArray.getDimensionPixelSize(R.styleable.ActivationCodeView_icv_et_text_size, 16);
mEtTextColor = typedArray.getColor(R.styleable.ActivationCodeView_icv_et_text_color, Color.BLACK);
mEtBackgroundDrawableFocus = typedArray.getDrawable(R.styleable.ActivationCodeView_icv_et_bg_focus);
mEtBackgroundDrawableNormal = typedArray.getDrawable(R.styleable.ActivationCodeView_icv_et_bg_normal);
//Release resources
typedArray.recycle();
// When not configured in xml, the default image is initially configured here
if (mEtDividerDrawable == null) {
mEtDividerDrawable = context.getResources().getDrawable(R.drawable.shape_divider_identifying);
}
if (mEtBackgroundDrawableFocus == null) {
mEtBackgroundDrawableFocus = context.getResources().getDrawable(R.drawable.shape_icv_et_bg_focus);
}
if (mEtBackgroundDrawableNormal == null) {
mEtBackgroundDrawableNormal = context.getResources().getDrawable(R.drawable.shape_icv_et_bg_normal);
}
initUI();
}
- Initialize TextView
//Initialize TextView
private void initTextViews(Context context, int etNumber, int etWidth, Drawable etDividerDrawable, float etTextSize, int etTextColor) {
// Set the input length of editText
mInputEt.setCursorVisible(false);//Hide the cursor
mInputEt.setFilters(new InputFilter[]{new InputFilter.LengthFilter(etNumber)}); //Maximum input length
// Set the width of the partition line
if (etDividerDrawable != null) {
etDividerDrawable.setBounds(0, 0, etDividerDrawable.getMinimumWidth(), etDividerDrawable.getMinimumHeight());
mContainerLl.setDividerDrawable(etDividerDrawable);
}
mTextViews = new TextView[etNumber];
for (int i = 0; i < mTextViews.length; i++) {
TextView textView = new TextView(context);
textView.setTextSize(etTextSize);
textView.setTextColor(etTextColor);
textView.setWidth(etWidth);
textView.setHeight(etWidth);
if (i == 0) {
textView.setBackgroundDrawable(mEtBackgroundDrawableFocus);
} else {
textView.setBackgroundDrawable(mEtBackgroundDrawableNormal);
}
//Setting Transparency
textView.getBackground().setAlpha(mAlpha);
textView.setGravity(Gravity.CENTER);
textView.setFocusable(false);
mTextViews[i] = textView;
}
}
The dynamic generation of TextView implementation annotations is very clear here. One thing to note is that textView.setFocusable(false); otherwise TextView will get a cursor in the page. Use an array to store TextView.
- Fill in the iner Layout that holds TextView
private void initEtContainer(TextView[] mTextViews) {
for (int i = 0; i < mTextViews.length; i++) {
mContainerLl.addView(mTextViews[i]);
}
}
- Handling our input events
private class ActivationCodeTextWatcher implements 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 editable) {
String inputStr = editable.toString();
if (inputStr != null && !inputStr.equals("")) {
setText(inputStr);
mInputEt.setText("");
}
}
}
// Listen for Delete Keys
mInputEt.setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN) {
onKeyDelete();
return true;
}
return false;
}
});
// Set text for TextView
private void setText(String inputContent) {
for (int i = 0; i < mTextViews.length; i++) {
TextView tv = mTextViews[i];
if (tv.getText().toString().trim().equals("") || tv.getText().toString().length() < mTvCountNumber) {
tv.setText(tv.getText().toString() + inputContent);
// Adding input-completed listening
if (inputCompleteListener != null) {
inputCompleteListener.inputComplete();
}
//Hop the cursor to the next one
if(tv.getText().toString().length() == mTvCountNumber) {
tv.setBackgroundDrawable(mEtBackgroundDrawableNormal);
if (i < mEtNumber - 1) {
mTextViews[i + 1].setBackgroundDrawable(mEtBackgroundDrawableFocus);
}
}
break;
}
}
}
// Monitor deletion
private void onKeyDelete() {
for (int i = mTextViews.length - 1; i >= 0; i--) {
TextView tv = mTextViews[i];
if (!tv.getText().toString().trim().equals("")) {
tv.setText(tv.getText().subSequence(0,tv.getText().length()-1));
// Added Delete Complete Listening
if (inputCompleteListener != null) {
inputCompleteListener.deleteContent();
}
tv.setBackgroundDrawable(mEtBackgroundDrawableFocus);
if (i < mEtNumber - 1) {
mTextViews[i + 1].setBackgroundDrawable(mEtBackgroundDrawableNormal);
}
break;
}
}
}
- The method provided to the outside world
/**
* Get input text
*
* @return string
*/
public String getInputContent() {
StringBuffer buffer = new StringBuffer();
for (TextView tv : mTextViews) {
buffer.append(tv.getText().toString().trim());
}
return buffer.toString();
}
/**
* Delete input
*/
public void clearInputContent() {
for (int i = 0; i < mTextViews.length; i++) {
if (i == 0) {
mTextViews[i].setBackgroundDrawable(mEtBackgroundDrawableFocus);
} else {
mTextViews[i].setBackgroundDrawable(mEtBackgroundDrawableNormal);
}
mTextViews[i].setText("");
}
}
/**
* Set the number of input boxes
* @param etNumber
*/
public void setEtNumber(int etNumber) {
this.mEtNumber = etNumber;
mInputEt.removeTextChangedListener(mTextWatcher);
mContainerLl.removeAllViews();
initUI();
}
/**
* Get the number of bits of input
*
* @return int
*/
public int getEtNumber() {
return mEtNumber;
}
// Successful listening for input completion and deletion
private InputCompleteListener inputCompleteListener;
public void setInputCompleteListener(InputCompleteListener inputCompleteListener) {
this.inputCompleteListener = inputCompleteListener;
}
public interface InputCompleteListener {
void inputComplete();
void deleteContent();
}