验证码输入框,满足剪切板内容自动填充,看效果

原本做法是6个EditText,后来发现,这样写最大问题是,无法满足粘贴功能,验证码短信 一般都带“复制”,点击 短信通知栏
的“复制”后,6位验证码会自动显示在软键盘左上角,点击一下即完成填充。

如果牺牲掉了验证码“通知栏短信-复制-点击填充”功能
,用户必须一次性记住6位,逐个输入;若是用户习惯性点击了复制后,发现app竟然无法填充,自己也没记验证码,再次下拉看通知栏看短信时,发现通知栏短信也没了,就必须要回到短信收件箱里查找,这种用户体验,WTF,狠操蛋!!!一定要规避这种打破用户操作习惯,引起用户不爽的细节。

先聊聊思路:
1.首先想到
写一个EditText,然后setBackground()为6个框,字间距刚好让每个数字处于框中间;然而字间距的方法没找到合适的,全部是按比例分间距的,累觉不爱,适配是个巨坑,前路艰险,性价比太低,放弃之。
2.我打开滴滴,美团,结果大厂的复制粘贴各种花式bug啪啪打脸,就不一一拉出来细评了。
3.功夫不负有心人,终于找到一个支持粘贴的app—建设银行,尽管被我测出了bug,也给我启发,让我看出了端倪。



看到上图我猜想,蓝色水滴中间才是全部编辑框字体,于是我剪切,结果正如我所料,框内字体被清除了,并且无论我如何点击,双击,长按最后一个框,光标始终在第二个框里跳动。
于是,我有思路了,所有的框就是TextView,而真正的编辑框内容是透明的。为了点击最后一个框也能唤起软键盘,需要让EditText的宽度与六个框一样宽;

建行app 有个缺点就是 光标可以随着手势左右滑动游走,怎么避免呢,

* 第一道防线:EditText.setTextSize(0.01f),即便不小心弹出选中操作框,字体足够小
,小到可以避免光标随手势左右游走,精度层避免该现象,当然这只是补救措施,根本杜绝的话,需要避免选中操作框弹出;
* 第二道防线:屏蔽长按事件避免出现“剪切,复制,粘贴”的那个系统选项框;
* 第三道防线:屏蔽双击事件,经测小米、华为等手机,双击和长按都会弹出“剪切,复制,粘贴”系统选项框,宜将剩勇追穷寇,务必赶尽杀绝。
实现功能:
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.
bg_edit_vercode); inputType = ta.getInt(R.styleable.VerCodeInputView_inputType,
InputType.TYPE_CLASS_NUMBER); ta.recycle(); this.initViews(); } private List<
TextView> textViewList; private EditText editText; 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.1f); editText.setCursorVisible(false);
editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(inputNum)});
//屏蔽长按 editText.setLongClickable(false); 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
= editText.getText().toString(); 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(""); } } } }; 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); } } 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; } /** * 获取编辑框内容 * * @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
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="
http://schemas.android.com/apk/res/android"> <!-- 连框颜色值 --> <item> <shape
android:shape="rectangle"> <solid android:color="@color/color_ffffff" /> <stroke
android:width="0.5dp" android:color="@color/color_00b38a" /> </shape> </item> </
layer-list>
------------------------------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
<?xml version="1.0" encoding="utf-8"?> <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>
最近发现有同行也做了类似需求:https://www.jianshu.com/p/3238a5afc21c
<https://www.jianshu.com/p/3238a5afc21c>

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:[email protected]
QQ群:637538335
关注微信