EditText限制输入2位小数功能完善

前言

事情的过程是这样的,康师傅写的EditText限制输入2位小数这篇文章中实现的功能有点BUG,断点同学发现有BUG并提出了问题Android edittext 显示2位小数输入的正则请教,于是乎我开始解决这个问题,所以有了这篇文章。

放码过来

这里只放了Activity的代码,布局就一个EditText,就不放布局代码了。

Java版本代码

NumActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155


import android.annotation.SuppressLint;
import android.text.InputFilter;
import android.text.Spanned;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.widget.EditText;
import android.widget.TextView;

import com.example.firstlinecode.R;
import com.example.firstlinecode.base.BaseActivity;

import java.lang.reflect.Field;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class NumActivity extends BaseActivity {

private static final String TAG = "NumActivity";
private EditText mEtInputText;

@Override
public int getLayoutResId() {
return R.layout.activity_num;
}

@Override
public void initView() {
mEtInputText = findViewById(R.id.etInputText);
disableCopyAndPaste(mEtInputText);
}

@Override
public void initEvent() {
mEtInputText.setFilters(new InputFilter[]{new PriseNumberFilter()});
}

private static class PriseNumberFilter implements InputFilter {

private final Pattern mPattern;

public PriseNumberFilter() {
//前面位数不限,后面限制2位,如果需要自行修改即可
String regex = "[0-9]*+(\\.[0-9]{0,2})?";
mPattern = Pattern.compile(regex);
}

/**
* @param source 将要输入的字符串,如果是删除操作则为空字符串
* @param start 将要输入的字符串起始下标,一般为0
* @param end start + source字符的长度
* @param dest 输入之前文本框中的内容
* @param dstart 将会被替换的起始位置
* @param dend 将会被替换的字符串长度
* @return 返回值 :方法返回的值将会替换掉dest字符串中dstartd位置到dend位置之间字符
* 返回source表示不做任何处理,返回空字符串""表示不输入任何字符
*/
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
if (".".contentEquals(source) && "".contentEquals(dest.toString())) {
return "0.";
}
StringBuilder builder = new StringBuilder(dest);
if ("".contentEquals(source)) {
builder.replace(dstart, dend, "");
} else {
builder.insert(dstart, source);
}
String resultTemp = builder.toString();
Matcher matcher = mPattern.matcher(resultTemp);
// 新添加的代码片段 resultTemp.matches("^0\\d") ||
if (resultTemp.matches("^0\\d") || !matcher.matches()) {
return "";
}
return null;
}
}

@SuppressLint("ClickableViewAccessibility")
public void disableCopyAndPaste(final EditText editText) {
try {
if (editText == null) {
return;
}
// ========新添加的代码start=======
editText.setOnClickListener(v -> {
editText.requestFocus();
editText.setSelection(editText.getText().length());
});
// ========新添加的代码end=======
editText.setOnLongClickListener(v -> true);
editText.setLongClickable(false);
editText.setOnTouchListener((v, event) -> {
// ========新添加的代码start=======
editText.requestFocus();
editText.setSelection(editText.getText().length());
// ========新添加的代码end=======
if (event.getAction() == MotionEvent.ACTION_DOWN) {
// setInsertionDisabled when user touches the view
setInsertionDisabled(editText);
}
return false;
});
editText.setCustomSelectionActionModeCallback(new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
return false;
}

@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}

@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return false;
}

@Override
public void onDestroyActionMode(ActionMode mode) {

}
});
} catch (Exception e) {
e.printStackTrace();
}
}

@SuppressLint("PrivateApi")
private void setInsertionDisabled(EditText editText) {
try {
Field editorField = TextView.class.getDeclaredField("mEditor");
editorField.setAccessible(true);
Object editorObject = editorField.get(editText);

// if this view supports insertion handles
Class editorClass = Class.forName("android.widget.Editor");
Field mInsertionControllerEnabledField = editorClass.getDeclaredField("mInsertionControllerEnabled");
mInsertionControllerEnabledField.setAccessible(true);
mInsertionControllerEnabledField.set(editorObject, false);

// if this view supports selection handles
Field mSelectionControllerEnabledField = editorClass.getDeclaredField("mSelectionControllerEnabled");
mSelectionControllerEnabledField.setAccessible(true);
mSelectionControllerEnabledField.set(editorObject, false);
} catch (Exception e) {
e.printStackTrace();
}
}
}

Kotlin版本代码

NumActivity.kt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125

import android.annotation.SuppressLint
import android.text.InputFilter
import android.text.Spanned
import android.view.*
import android.widget.EditText
import android.widget.TextView
import com.example.firstlinecode.R
import com.example.firstlinecode.base.BaseActivity
import kotlinx.android.synthetic.main.activity_num.*
import java.util.regex.Pattern

class NumActivity : BaseActivity() {

override fun getLayoutResId() = R.layout.activity_num

override fun initView() {
disableCopyAndPaste(etInputText)
}

override fun initEvent() {
etInputText.filters = arrayOf(PriseNumberFilter())
}

private class PriseNumberFilter : InputFilter {

private val mPattern: Pattern

init {
//前面位数不限,后面限制2位,如果需要自行修改即可
val regex = "[0-9]*+(\\.[0-9]{0,2})?"
mPattern = Pattern.compile(regex)
}

/**
* @param source 将要输入的字符串,如果是删除操作则为空字符串
* @param start 将要输入的字符串起始下标,一般为0
* @param end start + source字符的长度
* @param dest 输入之前文本框中的内容
* @param dstart 将会被替换的起始位置
* @param dend 将会被替换的字符串长度
* @return 返回值 :方法返回的值将会替换掉dest字符串中dstartd位置到dend位置之间字符
* 返回source表示不做任何处理,返回空字符串""表示不输入任何字符
*/
override fun filter(source: CharSequence, start: Int, end: Int, dest: Spanned, dstart: Int, dend: Int): CharSequence? {
if ("." == source && "" == dest.toString()) {
return "0."
}
val builder = StringBuilder(dest)
if ("" == source) {
builder.replace(dstart, dend, "")
} else {
builder.insert(dstart, source)
}
val resultTemp = builder.toString()
val matcher = mPattern.matcher(resultTemp)
return if (Regex("^0\\d") matches resultTemp || !matcher.matches()) {
""
} else null
}
}

@SuppressLint("ClickableViewAccessibility")
fun disableCopyAndPaste(editText: EditText?) {
editText?.run {
try {
setOnClickListener {
requestFocus()
setSelection(text.length)
}
setOnLongClickListener { true }
isLongClickable = false
setOnTouchListener { _: View?, event: MotionEvent ->
requestFocus()
setSelection(text.length)
if (event.action == MotionEvent.ACTION_DOWN) {
// setInsertionDisabled when user touches the view
setInsertionDisabled(this)
}
false
}
customSelectionActionModeCallback = object : ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
return false
}

override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
return false
}

override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
return false
}

override fun onDestroyActionMode(mode: ActionMode) {}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}

@SuppressLint("PrivateApi")
private fun setInsertionDisabled(editText: EditText) {
try {
val editorField = TextView::class.java.getDeclaredField("mEditor")
editorField.isAccessible = true
val editorObject = editorField[editText]

// if this view supports insertion handles
val editorClass = Class.forName("android.widget.Editor")
val mInsertionControllerEnabledField = editorClass.getDeclaredField("mInsertionControllerEnabled")
mInsertionControllerEnabledField.isAccessible = true
mInsertionControllerEnabledField[editorObject] = false

// if this view supports selection handles
val mSelectionControllerEnabledField = editorClass.getDeclaredField("mSelectionControllerEnabled")
mSelectionControllerEnabledField.isAccessible = true
mSelectionControllerEnabledField[editorObject] = false
} catch (e: Exception) {
e.printStackTrace()
}
}
}

BaseActivity部分的代码

BaseActivity.kt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52


import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.annotation.CallSuper
import androidx.appcompat.app.AppCompatActivity

abstract class BaseActivity : AppCompatActivity() {

@CallSuper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val layoutResId = getLayoutResId()
if (layoutResId > 0) setContentView(layoutResId)
initView()
initData()
initEvent()
}

abstract fun getLayoutResId(): Int

open fun initEvent() {

}

open fun initData() {

}

open fun initView() {

}

override fun onDestroy() {
super.onDestroy()
release()
}

open fun release() {

}

fun startActivity(context: Context, cls: Class<*>) {
context.startActivity(Intent(context, cls))
}

fun startActivity(cls: Class<*>) {
startActivity(Intent(this, cls))
}
}

总结

部分输入法有改变光标的功能,这就导致我们在点击和触摸监听器中改变光标到字符串末尾失效。
这部分就需要自己自定义数字键盘实现才能解决啦(我目前还没发现其他方法,如果有的话,麻烦大家评论区告诉我),自定义数字键盘部分请同学们去看康师傅的 Android开发自定义控件系列课程

参考文章

请同学们点赞、评论、打赏+关注啦~


EditText限制输入2位小数功能完善
https://blog.funkt.cn/2021/01/19/1351472282035376128/
作者
anjiemo
发布于
2021年1月19日
许可协议
未经作者授权,禁止转载