Skip to content

Instantly share code, notes, and snippets.

@dkostyrev
Created June 10, 2013 07:40
Show Gist options
  • Save dkostyrev/5747102 to your computer and use it in GitHub Desktop.
Save dkostyrev/5747102 to your computer and use it in GitHub Desktop.
Android EditText with phone pattern support
package com.nemezis.phoneeditsample;
import java.util.ArrayList;
import android.content.Context;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.widget.EditText;
public class MyEditText extends EditText {
ArrayList<TextWatcher> watchers;
public MyEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void addTextChangedListener(TextWatcher watcher) {
if (watchers == null)
watchers = new ArrayList<TextWatcher>();
watchers.add(watcher);
super.addTextChangedListener(watcher);
}
@Override
public void removeTextChangedListener(TextWatcher watcher) {
if (watchers != null) {
int index = watchers.indexOf(watcher);
if (index != -1)
watchers.remove(index);
}
super.removeTextChangedListener(watcher);
}
public void removeAllTextChangedListeners() {
if (watchers == null)
return;
for (TextWatcher watcher : watchers)
super.removeTextChangedListener(watcher);
watchers.clear();
watchers = null;
}
}
package com.nemezis.phoneeditsample;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.content.Context;
import android.text.Editable;
import android.text.InputType;
import android.text.Selection;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
import android.view.KeyEvent;
import android.view.View;
/**
* EditText which can be set according to some pattern
* e.g. +X (XXX) XXX - XX - XX where X - is any number
* any other symbols can not be erased from the field
*/
public class PhoneEditText extends MyEditText {
public static Character NUMBER_CHAR = 'X';
private String pattern;
private boolean erasing;
private boolean forbidSelection = false;
private ArrayList<Pair<Integer, Integer>> insertIndexes;
private String TAG = "com.nemezis.phoneeditsample";
public PhoneEditText(Context context, AttributeSet attrs) {
super(context, attrs);
setInputType(InputType.TYPE_CLASS_PHONE);
}
public void setPattern(String pattern) {
this.pattern = pattern;
setText(pattern.replace(NUMBER_CHAR, ' '));
insertIndexes = getPatternInsertIndexes(pattern);
setSelection(pattern.indexOf(NUMBER_CHAR));
removeAllTextChangedListeners();
addTextChangedListener(new PhoneTextChangedListener(pattern, insertIndexes));
setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(View arg0, int keyCode, KeyEvent arg2) {
if (keyCode == KeyEvent.KEYCODE_DEL)
erasing = true;
return false;
}
});
}
private ArrayList<Pair<Integer, Integer>> getPatternInsertIndexes(String pattern) {
ArrayList<Pair<Integer, Integer> > result = new ArrayList<Pair<Integer, Integer> >();
Pair<Integer, Integer> currentPair = new Pair<Integer, Integer>(0, 0);
boolean found = false;
for (int i = 0; i < pattern.length(); ++i) {
if (pattern.charAt(i) == NUMBER_CHAR && !found) {
currentPair = new Pair<Integer, Integer>(i, 0);
found = true;
}
else if (pattern.charAt(i) != NUMBER_CHAR && found) {
found = false;
currentPair = new Pair<Integer, Integer>(currentPair.first, i);
result.add(currentPair);
} else if (i == pattern.length() - 1 && found) {
currentPair = new Pair<Integer, Integer>(currentPair.first, pattern.length());
result.add(currentPair);
}
}
return result;
}
public String getPattern() {
return this.pattern;
}
public void setForbidSelection(boolean forbid) {
this.forbidSelection = forbid;
}
public boolean getForbidSelection() {
return this.forbidSelection;
}
class PhoneTextChangedListener implements TextWatcher {
private String pattern;
private boolean isEditFromInside = false;
private ArrayList<Pair<Integer, Integer>> insertIndexes;
public PhoneTextChangedListener(String pattern, ArrayList<Pair<Integer, Integer>> insertIndexes) {
this.pattern = pattern;
this.insertIndexes = insertIndexes;
}
@Override
public void afterTextChanged(Editable s) {
if (!isEditFromInside) {
int selection = getSelectionStart();
isEditFromInside = true;
String str = s.toString();
str = resetStrAccordingToPattern(str);
s.clear();
s.append(str);
isEditFromInside = false;
if (!erasing)
setSelection(getSelection(selection));
else
setSelection(getSelectionErasing(selection));
erasing = false;
}
}
private int getSelection(int selection) {
if (selection < insertIndexes.get(0).first)
return insertIndexes.get(0).first;
for (int i = 0; i < insertIndexes.size(); ++i) {
Pair<Integer, Integer> currentPair = insertIndexes.get(i);
if (i != insertIndexes.size() - 1 && selection == currentPair.second)
return insertIndexes.get(i + 1).first;
if (i == insertIndexes.size() - 1 && selection >= currentPair.second)
return currentPair.second;
}
return selection;
}
private int getSelectionErasing(int selection) {
if (selection < insertIndexes.get(0).first)
return insertIndexes.get(0).first;
for (int i = 0; i < insertIndexes.size(); ++i) {
Pair<Integer, Integer> currentPair = insertIndexes.get(i);
if (i != 0 && selection == currentPair.first)
return insertIndexes.get(i - 1).second;
if (i == 0 && selection <= currentPair.first)
return currentPair.first;
}
return selection;
}
private String resetStrAccordingToPattern(String phoneNumber) {
if (TextUtils.isEmpty(phoneNumber))
return phoneNumber;
phoneNumber = toNormalizedForm(phoneNumber, pattern.indexOf(PhoneEditText.NUMBER_CHAR));
String patternFormat = pattern.replaceAll("(X+)", "%s");
Pattern regex = Pattern.compile("(X+)", Pattern.CASE_INSENSITIVE);
Matcher matcher = regex.matcher(pattern);
matcher.matches();
ArrayList<String> substrings = new ArrayList<String>();
int lastGroup = 0, index = 0;
while (matcher.find()) {
int currentGroup = matcher.group(index + 1).length();
char[] currentSubstring = new char[currentGroup];
for (int i = 0; i < currentSubstring.length; ++i) {
if (lastGroup + i < phoneNumber.length()) {
currentSubstring[i] = phoneNumber.charAt(lastGroup + i);
} else currentSubstring[i] = ' ';
}
lastGroup += currentGroup;
substrings.add(new String(currentSubstring));
//index++;
}
return String.format(patternFormat, substrings.toArray());
}
private String toNormalizedForm(String phoneNumber, int startIndex) {
if (TextUtils.isEmpty(phoneNumber))
return phoneNumber;
if (startIndex != -1) {
phoneNumber = phoneNumber.substring(startIndex).trim();
phoneNumber = phoneNumber.replaceAll("[^0-9+]", "");
}
return phoneNumber;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment