验证码输入框,满足剪切板内容自动填充,看效果
原本做法是6个EditText,后来发现,这样写最大问题是,无法满足粘贴功能,验证码短信 一般都带“复制”,点击 短信通知栏 的“复制”后,6位验证码会自动显示在软键盘左上角,点击一下即完成填充。
如果牺牲掉了验证码“通知栏短信-复制-点击填充”功能 ,用户必须一次性记住6位,逐个输入;若是用户习惯性点击了复制后,发现app竟然无法填充,自己也没记验证码,再次下拉看通知栏看短信时,发现通知栏短信也没了,就必须要回到短信收件箱里查找,这种用户体验,WTF,狠操蛋!!!一定要规避这种打破用户操作习惯,引起用户不爽的细节。
先聊聊思路:
1.首先想到 写一个EditText,然后setBackground()为6个框,字间距刚好让每个数字处于框中间;然而字间距的方法没找到合适的,全部是按比例分间距的,累觉不爱,适配是个巨坑,前路艰险,性价比太低,放弃之。
2.我打开滴滴,美团,结果大厂的复制粘贴各种花式bug啪啪打脸,就不一一拉出来细评了。
3.功夫不负有心人,终于找到一个支持粘贴的app—建设银行,尽管被我测出了bug,也给我启发,让我看出了端倪。
看到上图我猜想,蓝色水滴中间才是全部编辑框字体,于是我剪切,结果正如我所料,框内字体被清除了,并且无论我如何点击,双击,长按最后一个框,光标始终在第二个框里跳动。
于是,我有思路了,所有的框就是TextView,而真正的编辑框内容是透明的。为了点击最后一个框也能唤起软键盘,需要让EditText的宽度与六个框一样宽;
建行app 有个缺点就是 光标可以随着手势左右滑动游走,怎么避免呢,
实现功能:
1.点短信复制后,支持剪切板自动填充,即粘贴;
2.屏蔽长按粘贴,和双击选中;
3.输入完成回调;
4.根据屏幕宽度和左右间距 自动适配 输入方框大小
明显缺点:
此种情况下,无法显示光标,暂时没有想到简单易行的解决办法,如有思路,求评论区赐教。
上代码吧:
/**
* Created by @author iblade.Wang on 2019/4/4.
* 验证码输入框
* EditText字号极小,且颜色透明
*/
public class VerCodeInputView extends FrameLayout {
/**
* 输入框个数
*/
private int inputNum;
/**
* 输入框宽度
*/
private int inputWidth;
private int inputHeight;
/**
* 输入框之间的间隔
*/
private int childPadding;
/**
* 输入框背景
*/
private int editTextBg;
/**
* 文本颜色
*/
private int textColor;
/**
* 文本字体大小
*/
private int textSize;
/**
* 输入类型
*/
private int inputType;
public VerCodeInputView(Context context) {
this(context, null);
}
public VerCodeInputView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public VerCodeInputView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.VerCodeInputView, defStyleAttr, 0);
inputNum = ta.getInteger(R.styleable.VerCodeInputView_inputNum, 6);
inputWidth = ta.getDimensionPixelSize(R.styleable.VerCodeInputView_inputWidth, DensityUtil.dip2px(context, 43));
inputHeight = inputWidth;
childPadding = ta.getDimensionPixelSize(R.styleable.VerCodeInputView_inputPadding, DensityUtil.dip2px(context, 7.5f));
textColor = ta.getColor(R.styleable.VerCodeInputView_inputTxtColor, Color.parseColor("#333333"));
textSize = ta.getDimensionPixelSize(R.styleable.VerCodeInputView_inputTxtSize, 24);
editTextBg = ta.getResourceId(R.styleable.VerCodeInputView_inputBg, R.drawable.selector_bg_edit);
inputType = ta.getInt(R.styleable.VerCodeInputView_inputType, InputType.TYPE_CLASS_NUMBER);
ta.recycle();
textViewList = new ArrayList<>(inputNum);
initViews();
}
private List<TextView> textViewList;
private EditText editText;
private void initViews() {
textViewList.clear();
//textViewList = new ArrayList<>(inputNum);
LinearLayout llTextViewRoot = new LinearLayout(getContext());
LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
llTextViewRoot.setLayoutParams(layoutParams);
llTextViewRoot.setOrientation(LinearLayout.HORIZONTAL);
addView(llTextViewRoot);
for (int i = 0; i < inputNum; i++) {
TextView textView = new TextView(getContext());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(inputWidth, inputHeight);
if (i != inputNum - 1) {//最后一个textView 不设置margin
params.rightMargin = childPadding;
}
params.gravity = Gravity.CENTER;
textView.setLayoutParams(params);
textView.setTextColor(textColor);
textView.setTextSize(textSize);
textView.setGravity(Gravity.CENTER);
textView.setFilters(new InputFilter[]{new InputFilter.LengthFilter(1)});
textView.setInputType(inputType);
textView.setBackgroundResource(editTextBg);
if (i == 0) textView.setSelected(true);//默认首个方框选中
textView.setId(i);
llTextViewRoot.addView(textView);
textViewList.add(textView);
}
editText = new EditText(getContext());
LayoutParams layoutParam2 = new LayoutParams(LayoutParams.MATCH_PARENT, inputHeight);
editText.setLayoutParams(layoutParam2);
editText.setTextSize(0.01f);
//设置透明光标,如果直接不显示光标的话,长按粘贴会没效果
try {
Field f = TextView.class.getDeclaredField("mCursorDrawableRes");
f.setAccessible(true);
f.set(editText, R.drawable.edit_cursor_bg_transparent);
} catch (Exception e) {
e.printStackTrace();
}
editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(inputNum)});
editText.setInputType(inputType);
editText.setTextColor(ContextCompat.getColor(getContext(), R.color.transparent));
editText.setBackground(null);
editText.addTextChangedListener(textWatcher);
addView(editText);
initListener();
}
private void initListener() {
//屏蔽双击: 好多手机双击会出现 选择 剪切 粘贴 的选项卡,
new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDoubleTap(MotionEvent e) {
return true;
}
});
}
private TextWatcher textWatcher = 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 editable) {
String inputContent = (null == editText.getText()) ? "" : editText.getText().toString();
//已经有输入时,屏蔽长按和光标
if (inputContent.length() > 0) {
editText.setLongClickable(false);
editText.setCursorVisible(false);
} else {
editText.setLongClickable(true);
editText.setCursorVisible(true);
}
if (listener != null && inputContent.length() >= inputNum) {
listener.onComplete(inputContent);
}
for (int i = 0, len = textViewList.size(); i < len; i++) {
TextView textView = textViewList.get(i);
textView.setSelected(false);
if (i < inputContent.length()) {
textView.setText(String.valueOf(inputContent.charAt(i)));
} else {
textView.setText("");
//选中待输入的textView
if (i == inputContent.length()) {
textView.setSelected(true);
}
}
}
}
};
private boolean isAuto = false;
/**
* 设置宽高自适应,单个框的宽度平分父布局总宽度
*/
public void setAutoWidth() {
isAuto = true;
requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
if (isAuto && width > 0) {
isAuto = false;
//resetWH(width);
resetMargin(width);
}
}
/* private void resetWH(int w) {
int paddings = childPadding * (inputNum - 1);
inputWidth = (w - paddings) / (inputNum);
inputHeight = inputWidth;
for (int i = 0, len = textViewList.size(); i < len; i++) {
View child = textViewList.get(i);
child.getLayoutParams().height = inputHeight;
child.getLayoutParams().width = inputWidth;
}
editText.getLayoutParams().height = inputHeight;
}*/
private void resetMargin(int width) {
if (width > 0) {
int remainWidth = width - (inputNum * inputWidth);
if (remainWidth > 0 && inputNum > 1) {
childPadding = remainWidth / (inputNum - 1);
for (int i = 0, len = textViewList.size(); i < len; i++) {
View child = textViewList.get(i);
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) child.getLayoutParams();
if (i != inputNum - 1) {//最后一个textView 不设置margin
params.rightMargin = childPadding;
}
params.gravity = Gravity.CENTER;
child.setLayoutParams(params);
child.getLayoutParams().height = inputHeight;
child.getLayoutParams().width = inputWidth;
}
editText.getLayoutParams().height = inputHeight;
}
}
}
/**
* 获取编辑框内容
*
* @return 编辑框内容
*/
public String getEditContent() {
return editText.getText().toString();
}
public OnCompleteListener listener;
public void setOnCompleteListener(OnCompleteListener listener) {
this.listener = listener;
}
public interface OnCompleteListener {
/**
* 完成验证码的填写
*
* @param content 填写内容
*/
void onComplete(String content);
}
}
如何调用:
/**
* @author YlWang
*/
public class MainActivity extends AppCompatActivity {
private VerCodeInputView codeInputCard;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
codeInputCard = findViewById(R.id.edit);
codeInputCard.setAutoWidth();
codeInputCard.setOnCompleteListener(new VerCodeInputView.OnCompleteListener() {
@Override
public void onComplete(String content) {
Toast.makeText(MainActivity.this, "您输入了:" + content, Toast.LENGTH_LONG).show();
}
});
}
}
另外,框框背景drawable目录下 bg_edit_vercode.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true">
<shape android:shape="rectangle">
<solid android:color="@color/color_ffffff" />
<stroke android:width="@dimen/dp_05" android:color="@color/color_00b38a" />
shape>
item>
<item android:state_selected="false">
<shape android:shape="rectangle">
<solid android:color="@color/color_ffffff" />
<stroke android:width="@dimen/dp_05" android:color="@color/color_dfdfdf" />
shape>
item>
selector>
------------------------------2019.4.10更新-----------------------
经测试Vivo,华为部分机型 不会把复制短信的内容呈现在软键盘上方,复制完之后,想粘贴有两种办法:①需要资深玩家在软键盘里找到粘贴按键(缺点:操作麻烦),②大家习惯的长按出现粘贴;
然鹅。。。上面代码又把长按屏蔽了。哎~心塞塞!!!
继续优化:
我们期待长按后出现这种:
可是一旦不屏蔽长按,恶魔放出了瓶子;
例如:
有坑警告:为了满足长按出现粘贴,删除editText.setLongClickable(false);,长按了好久了,不出现粘贴;同步代码,Clean,Rebuild,各种大动作,仍然长按无效;
抱着没啥希望的心态 加上editText.setLongClickable(true)再试一把,果然没希望,还是长按无效。
WHY?
经测删除editText.setCursorVisible(false);再试,长按终于出粘贴了。
结论:设置光标不可见时,长按将会无效。
为了满足长按可用,只好设置光标可见,大不了颜色设成透明的(效果上等同于设置不可见)。
上图蓝色圆球和一大串操作栏好丑,坚决不能出现,那就不得不动态设置了:
例如不输入内容时,不屏蔽长按;一旦有了输入内容后,屏蔽长按,设置光标不可见,这样就不会出现了,所以修改后代码是这样的。
private void initViews() {
textViewList = new ArrayList<>(inputNum);
LinearLayout llTextViewRoot = new LinearLayout(getContext());
LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
llTextViewRoot.setLayoutParams(layoutParams);
llTextViewRoot.setOrientation(LinearLayout.HORIZONTAL);
addView(llTextViewRoot);
for (int i = 0; i < inputNum; i++) {
TextView textView = new TextView(getContext());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(inputWidth, inputHeight);
if (i != inputNum - 1) {//最后一个textView 不设置margin
params.rightMargin = childPadding;
}
params.gravity = Gravity.CENTER;
textView.setLayoutParams(params);
textView.setTextColor(textColor);
textView.setTextSize(textSize);
textView.setGravity(Gravity.CENTER);
textView.setFilters(new InputFilter[]{new InputFilter.LengthFilter(1)});
textView.setInputType(inputType);
textView.setBackgroundResource(editTextBg);
textView.setId(i);
llTextViewRoot.addView(textView);
textViewList.add(textView);
}
editText = new EditText(getContext());
LayoutParams layoutParam2 = new LayoutParams(LayoutParams.MATCH_PARENT, inputHeight);
editText.setLayoutParams(layoutParam2);
editText.setTextSize(0.01f);
//设置透明光标,如果直接不显示光标的话,长按粘贴会没效果
try {
Field f = TextView.class.getDeclaredField("mCursorDrawableRes");
f.setAccessible(true);
f.set(editText, R.drawable.edit_cursor_bg_transparent);
} catch (Exception e) {
e.printStackTrace();
}
editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(inputNum)});
editText.setInputType(inputType);
editText.setTextColor(ContextCompat.getColor(getContext(), R.color.transparent));
editText.setBackground(null);
editText.addTextChangedListener(textWatcher);
addView(editText);
initListener();
}
@Override
public void afterTextChanged(Editable editable) {
String inputContent = (null == editText.getText()) ? "" : editText.getText().toString();
//已经有输入时,屏蔽长按和光标
if (inputContent.length() > 0) {
editText.setLongClickable(false);
editText.setCursorVisible(false);
} else {
editText.setLongClickable(true);
editText.setCursorVisible(true);
}
if (listener != null && inputContent.length() >= inputNum) {
listener.onComplete(inputContent);
}
for (int i = 0, len = textViewList.size(); i < len; i++) {
TextView textView = textViewList.get(i);
if (i < inputContent.length()) {
textView.setText(String.valueOf(inputContent.charAt(i)));
} else {
textView.setText("");
}
}
}
其中:edit_cursor_bg_transparent.xml
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:width="0.01dp" />
<solid android:color="@android:color/transparent" />
shape>
2020.04.26更新,加上了 待输入方框的选中色。效果如下:
最近发现有同行也做了类似需求:https://www.jianshu.com/p/3238a5afc21c