Created
June 10, 2013 07:40
-
-
Save dkostyrev/5747102 to your computer and use it in GitHub Desktop.
Android EditText with phone pattern support
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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