29
29
import android .os .Parcelable ;
30
30
import android .text .Editable ;
31
31
import android .text .InputFilter ;
32
+ import android .text .NoCopySpan ;
32
33
import android .text .Selection ;
33
34
import android .text .SpannableStringBuilder ;
34
35
import android .text .Spanned ;
36
+ import android .text .TextUtils ;
35
37
import android .text .TextWatcher ;
36
38
import android .text .style .ForegroundColorSpan ;
37
39
import android .util .AttributeSet ;
40
+ import android .util .Log ;
41
+ import androidx .annotation .CallSuper ;
38
42
import java .util .ArrayList ;
39
43
import java .util .List ;
40
44
@@ -60,6 +64,7 @@ public class MaskNumberEditText extends ClearEditText {
60
64
private String mCurrencySymbol ;
61
65
private int mCurrencySymbolTextColor = -1 ;
62
66
private int mDecimalLength = -1 ;
67
+ private double mMaxNumberValue = -1 ;
63
68
private boolean mAutoFillNumbers = false ;
64
69
private int mAutoFillNumbersTextColor = -1 ;
65
70
private boolean mShowThousandsSeparator = true ;
@@ -98,6 +103,17 @@ private void init(Context context, AttributeSet attrs, int defStyleAttr) {
98
103
setShowThousandsSeparator (
99
104
ta .getBoolean (
100
105
R .styleable .MaskNumberEditText_fet_showThousandsSeparator , true ));
106
+ String maxValue = ta .getString (R .styleable .MaskNumberEditText_fet_maxNumberValue );
107
+ if (maxValue != null ) {
108
+ try {
109
+ double max = Double .parseDouble (maxValue );
110
+ setMaxNumberValue (max );
111
+ } catch (NumberFormatException e ) {
112
+ Log .e (
113
+ getClass ().getSimpleName (),
114
+ "The value of attribute fet_maxNumberValue is not a Double" );
115
+ }
116
+ }
101
117
} finally {
102
118
ta .recycle ();
103
119
}
@@ -109,7 +125,7 @@ private void init(Context context, AttributeSet attrs, int defStyleAttr) {
109
125
}
110
126
return ;
111
127
}
112
- formatNumber (text );
128
+ formatEditable (text );
113
129
Selection .setSelection (text , text .length ());
114
130
}
115
131
@@ -128,6 +144,29 @@ public void removeTextChangedListener(TextWatcher watcher) {
128
144
}
129
145
}
130
146
147
+ @ Override
148
+ @ CallSuper
149
+ public void setFilters (InputFilter [] filters ) {
150
+ if (filters == null ) {
151
+ throw new IllegalArgumentException ("filters can not be null" );
152
+ }
153
+ boolean havingFilter = false ;
154
+ for (InputFilter filter : filters ) {
155
+ if (filter instanceof MaxNumberValueFilter ) {
156
+ havingFilter = true ;
157
+ break ;
158
+ }
159
+ }
160
+ if (!havingFilter ) {
161
+ InputFilter [] replaceFilters = new InputFilter [filters .length + 1 ];
162
+ replaceFilters [0 ] = new MaxNumberValueFilter ();
163
+ System .arraycopy (filters , 0 , replaceFilters , 1 , filters .length );
164
+ super .setFilters (replaceFilters );
165
+ return ;
166
+ }
167
+ super .setFilters (filters );
168
+ }
169
+
131
170
public String getCurrencySymbol () {
132
171
return mCurrencySymbol ;
133
172
}
@@ -180,6 +219,18 @@ public void setShowThousandsSeparator(boolean showThousandsSeparator) {
180
219
mShowThousandsSeparator = showThousandsSeparator ;
181
220
}
182
221
222
+ public double getMaxNumberValue () {
223
+ return mMaxNumberValue ;
224
+ }
225
+
226
+ public void setMaxNumberValue (double maxNumberValue ) {
227
+ if (maxNumberValue < 0 ) {
228
+ throw new IllegalArgumentException (
229
+ "maxNumberValue must be greater than or equal to zero" );
230
+ }
231
+ mMaxNumberValue = maxNumberValue ;
232
+ }
233
+
183
234
public String getRealNumber () {
184
235
return getRealNumber (false );
185
236
}
@@ -189,10 +240,21 @@ private String getRealNumber(boolean saved) {
189
240
if (editable == null || editable .length () == 0 ) {
190
241
return "" ;
191
242
}
192
- SpannableStringBuilder value = new SpannableStringBuilder (editable );
193
- clearPlaceholders (value );
194
- final String realText = value .toString ();
195
- value .clear ();
243
+ SpannableStringBuilder builder = new SpannableStringBuilder (editable );
244
+ String number = getRealNumber (builder , saved );
245
+ if (!saved && TextUtils .isEmpty (number )) {
246
+ return "0" ;
247
+ }
248
+ return number ;
249
+ }
250
+
251
+ private String getRealNumber (Editable editable , boolean saved ) {
252
+ if (editable == null || editable .length () == 0 ) {
253
+ return "" ;
254
+ }
255
+ clearPlaceholders (editable );
256
+ final String realText = editable .toString ();
257
+ editable .clear ();
196
258
if (!saved ) {
197
259
if (realText .length () > 0 ) {
198
260
if (realText .charAt (realText .length () - 1 ) == DECIMAL_POINT_CHAR ) {
@@ -242,7 +304,7 @@ private void sendAfterTextChanged(Editable s) {
242
304
}
243
305
}
244
306
245
- private void formatNumber (final Editable editable ) {
307
+ private void formatEditable (final Editable editable ) {
246
308
mIsFormatted = true ;
247
309
final boolean filter = mFilterRestoreTextChangeEvent ;
248
310
super .removeTextChangedListener (mTextWatcher );
@@ -254,6 +316,24 @@ private void formatNumber(final Editable editable) {
254
316
selectionEnd = Selection .getSelectionEnd (editable );
255
317
editable .setSpan (SELECTION_SPAN , selectionStart , selectionEnd , Spanned .SPAN_MARK_MARK );
256
318
}
319
+ formatNumber (editable );
320
+ if (!filter ) {
321
+ selectionStart = editable .getSpanStart (SELECTION_SPAN );
322
+ selectionEnd = editable .getSpanEnd (SELECTION_SPAN );
323
+ editable .removeSpan (SELECTION_SPAN );
324
+ editable .setFilters (filters );
325
+ Selection .setSelection (
326
+ editable ,
327
+ Math .min (selectionStart , editable .length ()),
328
+ Math .min (selectionEnd , editable .length ()));
329
+ } else {
330
+ editable .setFilters (filters );
331
+ }
332
+ mIsFormatted = false ;
333
+ super .addTextChangedListener (mTextWatcher );
334
+ }
335
+
336
+ private void formatNumber (final Editable editable ) {
257
337
clearPlaceholders (editable );
258
338
DecimalPointSpan [] spans = editable .getSpans (0 , editable .length (), DecimalPointSpan .class );
259
339
if (spans .length > 0 ) {
@@ -313,20 +393,6 @@ private void formatNumber(final Editable editable) {
313
393
}
314
394
}
315
395
formatCurrencySymbol (editable );
316
- if (!filter ) {
317
- selectionStart = editable .getSpanStart (SELECTION_SPAN );
318
- selectionEnd = editable .getSpanEnd (SELECTION_SPAN );
319
- editable .removeSpan (SELECTION_SPAN );
320
- editable .setFilters (filters );
321
- Selection .setSelection (
322
- editable ,
323
- Math .min (selectionStart , editable .length ()),
324
- Math .min (selectionEnd , editable .length ()));
325
- } else {
326
- editable .setFilters (filters );
327
- }
328
- mIsFormatted = false ;
329
- super .addTextChangedListener (mTextWatcher );
330
396
}
331
397
332
398
private void formatCurrencySymbol (Editable editable ) {
@@ -576,6 +642,73 @@ public void writeToParcel(Parcel out, int flags) {
576
642
}
577
643
}
578
644
645
+ private class MaxNumberValueFilter implements InputFilter {
646
+ private SpannableStringBuilder mBuilder ;
647
+
648
+ @ Override
649
+ public CharSequence filter (
650
+ CharSequence source , int start , int end , Spanned dest , int dstart , int dend ) {
651
+ if (mRestoring || mIsFormatted ) {
652
+ return null ;
653
+ }
654
+ if (mMaxNumberValue != -1 ) {
655
+ String destString = dest .toString ();
656
+ Object [] spans = dest .getSpans (0 , dest .length (), Object .class );
657
+ if (mBuilder == null ) {
658
+ mBuilder = new SpannableStringBuilder ();
659
+ }
660
+ resetDestSpanned (dest , destString , spans );
661
+ if (dstart - dend != 0 ) {
662
+ mBuilder .delete (dstart , dend );
663
+ } else {
664
+ String number = getRealNumber (mBuilder , false );
665
+ if (isLarger (number )) {
666
+ return "" ;
667
+ }
668
+ }
669
+ int endIndex = end ;
670
+ for (int i = start ; i < end ; i ++) {
671
+ resetDestSpanned (dest , destString , spans );
672
+ mBuilder .insert (dstart , source .subSequence (start , i + 1 ));
673
+ formatNumber (mBuilder );
674
+ String number = getRealNumber (mBuilder , false );
675
+ if (isLarger (number )) {
676
+ endIndex = i ;
677
+ break ;
678
+ }
679
+ }
680
+ mBuilder .clear ();
681
+ return source .subSequence (start , endIndex );
682
+ }
683
+ return null ;
684
+ }
685
+
686
+ private void resetDestSpanned (Spanned dest , String destString , Object [] spans ) {
687
+ mBuilder .clear ();
688
+ mBuilder .append (destString );
689
+ for (Object obj : spans ) {
690
+ if (obj instanceof NoCopySpan ) {
691
+ continue ;
692
+ }
693
+ mBuilder .setSpan (
694
+ obj , dest .getSpanStart (obj ), dest .getSpanEnd (obj ), dest .getSpanFlags (obj ));
695
+ }
696
+ }
697
+
698
+ private boolean isLarger (String number ) {
699
+ if (TextUtils .isEmpty (number )) {
700
+ return mMaxNumberValue < 0 ;
701
+ }
702
+ try {
703
+ double value = Double .parseDouble (number );
704
+ return mMaxNumberValue < value ;
705
+ } catch (NumberFormatException e ) {
706
+ // ignored
707
+ }
708
+ return true ;
709
+ }
710
+ }
711
+
579
712
private class MaskNumberTextWatcher implements TextWatcher {
580
713
@ Override
581
714
public void beforeTextChanged (CharSequence s , int start , int count , int after ) {
@@ -592,7 +725,7 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {
592
725
}
593
726
sendOnTextChanged (s , start , before , count );
594
727
if (!mIsFormatted && s instanceof Editable ) {
595
- formatNumber ((Editable ) s );
728
+ formatEditable ((Editable ) s );
596
729
}
597
730
}
598
731
0 commit comments