友情提示 该项目已在github开源,解决所有bug,也有更新计划,具体请看 README
最新版本已支持密码模式
概述:先描述一下具体需求吧,我们在项目中可能会遇到修改用户名及密码的需求,为保证一定的完全性,服务端一般会接入短信验证码的功能。我们需要将接受到的验证码返回给服务端进行验证。可能会有以下的界面让用户输入验证码:
那么此时我们就需要用自定义控件去实现我们的需求了。源码下载
一 分析
在安卓开发中,实现自定义控件的方式有三种,继承控件,组合控件,自绘控件。现在我们需要通过分析决定使用哪种方式。首先我们需要有输入事件,那么我们肯定是需要有EditText控件的。其次我们用需要显示验证码。我们需要显示文本的控件TextView。那么很明显了,我们需要使用组合控件的方式来实现我们的需求。
二 思路
我们需要通过EditText控件接受我们的输入,界面中明显是不能看到这个控件的,所以我们需要将这个控件给设置透明。然后将接受到的输入内容设置给我们的TextView。为了满足正方形的样式,只需要给TextView设置一个背景即可。在开发过程中,我们发现验证码的个数不是固定的,有4位数的,也有6位数的。为了实现低耦合的复用性。我们需要一个自定义的属性来满足这个要求。其他具体的思路将会在具体实现的时候讲。
三 实现
(1)组合控件的布局实现
<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>
1234567891011121314151617181920212223
这的LinearLayout用来存放TextVeiw ,android:showDividers=”middle”来设定我们的间隔的方式。
(2)设计自定义属性
<declare-styleable name="IdentifyingCodeView">
<attr name="icv_et_number" format="integer" />
<attr name="icv_et_width" format="dimension|reference" />
<attr name="icv_et_divider_drawable" format="reference" />
<attr name="icv_et_text_color" format="color|reference" />
<attr name="icv_et_text_size" format="dimension|reference" />
<attr name="icv_et_bg_focus" format="reference" />
<attr name="icv_et_bg_normal" format="reference" />
</declare-styleable>
1234567891011121314151617
注释已经很清楚了,就不再解释了。
(3)获取自定义属性
public class IdentifyingCodeView extends RelativeLayout
public IdentifyingCodeView(Context context) {
this(context,
null);
}
public IdentifyingCodeView(Context context, AttributeSet attrs) {
this(context, attrs,
0);
}
public IdentifyingCodeView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
private void init(Context context, AttributeSet attrs,
int defStyleAttr) {
LayoutInflater.from(context).inflate(R.layout.layout_identifying_code,
this);
containerEt = (LinearLayout)
this.findViewById(R.id.container_et);
et = (EditText)
this.findViewById(R.id.et);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.IdentifyingCodeView, defStyleAttr,
0);
mEtNumber = typedArray.getInteger(R.styleable.IdentifyingCodeView_icv_et_number,
1);
count = mEtNumber;
mEtWidth = typedArray.getDimensionPixelSize(R.styleable.IdentifyingCodeView_icv_et_width,
42);
mEtDividerDrawable = typedArray.getDrawable(R.styleable.IdentifyingCodeView_icv_et_divider_drawable);
mEtTextSize = typedArray.getDimensionPixelSize(R.styleable.IdentifyingCodeView_icv_et_text_size,
16);
mEtTextColor = typedArray.getColor(R.styleable.IdentifyingCodeView_icv_et_text_color, Color.WHITE);
mEtBackgroundDrawableFocus = typedArray.getDrawable(R.styleable.IdentifyingCodeView_icv_et_bg_focus);
mEtBackgroundDrawableNormal = typedArray.getDrawable(R.styleable.IdentifyingCodeView_icv_et_bg_normal);
typedArray.recycle();
}
12345678910111213141516171819202122232425262728293031323334
(4)在 onFinishInflate 方法中进行逻辑实现
① 初始化 TextView
initTextViews(getContext(), mEtNumber, mEtWidth, mEtDividerDrawable, mEtTextSize, mEtTextColor);
private void initTextViews(Context context,
int etNumber,
int etWidth, Drawable etDividerDrawable,
float etTextSize,
int etTextColor) {
et.setCursorVisible(
false);
et.setFilters(
new InputFilter[]{
new InputFilter.LengthFilter(etNumber)});
if (etDividerDrawable !=
null) {
etDividerDrawable.setBounds(
0,
0, etDividerDrawable.getMinimumWidth(), etDividerDrawable.getMinimumHeight());
containerEt.setDividerDrawable(etDividerDrawable);
}
mTextViews =
new TextView[etNumber];
for (
int i =
0; i < mTextViews.length; i++) {
TextView textView =
new EditText(context);
textView.setTextSize(etTextSize);
textView.setTextColor(etTextColor);
textView.setWidth(etWidth);
textView.setHeight(etWidth);
if (i ==
0) {
textView.setBackgroundDrawable(mEtBackgroundDrawableFocus);
}
else {
textView.setBackgroundDrawable(mEtBackgroundDrawableNormal);
}
textView.setGravity(Gravity.CENTER);
textView.setFocusable(
false);
mTextViews[i] = textView;
}
}
1234567891011121314151617181920212223242526272829303132
这里动态生成TextView 具体实现 注释很清楚,需要注意一点的是 textView.setFocusable(false); 不然在页面中TextView会获取焦点有一个 光标。用一个数组来存放 TextView。
② 填充盛放TextView的 LinerLayout
initEtContainer(mTextViews);
private void initEtContainer(TextView[] mTextViews) {
for (
int i =
0; i < mTextViews.length; i++) {
containerEt.addView(mTextViews[i]);
}
}
123456789
③ 处理我们的输入事件
setListener();
private void setListener() {
et.addTextChangedListener(
new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence,
int i,
int i1,
int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence,
int i,
int i1,
int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
String inputStr = editable.toString();
if (inputStr !=
null && !inputStr.equals(
"")) {
setText(inputStr);
et.setText(
"");
}
}
});
et.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;
}
});
}
public void setText(String inputContent) {
for (
int i =
0; i < mTextViews.length; i++) {
TextView tv = mTextViews[i];
if (tv.getText().toString().trim().equals(
"")) {
tv.setText(inputContent);
if (inputCompleteListener !=
null) {
inputCompleteListener.inputComplete();
}
tv.setBackgroundDrawable(mEtBackgroundDrawableNormal);
if (i < mEtNumber -
1) {
mTextViews[i +
1].setBackgroundDrawable(mEtBackgroundDrawableFocus);
}
break;
}
}
}
public void onKeyDelete() {
for (
int i = mTextViews.length -
1; i >=
0; i--) {
TextView tv = mTextViews[i];
if (!tv.getText().toString().trim().equals(
"")) {
tv.setText(
"");
if (inputCompleteListener !=
null) {
inputCompleteListener.deleteContent();
}
tv.setBackgroundDrawable(mEtBackgroundDrawableFocus);
if (i < mEtNumber -
1) {
mTextViews[i +
1].setBackgroundDrawable(mEtBackgroundDrawableNormal);
}
break;
}
}
}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
代码都很简洁明了 就不一一说明了
④ 提供给外界的方法
/**
* 获取输入文本
*
* @return
*/
public String
getTextContent() {
StringBuffer buffer =
new StringBuffer();
for (TextView tv : mTextViews) {
buffer.append(tv.getText().toString().trim());
}
return buffer.toString();
}
/**
* 删除所有内容
*/
public void clearAllText() {
for (
int i =
0; i < mTextViews.length; i++) {
if (i ==
0) {
mTextViews[i].setBackgroundDrawable(mEtBackgroundDrawableFocus);
}
else {
mTextViews[i].setBackgroundDrawable(mEtBackgroundDrawableNormal);
}
mTextViews[i].setText(
"");
}
}
/**
* 获取输入的位数
*
* @return
*/
public int getTextCount() {
return mEtNumber;
}
private InputCompleteListener inputCompleteListener;
public void setInputCompleteListener(InputCompleteListener inputCompleteListener) {
this.inputCompleteListener = inputCompleteListener;
}
public interface InputCompleteListener {
void inputComplete();
void deleteContent();
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
四 效果
是不是棒棒哒 ,其中在逻辑实现过程中,代码和思路都非常的简洁明了。源码下载 GitHub地址
转自:https://blog.csdn.net/qq_33553515/article/details/73344155