Хорошо, теперь я знаю, почему никто не ответил на это - это противно.Я провел много исследований и не мог найти ничего даже отдаленно похожего - возможно, я не очень хорош в поиске.Сначала немного истории моего исследования.Сначала я думал, что могу просто наблюдать за нажатиями клавиш в поле и реагировать на них.На самом деле, нет.Вы можете сделать это с помощью жесткой клавиатуры, но не мягкой.Я попробовал различные методы против устройства Samsung без успеха.Может быть, кто-то знает фокус, но я не смог его найти.Поэтому я выбрал единственный доступный вариант - TextWatcher.Единственная реальная проблема заключается в том, что вы не можете реально увидеть, какая клавиша была нажата, чтобы среагировать (была добавлена цифра или нажата клавиша удаления?), Поэтому вы должны проверить предыдущую строку с текущей измененной строкой и сделать все возможное, чтобы определитьчто изменилось и что с этим делать.
Просто чтобы помочь, поведение, которое я достиг, было в основном для всех пользователей, чтобы вводить числа (0-9) и НЕ изменять другие элементы маски.Также мне нужно было переместить курсор в правильное положение при вводе или удалении элементов.Кроме того, если мы удалили их, нам нужно было удалить соответствующий элемент и вернуть маску обратно.
Например, если маска «ADX - ### - R», то при вводе произойдет следующее:
Given : "ADX-###-R" Typing: "4" Results: "ADX-4##-R" Cursor at "4"
Given : "ADX-4##-R" Typing: "3" Results: "ADX-43#-R" Cursor at "3"
Given : "ADX-43#-R" Typing: "1" Results: "ADX-431-R" Cursor at end of string
Given : "ADX-431-R" Typing: "Del" Results: "ADX-43#-R" Cursor at "3"
В этом и заключается его суть.У нас также есть требование для значений Hint / Placeholder и Default, которые я оставил. Теперь код.
Вот скриншот того, как это выглядит:
![enter image description here](https://i.stack.imgur.com/7DLBr.jpg)
Первый XML:
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true">
<TextView
android:id="@+id/name"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textStyle="normal"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:text="" />
<EditText android:id="@+id/entry"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:singleLine="true"
android:maxLines="1"
android:ellipsize="end" />
<View
android:layout_marginTop="8dp"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@android:color/darker_gray"
android:visibility="gone" />
</LinearLayout>
Основной код поля:
public class FormattedInput extends LinearLayout {
private Context mContext;
private Field mField;
private TextView mName;
private EditText mEntry;
private Boolean mEnableEvents = true;
private String mPlaceholderText = "";
private final static String REPLACE_CHAR = " "; // Replace missing data with blank
public FormattedInput(Context context, Field field) {
super(context);
mContext = context;
mField = field;
initialize();
render(mField);
}
private void initialize() {
LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.field_formatted_input, this);
// setup fields
mName = (TextView)findViewById(R.id.name);
mEntry = (EditText)findViewById(R.id.entry);
mEntry.setFocusable(true);
mEntry.setRawInputType(Configuration.KEYBOARD_QWERTY);
mEntry.addTextChangedListener(
new MaskedWatcher(mField.getDisplayMask())
);
}
public void render(Field field) {
mName.setText(mField.getFieldName());
mPlaceholderText = mField.getPlaceholderText();
if(Utilities.stringIsBlank(mPlaceholderText)) {
mPlaceholderText = mField.getDisplayMask();
}
mEntry.setHint(mPlaceholderText);
mEntry.setHintTextColor(Color.GRAY);
if(!Utilities.stringIsBlank(mField.getValue())) {
mEnableEvents = false;
String value = String.valueOf(mField.getValue());
if (value.equalsIgnoreCase(mField.getDisplayMask()))
mEntry.setText(mField.getDisplayMask());
else {
String val = fillValueWithMask(value, mField.getDisplayMask());
mEntry.setText(val);
}
mEnableEvents = true;
}
else if (!Utilities.stringIsBlank(mField.getDefaultValue())) {
mEnableEvents = false;
String val = fillValueWithMask(mField.getDefaultValue(), mField.getDisplayMask());
mEntry.setText(val);
mEnableEvents = true;
}
else {
mEnableEvents = false;
mEntry.setText(null);
mEnableEvents = true;
}
}
public static String fillValueWithMask(String value, String mask) {
StringBuffer result = new StringBuffer(mask);
for (int i = 0; i < value.length() && i <= mask.length()-1 ; i++){
if (mask.charAt(i) == '#' && value.charAt(i) != ' ' && Character.isDigit(value.charAt(i)))
result.setCharAt(i,value.charAt(i));
}
return result.toString();
}
public class MaskedWatcher implements TextWatcher {
private String mMask;
String mResult = "";
String mPrevResult = "";
int deletePosition = 0;
public MaskedWatcher(String mask){
mMask = mask;
}
public void afterTextChanged(Editable s) {
String value = s.toString();
// No Change, return - or reset of field
if (value.equals(mPrevResult) && (!Utilities.stringIsBlank(value) && !Utilities.stringIsBlank(mPrevResult))) {
return;
}
String diff = value;
// First time in and no value, set value to mask
if (Utilities.stringIsBlank(mPrevResult) && Utilities.stringIsBlank(value)) {
mPrevResult = mMask;
mEntry.setText(mPrevResult);
}
// If time, but have value
else if (Utilities.stringIsBlank(mPrevResult) && !Utilities.stringIsBlank(value)) {
mPrevResult = value;
mEntry.setText(mPrevResult);
}
// Handle other cases of delete and new value, or no more typing allowed
else {
// If the new value is larger or equal than the previous value, we have a new value
if (value.length() >= mPrevResult.length())
diff = Utilities.difference(mPrevResult, value);
// See if new string is smaller, if so it was a delete.
if (value.length() < mPrevResult.length()) {
mPrevResult = removeCharAt(mPrevResult, deletePosition);
// Deleted back to mask, reset
if (mPrevResult.equalsIgnoreCase(mMask)) {
mPrevResult = "";
setFieldValue("");
mEntry.setText("");
mEntry.setHint(mPlaceholderText);
return;
}
// Otherwise set value
else
setFieldValue(mPrevResult);
mEntry.setText(mPrevResult);
}
// A new value was added, add to end
else if (mPrevResult.indexOf('#') != -1) {
mPrevResult = mPrevResult.replaceFirst("#", diff);
mEntry.setText(mPrevResult);
setFieldValue(mPrevResult);
}
// Unallowed change, reset the value back
else {
mEntry.setText(mPrevResult);
}
}
// Move cursor to next spot
int i = mPrevResult.indexOf('#');
if (i != -1)
mEntry.setSelection(i);
else
mEntry.setSelection(mPrevResult.length());
}
private void setFieldValue(String value) {
//mEnableEvents = false;
if(mEnableEvents == false) {
return;
}
// Set the value or do whatever you want to do to save or react to the change
}
private String replaceMask(String str) {
return str.replaceAll("#",REPLACE_CHAR);
}
private String removeCharAt(String str, int pos) {
StringBuilder info = new StringBuilder(str);
// If the position is a mask character, change it, else ignore the change
if (mMask.charAt(pos) == '#') {
info.setCharAt(pos, '#');
return info.toString();
}
else {
Toast.makeText(mContext, "The mask value can't be deleted, only modifiable portion", Toast.LENGTH_SHORT);
return str;
}
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
deletePosition = start;
}
}
}
Код утилиты:
public static boolean stringIsBlank(String stringValue) {
if (stringValue != null) {
return stringValue.trim().length() <= 0;
} else {
return true;
}
}
и
public static String difference(String str1, String str2) {
int at = indexOfDifference(str1, str2);
if (at == -1) {
return "";
}
return str2.substring(at,at+1);
}
И класс поля ... вам нужно будет добавитьгеттеры и сеттеры:
public class Field {
private String defaultValue;
private Object value;
private String displayMask;
private String placeholderText;
}
Несколько заключительных мыслей.Основной механизм заключается в сравнении предыдущей строки с текущей строкой.Если новая строка меньше, то мы удаляем и используем deletePosition
, пока позиция соответствует «#» в маске, поскольку другие символы не могут быть изменены.Существуют также проблемы с появлением предыдущего значения - и предполагается, что если это значение входит в значения «#», то при отсутствии будет заменено на «» (пробелы).Field
не является необходимым, но был вспомогательным классом, который в нашем случае имеет множество других функций.Надеюсь, это кому-нибудь поможет!