The library allows to format user input on the fly according to the provided mask and to extract valueable characters.
Masks consist of blocks of symbols, which may include:
[]
— a block for valueable symbols written by user.
Square brackets block may contain any number of special symbols:
0
— mandatory digit. For instance,[000]
mask will allow user to enter three numbers:123
.9
— optional digit . For instance,[00099]
mask will allow user to enter from three to five numbers.А
— mandatory letter.[AAA]
mask will allow user to enter three letters:abc
.а
— optional letter.[АААааа]
mask will allow to enter from three to six letters._
— mandatory symbol (digit or letter).-
— optional symbol (digit or letter).…
— ellipsis. Allows to enter endless count of symbols. For details and rules see Elliptical masks.
Other symbols inside square brackets will cause a mask initialization error.
Blocks may contain mixed types of symbols; such that, [000AA]
will end up being divided in two groups: [000][AA]
(this happens automatically).
Blocks must not contain nested brackets. [[00]000]
format will cause a mask initialization error.
Symbols outside the square brackets will take a place in the output. For instance, +7 ([000]) [000]-[0000]
mask will format the input field to the form of +7 (123) 456-7890
.
{}
— a block for valueable yet fixed symbols, which could not be altered by the user.
Symbols within the square and curly brackets form an extracted value (valueable characters).
In other words, [00]-[00]
and [00]{-}[00]
will format the input to the same form of 12-34
,
but in the first case the value, extracted by the library, will be equal to 1234
, and in the second case it will result in 12-34
.
Mask format examples:
- [00000000000]
- {401}-[000]-[00]-[00]
- [000999999]
- {818}-[000]-[00]-[00]
- [A][-----------------------------------------------------]
- [A][_______________________________________________________________]
- 8 [0000000000]
- 8([000])[000]-[00]-[00]
- [0000]{-}[00]
- +1 ([000]) [000] [00] [00]
Mask format supports backslash escapes when you need square or curly brackets in the output.
For instance, \[[00]\]
mask will allow user to enter [12]
. Extracted value will be equal to 12
.
Note that you've got to escape backslashes in the actual code:
let format: String = "\\[[00]\\]"
Escaped square or curly brackets might be included in the extracted value. For instance, \[[00]{\]}
mask will allow user
to enter the same [12]
, yet the extracted value will contain the latter square bracket: 12]
.
repositories {
jcenter()
}
dependencies {
implementation 'com.redmadrobot:inputmask:3.2.0'
}
Listening to the text change events of EditText
and simultaneously altering the entered text could be a bit tricky as
long as you need to add, remove and replace symbols intelligently preserving the cursor position.
Thus, the library provides corresponding MaskedTextChangedListener
class.
MaskedTextChangedListener
conforms to TextWatcher
and OnFocusChangeListener
interfaces and encaspulates logic to process text edit events.
The object might be instantiated via code and then wired with the corresponding EditText
.
MaskedTextChangedListener
has his own listener MaskedTextChangedListener.ValueListener
, which allows capturing extracted value.
All the TextWatcher
calls from the client EditText
are forwarded to the decorated TextWatcher
object (you may provide one when initializing MaskedTextChangedListener
).
public final class MainActivity extends Activity {
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final EditText editText = (EditText) findViewById(R.id.edit_text);
final MaskedTextChangedListener listener = new MaskedTextChangedListener(
"+7 ([000]) [000] [00] [00]",
true,
editText,
null,
new MaskedTextChangedListener.ValueListener() {
@Override
public void onTextChanged(boolean maskFilled, @NonNull final String extractedValue) {
Log.d(MainActivity.class.getSimpleName(), extractedValue);
Log.d(MainActivity.class.getSimpleName(), String.valueOf(maskFilled));
}
}
);
editText.addTextChangedListener(listener);
editText.setOnFocusChangeListener(listener);
editText.setHint(listener.placeholder());
}
}
In case you want to format a String
somewhere in your applicaiton's code, Mask
is the class you are looking for.
Instantiate a Mask
instance and feed it with your string, mocking the cursor position:
final Mask mask = new Mask("+7 ([000]) [000] [00] [00]");
final String input = "+71234567890";
final Mask.Result result = mask.apply(
new CaretString(
input,
input.length()
),
true // you may consider disabling autocompletion for your case
);
final String output = result.getFormattedText().getString();
An experimental feature. While transforming the text, Mask
calculates affinity
index, which is basically an Int
that shows the absolute rate of similarity between the text and the mask pattern.
This index might be used to choose the most suitable pattern between predefined, and then applied to format the text.
For the implementation, look for the PolyMaskTextChangedListener
class, which inherits logic from MaskedTextChangedListener
. It has its primary mask pattern and corresponding list of affine formats.
public final class MainActivity extends Activity {
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final EditText editText = (EditText) findViewById(R.id.edit_text);
final List<String> affineFormats = new ArrayList<>();
affineFormats.add("8 ([000]) [000] [000] [00]");
final MaskedTextChangedListener listener = new PolyMaskTextChangedListener(
"+7 ([000]) [000] [00] [00]",
affineFormats,
true,
editText,
null,
new MaskedTextChangedListener.ValueListener() {
@Override
public void onTextChanged(boolean maskFilled, @NonNull final String extractedValue) {
Log.d(MainActivity.class.getSimpleName(), extractedValue);
Log.d(MainActivity.class.getSimpleName(), String.valueOf(maskFilled));
}
}
);
editText.addTextChangedListener(listener);
editText.setOnFocusChangeListener(listener);
editText.setHint(listener.placeholder());
}
}
An experimental feature. Allows to enter endless line of symbols of specific type. Ellipsis "inherits" its symbol type from the
previous character in format string. Masks like [A…]
or [a…]
will allow to enter letters, [0…]
or [9…]
— numbers, etc.
Be aware that ellipsis doesn't count as a required character. Also, ellipsis works as a string terminator, such that mask [0…][AAA]
filled with a single digit 5
returns true
in Result.complete
, yet continues to accept digits (not letters!). Format after ellipsis is compiled into the mask but
never actually used; characters [AAA]
in the [0…][AAA]
mask are pretty much useless.
Elliptical format examples:
[…]
is a wildcard mask, allowing to enter letters and digits. Always returnstrue
inResult.complete
.[00…]
is a numeric mask, allowing to enter digits. Requires at least two digits to becomplete
.[9…]
is a numeric mask, allowing to enter digits. Always returnstrue
inResult.complete
.[_…]
is a wildcard mask with a single mandatory character. Allows to enter letters and digits. Requires a single character (digit or letter).[-…]
acts same as[…]
.
Be careful when specifying field's android:inputType
.
The library uses native Editable
variable received on afterTextChange
event in order to replace text efficiently. Because of that, field's inputType
is actually considered when the library is trying to mutate the text.
For instance, having a field with android:inputType="numeric"
, you cannot put spaces and dashes into the mentioned Editable
variable by default. Doing so will cause an out of range exception when the MaskedTextChangedListener
will try to reposition the cursor.
Still, you may use a workaround by putting the android:digits
value beside your android:inputType
; there, you should specify all the acceptable symbols:
<EditText
android:inputType="number"
android:digits="0123456789 -."
... />
— such that, you'll have the SDK satisfied.
Alternatively, if you are using a programmatic approach without XML files, you may consider configuring a KeyListener
like this:
editText.setInputType(InputType.TYPE_CLASS_NUMBER);
editText.setKeyListener(DigitsKeyListener.getInstance("0123456789 -.")); // modify character set for your case, e.g. add "+()"
(presumably fixed by PR50)
Symptoms:
- You've got a wildcard template like
[________]
, allowing user to write any kind of symbols; - Cursor jumpes to the beggining of the line or to some random position while user input.
In this case text autocorrection & prediction might be a root cause of your problem, as it behaves somewhat weirdly in case when field listener tries to change the text during user input.
If so, consider disabling text suggestions by using corresponding input type:
<EditText
...
android:inputType="textNoSuggestions" />
Additionally be aware that some of the third-party keyboards ignore textNoSuggestions
setting; the recommendation is to use an extra workaround by setting the inputType
to textVisiblePassword
.
In 2.0.0 version separate callbacks onExtracted(String value) onMandatoryCharactersFilled(boolean complete) have been merged into the single callback onTextChanged(boolean maskFilled, @NonNull final String extractedValue)
Be careful while updating dependencies!
The library is distributed under the MIT LICENSE.