#Mention using Multiautocompletetextview - android

What i want to do
i want to mention both people and group in a chat activity.when the user types # i want populate the list which contains no of user and channels
so that he can mention both people and channels
What i have done
i took help from this question Android: Autocomplete TextView Similar To The Facebook App
here i have included my code
1.Arraylist which has users
ArrayList<People> users = new ArrayList<User>();
for (People user : SocketSingleton.userMap.values()) {
if (user.getId() != loggedUserId) {
users.add(user);
}
}
2.Arraylist which has groups list
ArrayList<Group> groups = new ArrayList<Group>();
for (Group channel : SocketSingleton.listgroups.values()) {
groups.add(channel);
}
3.Adapters for view
final UserAdapter Adapter = new UserAdapter(getActivity(), R.layout.all_user_list_item, users);
final GroupAdapter Adapter1 = new GroupAdapter(getActivity(), R.layout.all_groups_list_item, Groups);
4.MultiAutocompletetextview
textinput = (MultiAutoCompleteTextView) view.findViewById(R.id.message_input);
textinput.setAdapter(Adapter);
textinput.setThreshold(0);
textinput.setTokenizer(new MultiAutoCompleteTextView.Tokenizer() {
#Override
public CharSequence terminateToken(CharSequence text) {
int i = text.length();
while (i > 0 && text.charAt(i - 1) == ' ') {
i--;
}
if (i > 0 && text.charAt(i - 1) == ' ') {
return text;
} else {
if (text instanceof Spanned) {
SpannableString sp = new SpannableString(text + " ");
TextUtils.copySpansFrom((Spanned) text, 0, text.length(), Object.class, sp, 0);
return sp;
} else {
return text + " ";
}
}
}
#Override
public int findTokenStart(CharSequence text, int cursor) {
int i = cursor;
while (i > 0 && text.charAt(i - 1) != '#') {
i--;
}
//Check if token really started with #, else we don't have a valid token
if (i < 1 || text.charAt(i - 1) != '#') {
return cursor;
}
return i;
}
#Override
public int findTokenEnd(CharSequence text, int cursor) {
int i = cursor;
int len = text.length();
while (i < len) {
if (text.charAt(i) == ' ') {
return i;
} else {
i++;
}
}
return len;
}
});
textinput.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
Layout layout = textinput.getLayout();
int pos = textinput.getSelectionStart();
int line = layout.getLineForOffset(pos);
int baseline = layout.getLineBaseline(line);
int bottom = textinput.getHeight();
textinput.setDropDownVerticalOffset(baseline - bottom);
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void afterTextChanged(Editable s) {
}
});
textinput.setOnEditorActionListener(new TextView.OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView v, int id, KeyEvent event) {
if (id == R.id.send || id == EditorInfo.IME_NULL) {
messageSend();
return true;
}
return false;
}
});
Problem i have
with this code i can able to mention only people. i can only set either adapter1 or adapter2 for Autocomplete tectview. So it is only displaying either users or groups.
i want to display all list in single adapter so that the user can mention peoples and groups
i am new to this please help me to find a way

There is no built in support for multiple data types with MultiAutoCompleteTextView. However there are several open source libraries that allow you to do just that.
I've had the same requirement in the project I'm working on and here is our open source solution, feel free to check it out:
https://github.com/Teamwork/android-multiautocomplete
You can achieve that by doing this:
MultiAutoComplete autoComplete = new MultiAutoComplete.Builder()
...
.addTypeAdapter(typeAdapter1)
.addTypeAdapter(typeAdapter2)
.build();
You can also specify the type of filter and handle ('#' or '#' or any other) you want to use for each type adapter.

Related

In between of EditText how to use autocomplete android

I know how to use EditText in android and how to use AutoComplete EditText in android. But AutoComplete EditText is used like dropdown list where user type a character and suggestions will be displayed and user clicks on one of the suggestions. If there is no suggestion then it won't display any text.
My requirement is like, Consider there is an EditText. User can type anything they want, but when the word starts from special character like for example "#" then the api will hit and display suggestions.
Complete Example:
"My Name is James Bond and I am #And"
In above example when user type this sentence at the end "#And" it will show the auto suggestion from hitting the API(should display Android) and user can select this suggested word from list.
Is there any way to achieve this??
Please Do help me.
Thank You.
Android provides MultiAutoCompleteTextView. Use this widget instead of EditText and override its tokenizer.
MultiAutoCompleteTextView myautocomplete = (MultiAutoCompleteTextView) findViewById(R.id.multy);
ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, array_for_autocomplete);
myautocomplete.setAdapter(adapter);
myautocomplete.setTokenizer(new MultiAutoCompleteTextView.Tokenizer() {
#Override
public int findTokenStart(CharSequence text, int cursor) {
int i = cursor;
while (i > 0 && text.charAt(i - 1) != '#') {
i--;
}
while (i < cursor && text.charAt(i) == ' ') {
i++;
}
return i;
}
#Override
public int findTokenEnd(CharSequence text, int cursor) {
int i = cursor;
int len = text.length();
while (i < len) {
if (text.charAt(i) == ',') {
return i;
} else {
i++;
}
}
return len;
}
#Override
public CharSequence terminateToken(CharSequence text) {
int i = text.length();
while (i > 0 && text.charAt(i - 1) == ' ') {
i--;
}
if (i > 0 && text.charAt(i - 1) == ',') {
return text;
} else {
if (text instanceof Spanned) {
SpannableString sp = new SpannableString(text + ", ");
TextUtils.copySpansFrom((Spanned) text, 0, text.length(),
Object.class, sp, 0);
return sp;
} else {
return text + ", ";
}
}
}
});
Documentation : http://developer.android.com/reference/android/widget/MultiAutoCompleteTextView.html

How to show dropdown only when inserting # character on MultiAutoCompleteTextView in android

I have a MultiAutoCompleteTextView. It works fine. But I want to show suggestion dropdown only when user type # on it (like tagging user in facebook app). I have no idea how to do it. Here is my code :
mChatbox = (MultiAutoCompleteTextView) findViewById(R.id.chatbox);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,android.R.layout.simple_dropdown_item_1line, userList);
mChatBox.setAdapter(adapter);
mChatBox.setTokenizer(new SpaceTokenizer());
public class SpaceTokenizer implements MultiAutoCompleteTextView.Tokenizer {
public int findTokenStart(CharSequence text, int cursor) {
int i = cursor;
while (i > 0 && text.charAt(i - 1) != ' ') {
i--;
}
while (i < cursor && text.charAt(i) == ' ') {
i++;
}
return i;
}
public int findTokenEnd(CharSequence text, int cursor) {
int i = cursor;
int len = text.length();
while (i < len) {
if (text.charAt(i) == ' ') {
return i;
} else {
i++;
}
}
return len;
}
public CharSequence terminateToken(CharSequence text) {
int i = text.length();
while (i > 0 && text.charAt(i - 1) == ' ') {
i--;
}
if (i > 0 && text.charAt(i - 1) == ' ') {
return text;
} else {
if (text instanceof Spanned) {
SpannableString sp = new SpannableString(text + " ");
TextUtils.copySpansFrom((Spanned) text, 0, text.length(),
Object.class, sp, 0);
return sp;
} else {
return text + " ";
}
}
}
Create a custom Text view extending MultiAutoCompleteTextView -> override enoughToFilter() -> set the threshold to 0 (the bold variable in the below given code) :
public boolean enoughToFilter() {
Editable text = getText();
int end = getSelectionEnd();
if (end < 0 || mTokenizer == null) {
return false;
}
int start = mTokenizer.findTokenStart(text, end);
if (end - start >= mThreshold && start != -1) {
return true;
} else {
return false;
}
}
Using this code you'll see the auto suggested list on press of #
If you want to detect your string starts with '#' for mention (tag) someone or '#' for hashTag, then do query or filter with it, you could follow this code belows:
#Override
public void onTextChanged(CharSequence s, int start, int before, final int count) {
if (s.length() > 0) {
// Todo: query mentions
Matcher mentionMatcher = Pattern.compile("#([A-Za-z0-9_-]+)").matcher(s.toString());
// while matching
while (mentionMatcher.find()) {
yourSearchText = s.toString().substring(mentionMatcher.start() + 1, mentionMatcher.end());
// do query with yourSearchText below
}
}
}
It references from the link Multiautocompletetextview, Show autocomplete drop down only when user presses a key after '#' key (like mention in FB app) please scroll down to find #Phuong Sala answer.
I got a solution by myself. I create custom view which extends MultiAutoCompleteTextView and override performFiltering in it. Check if first char is "#", then filter the next chars after it. Otherwise, replace chars with "*" to avoid filtering. Here is my code.
#Override
protected void performFiltering(CharSequence text, int start, int end, int keyCode) {
if (text.charAt(start) == '#') {
start = start + 1;
} else {
text = text.subSequence(0, start);
for (int i = start; i < end; i++) {
text = text + "*";
}
}
super.performFiltering(text, start, end, keyCode);
}

How do I get MultiAutoCompleteTextView tokenizer similar to Facebook app?

I am creating an application which has a 'To' field just like in Facebook app's "New Message" feature.
After selecting an item from the drop down list, I create an imagespan and add it to the MultiAutoCompleteTextView. I have used SpaceTokenizer for this view . The problem is when I click on backspace, the cursor first moves to the empty space (i.e., space Tokenizer) and then when I click on the backspace again, the whole word gets deleted....I want to delete the whole word on my first click of backspace just like facebook app...
Here is my code for SpaceTokenizer
multiContentText.setTokenizer(new Tokenizer(){
public int findTokenStart(CharSequence text, int cursor) {
int i = cursor;
if(i>0){
Log.d("textchar ",""+text.charAt(i - 1));
}
while (i > 0 && text.charAt(i - 1) != ' ') {
i--;
}
while (i < cursor && text.charAt(i) == ' ' || text.charAt(i - 1) == '\n') {
i++;
}
return i;
}
public int findTokenEnd(CharSequence text, int cursor) {
int i = cursor;
int len = text.length();
while (i < len) {
if (text.charAt(i) == ' ' || text.charAt(i - 1) == '\n') {
return i;
} else {
i++;
}
}
return len;
}
public CharSequence terminateToken(CharSequence text) {
int i = text.length();
while (i > 0 && text.charAt(i - 1) == ' ' || text.charAt(i - 1) == '\n') {
i--;
}
if (i > 0 && text.charAt(i - 1) == ' ' || text.charAt(i - 1) == '\n') {
return text;
} else {
if (text instanceof Spanned) {
SpannableString sp = new SpannableString(text + " ");
TextUtils.copySpansFrom((Spanned) text, 0, text.length(),
Object.class, sp, 0);
return sp;
} else {
return text+" ";
}
}
}
});
I am using this code to create a TextView in my multi-ContentText
SpannableStringBuilder ssb = new SpannableStringBuilder(multiContentText.getText());
String c="text from the list";
TextView textView = (TextView) inflater.inflate(R.layout.chips_edittext, null);
textView.setText(c); // set text
int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
textView.measure(spec, spec);
textView.layout(0, 0, textView.getMeasuredWidth(), textView.getMeasuredHeight());
Bitmap b = Bitmap.createBitmap(textView.getWidth(), textView.getHeight(),Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(b);
canvas.translate(-textView.getScrollX(), -textView.getScrollY());
textView.draw(canvas);
textView.setDrawingCacheEnabled(true);
Bitmap cacheBmp = textView.getDrawingCache();
Bitmap viewBmp = cacheBmp.copy(Bitmap.Config.ARGB_8888, true);
textView.destroyDrawingCache(); // destory drawable
// create bitmap drawable for imagespan
BitmapDrawable bmpDrawable = new BitmapDrawable(viewBmp);
bmpDrawable.setBounds(0, 0,bmpDrawable.getIntrinsicWidth(),bmpDrawable.getIntrinsicHeight());
// create and set imagespan
ssb.setSpan(new ImageSpan(bmpDrawable),0 ,c.length() , Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// set chips span
multiContentText.setText(ssb);
multiContentText.setSelection(multiContentText.getText().length());
I am not sure whether the space Tokenizer is the right option for this type of behavior...Any help or pointers will be grateful...
Here is the screenshot for better understanding....
I have a text followed by a space and then a cursor...If I hit backspace, it first moves to the empty space and only when I hit backspace again the whole text is deleted....
Here is the another screenshot ..
Here the cursor is not exactly in between the two TextViews unlike in facebook app which again causes some issues in inserting the text...
Found the solution....
Add this textwatcher to the multiautocompletetextview
private TextWatcher textWather = new TextWatcher() {
int noOfCharAdded=0;int noOfCharDeleted=0;
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
startIdx=start;
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,int after) {
noOfCharAdded=after;
noOfCharDeleted=count;
}
#Override
public void afterTextChanged(Editable s) {
Editable buffer = s;
int start = multiContentText.getSelectionStart()<0?0:multiContentText.getSelectionStart();
int end = multiContentText.getSelectionEnd()<0?0:multiContentText.getSelectionEnd();
if(noOfCharAdded==0 && noOfCharDeleted==1){ //if space is deleted
if (start == end && delPrevText) {
ImageSpan link[] = buffer.getSpans(start, end,ImageSpan.class);
if (link.length > 0) {
buffer.replace(buffer.getSpanStart(link[0]),buffer.getSpanEnd(link[0]),"");
buffer.removeSpan(link[0]);
}
}
delPrevText=true;
multiContentText.setSelection(multiContentText.getText().length());
}
else if(noOfCharAdded==0 && noOfCharDeleted>1){//if the whole word is deleted
if(buffer.length()>0){
if(start<buffer.length()){
delPrevText=false;
if(buffer.charAt(start)==' '){
buffer.replace(start,start+1,"");
}
}
}
}
}
};
Try adding a TextWatcher to the MultiAutoCompleteTextView.
Save the current text and check if the last space was deleted.
If so, remove the last token.
Editable buffer = s;
int start = multiContentText.getSelectionStart()<0?0:multiContentText.getSelectionStart();
int end = multiContentText.getSelectionEnd()<0?0:multiContentText.getSelectionEnd();
if(noOfCharAdded==0 && noOfCharDeleted==1){ //if space is deleted
if (start == end && delPrevText) {
ImageSpan link[] = buffer.getSpans(start, end,ImageSpan.class);
if (link.length > 0) {
for(int i=0;i<contentArray.size();i++){
JSONObject jo=contentArray.get(i);
try {
int keyValue=jo.getInt("startIndx");//No i18N
if(keyValue==buffer.getSpanStart(link[0])){
jo.put("isRemoved", true);
contentArray.set(i,jo);
}
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
buffer.replace(buffer.getSpanStart(link[0]),buffer.getSpanEnd(link[0]),"");
buffer.removeSpan(link[0]);
}
}
delPrevText=true;
multiContentText.setSelection(multiContentText.getText().length());
}
else if(noOfCharAdded==0 && noOfCharDeleted>1){//if the whole word is deleted
if(buffer.length()>0){
if(start<buffer.length()){
delPrevText=false;
for(int i=0;i<contentArray.size();i++){
JSONObject jo=contentArray.get(i);
try {
int keyValue=jo.getInt("startIndx");//No i18N
if(keyValue==start){
jo.put("isRemoved", true);
contentArray.set(i,jo);
}
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(buffer.charAt(start)==' '){
buffer.replace(start,start+1,"");
}
}
}
}

Automatically add dash in phone number in Android

Instead of 5118710, it should be 511-8710. I'd like to add a dash after the user the user inputted 3 digits already in the EditText. The maximum length of the EditText is 7 digits only.
After I figured out the above problem, I've got stuck in coding again. When I already inputted 3 digits, it appends dash (that's what I'd like to happen) but my problem here is that the next 3 digits also appends dash (Like this: 511-871-)... Please help me with this. thanks!
txt_HomeNo.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
boolean flag = true;
String eachBlock[] = txt_HomeNo.getText().toString().split("-");
for (int i = 0; i < eachBlock.length; i++) {
if (eachBlock[i].length() > 3) {
flag = false;
}
}
if (flag) {
txt_HomeNo.setOnKeyListener(new OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DEL)
keyDel = 1;
return false;
}
});
if (keyDel == 0) {
if (((txt_HomeNo.getText().length() + 1) % 4) == 0) {
if (txt_HomeNo.getText().toString().split("-").length <= 3) {
txt_HomeNo.setText(txt_HomeNo.getText() + "-");
txt_HomeNo.setSelection(txt_HomeNo.getText().length());
}
}
a = txt_HomeNo.getText().toString();
} else {
a = txt_HomeNo.getText().toString();
keyDel = 0;
}
} else {
txt_HomeNo.setText(a);
}
}
The most straightforward solution is to use PhoneNumberFormattingTextWatcher which will format the number according to the system locale.
XML:
<EditText
android:id="#+id/phone_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/enter_phone_number"
android:inputType="phone" />
Add addTextChangedListener() in your class:
EditText phoneNumber = (EditText)findViewById(R.id.phone_number);
phoneNumber.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
Implement the following modified addTextChangedListener for txt_HomeNo. The code below is checking if the length of the text entered is 3 and if it is then add the - to it. Not a very robust solution but it works!
txt_HomeNo.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
txt_HomeNo.setOnKeyListener(new OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DEL)
keyDel = 1;
return false;
}
});
if (keyDel == 0) {
int len = txt_HomeNo.getText().length();
if(len == 3) {
txt_HomeNo.setText(txt_HomeNo.getText() + "-");
txt_HomeNo.setSelection(txt_HomeNo.getText().length());
}
} else {
keyDel = 0;
}
}
#Override
public void afterTextChanged(Editable arg0) {
// TODO Auto-generated method stub
}
#Override
public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
// TODO Auto-generated method stub
}
});
I have a few small changes to the solution of neo108 so it can work with both soft keyboard and hard keyboard, in my code for example the edittext will follow the rule to automatically add " " at position 5 and 9.
txtPhone.addTextChangedListener(new TextWatcher() {
int keyDel;
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
txtPhone.setOnKeyListener(new View.OnKeyListener() {
#Override
public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
if (keyCode == KeyEvent.KEYCODE_DEL) {
keyDel = 1;
}
return false;
}
});
String currentString = txtPhone.getText().toString();
int currentLength = txtPhone.getText().length();
if (currentLength == 5 || currentLength == 9) {
keyDel = 1;
}
if (keyDel == 0) {
if (currentLength == 4 || currentLength == 8) {
txtPhone.setText(txtPhone.getText() + " ");
txtPhone.setSelection(txtPhone.getText().length());
}
} else {
if (currentLength != 5 && currentLength != 9) {
keyDel = 0;
} else if ((currentLength == 5 || currentLength == 9)
&& !" ".equals(currentString.substring(currentLength - 1, currentLength))) {
txtPhone.setText(currentString.substring(0, currentLength - 1) + " "
+ currentString.substring(currentLength - 1, currentLength));
txtPhone.setSelection(txtPhone.getText().length());
}
}
}
I implemented a custom TextWatcher; this handles 10 and 11 digit phone numbers (i.e. 1-555-867-5309 and 555-867-5309). Allows adds, deletions, inserts, mass removal while maintaining proper cursor position.
public class CustomPhoneTextWatcher implements TextWatcher {
private final EditText editText;
private String previousString;
public CustomPhoneTextWatcher(EditText editText) {
this.editText = editText;
}
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
// if the previous editable ends with a dash and new is shorter than previous
// additionally remove preceding character
if (previousString != null && previousString.endsWith("-") && editable.toString().length() < previousString.length()) {
previousString = editable.toString();
String removedCharacterPriorToDash = editable.toString().substring(0, editable.length() - 1);
editText.setText(removedCharacterPriorToDash);
int position = editText.length();
Editable etext = editText.getText();
Selection.setSelection(etext, position);
} else {
previousString = editable.toString();
String numericString = StringUtils.removeNonnumeric(editable.toString());
int stringLength = numericString.length();
boolean startsWithOne = numericString.startsWith("1");
numericString = numericString.substring(0, Math.min(stringLength, 10 + (startsWithOne ? 1 : 0)));
int lastHyphenIndex = 6 + (startsWithOne ? 1 : 0);
int secondToLastHyphenIndex = 3 + (startsWithOne ? 1 : 0);
if (stringLength >= lastHyphenIndex) {
numericString = numericString.substring(0, lastHyphenIndex) + "-" + numericString.substring(lastHyphenIndex, numericString.length());
}
if (stringLength >= secondToLastHyphenIndex) {
numericString = numericString.substring(0, secondToLastHyphenIndex) + "-" + numericString.substring(secondToLastHyphenIndex, numericString.length());
}
if (numericString.startsWith("1")) {
numericString = numericString.substring(0, 1) + "-" + numericString.substring(1, numericString.length());
}
if (!numericString.equals(editable.toString())) {
editText.setText(numericString);
int position = editText.length();
Editable etext = editText.getText();
Selection.setSelection(etext, position);
}
}
}
}
StringUtils.removeNonnumeric(editable.toString()) is a call to this method:
public static String removeNonnumeric(String text) {
return text.replaceAll("[^\\d]", "");
}
Thanks for the all above answer.
The editText.setOnKeyListener() will never invoke when your device has only soft keyboard.
If we strictly follow the rule to add "-", then this code not always show desire result.
editText.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
but above code is best solution for formatting phone no.
Apart from above this solution, I write a code which work on all types of condition::
phoneNumber.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
if (len > phoneNumber.getText().length() ){
len--;
return;
}
len = phoneNumber.getText().length();
if (len == 4 || len== 8) {
String number = phoneNumber.getText().toString();
String dash = number.charAt(number.length() - 1) == '-' ? "" : "-";
number = number.substring(0, (len - 1)) + dash + number.substring((len - 1), number.length());
phoneNumber.setText(number);
phoneNumber.setSelection(number.length());
}
}
});
this line of code required to add "-" on 3rd & 6th position of number.
if (len == 4 || len== 8)
Do it yourself by using OnEditTextChangedListener and insert dash by counting number of chars, Counting Chars in EditText Changed Listener
import android.text.Editable;
import android.text.Selection;
import android.text.TextWatcher;
import android.util.Log;
import android.widget.EditText;
/**
* Auto-formats a number using -.
* Ex. 303-333-3333
* Ex. 1-303-333-3333
* Doesn't allow deletion of just -
*/
public class PhoneNumberFormattingTextWatcher implements TextWatcher {
private static final String TAG = "PhoneNumberTextWatcher";
private final EditText editText;
private String previousNumber;
/**
* Indicates the change was caused by ourselves.
*/
private boolean mSelfChange = false;
public PhoneNumberFormattingTextWatcher(EditText editText) {
this.editText = editText;
}
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
// if the previous editable ends with a dash and new is shorter than previous
// additionally remove preceding character
Log.i(TAG, "Previous String: " + previousNumber);
//if self change ignore
if (mSelfChange) {
Log.i(TAG, "Ignoring self change");
mSelfChange = false;
return;
}
String phoneNumber = removeNonnumeric(editable.toString());
int stringLength = phoneNumber.length();
//empty case
if(stringLength == 0) {
mSelfChange = true;
editText.setText("");
return;
}
boolean startsWithOne = phoneNumber.charAt(0) == '1';
int maxLength = 10 + (startsWithOne ? 1 : 0);
//too large
if(stringLength > maxLength) {
Log.i(TAG, "String length is greater than max allowed, using previous string: " + previousNumber);
mSelfChange = true;
editText.setText(previousNumber);
Editable etext = editText.getText();
Selection.setSelection(etext, previousNumber.length());
return;
}
phoneNumber = formatPhoneNumber(phoneNumber);
if(previousNumber != null && phoneNumber.length() == previousNumber.length()) {
//user deleting last character, and it is a -
if(phoneNumber.endsWith("-")) {
phoneNumber = phoneNumber.substring(0, phoneNumber.length()-2);
}
}
mSelfChange = true;
previousNumber = phoneNumber;
editText.setText(phoneNumber);
Editable etext = editText.getText();
Selection.setSelection(etext, phoneNumber.length());
}
private String formatPhoneNumber(String phoneNumber) {
int stringLength = phoneNumber.length();
//check if starts with 1, if it does, dash index is increased by 1
boolean startsWithOne = phoneNumber.charAt(0) == '1';
//if the length of the string is 6, add another dash
int lastHyphenIndex = 6 + (startsWithOne ? 1 : 0);
if (stringLength >= lastHyphenIndex) {
phoneNumber = phoneNumber.substring(0, lastHyphenIndex) + "-" + phoneNumber.substring(lastHyphenIndex, phoneNumber.length());
}
//if the length of the string is 3, add a dash
int secondToLastHyphenIndex = 3 + (startsWithOne ? 1 : 0);
if (stringLength >= secondToLastHyphenIndex) {
phoneNumber = phoneNumber.substring(0, secondToLastHyphenIndex) + "-" + phoneNumber.substring(secondToLastHyphenIndex, phoneNumber.length());
}
//If the number starts with 1, add a dash after 1
if (phoneNumber.startsWith("1")) {
phoneNumber = phoneNumber.substring(0, 1) + "-" + phoneNumber.substring(1, phoneNumber.length());
}
return phoneNumber;
}
private static String removeNonnumeric(String text) {
return text.replaceAll("[^\\d]", "");
}
}

Phone number formatting an EditText in Android

I am making a simple Address Book app (targeting 4.2) that takes name, address, city, state, zip and phone.
I want to format the phone number input as a phone number (XXX) XXX-XXXX, but I need to pull the value out as a string so I can store it in my database when I save. How can i do this??
I have the EditText set for "phone number" input but that obviously doesn't do too much.
Simply use the PhoneNumberFormattingTextWatcher, just call:
editText.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
Addition
To be clear, PhoneNumberFormattingTextWatcher's backbone is the PhoneNumberUtils class. The difference is the TextWatcher maintains the EditText while you must call PhoneNumberUtils.formatNumber() every time you change its contents.
There is a library called PhoneNumberUtils that can help you to cope with phone number conversions and comparisons. For instance, use ...
EditText text = (EditText) findViewById(R.id.editTextId);
PhoneNumberUtils.formatNumber(text.getText().toString())
... to format your number in a standard format.
PhoneNumberUtils.compare(String a, String b);
... helps with fuzzy comparisons. There are lots more. Check out http://developer.android.com/reference/android/telephony/PhoneNumberUtils.html for more.
p.s. setting the the EditText to phone is already a good choice; eventually it might be helpful to add digits e.g. in your layout it looks as ...
<EditText
android:id="#+id/editTextId"
android:inputType="phone"
android:digits="0123456789+"
/>
Simply Use This :
In Java Code :
editText.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
In XML Code :
<EditText
android:id="#+id/etPhoneNumber"
android:inputType="phone"/>
This code work for me. It'll auto format when text changed in edit text.
I've recently done a similar formatting like 1 (XXX) XXX-XXXX for Android EditText. Please find the code below. Just use the TextWatcher sub-class as the text changed listener :
....
UsPhoneNumberFormatter addLineNumberFormatter = new UsPhoneNumberFormatter(
new WeakReference<EditText>(mYourEditText));
mYourEditText.addTextChangedListener(addLineNumberFormatter);
...
private class UsPhoneNumberFormatter implements TextWatcher {
//This TextWatcher sub-class formats entered numbers as 1 (123) 456-7890
private boolean mFormatting; // this is a flag which prevents the
// stack(onTextChanged)
private boolean clearFlag;
private int mLastStartLocation;
private String mLastBeforeText;
private WeakReference<EditText> mWeakEditText;
public UsPhoneNumberFormatter(WeakReference<EditText> weakEditText) {
this.mWeakEditText = weakEditText;
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
if (after == 0 && s.toString().equals("1 ")) {
clearFlag = true;
}
mLastStartLocation = start;
mLastBeforeText = s.toString();
}
#Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
// TODO: Do nothing
}
#Override
public void afterTextChanged(Editable s) {
// Make sure to ignore calls to afterTextChanged caused by the work
// done below
if (!mFormatting) {
mFormatting = true;
int curPos = mLastStartLocation;
String beforeValue = mLastBeforeText;
String currentValue = s.toString();
String formattedValue = formatUsNumber(s);
if (currentValue.length() > beforeValue.length()) {
int setCusorPos = formattedValue.length()
- (beforeValue.length() - curPos);
mWeakEditText.get().setSelection(setCusorPos < 0 ? 0 : setCusorPos);
} else {
int setCusorPos = formattedValue.length()
- (currentValue.length() - curPos);
if(setCusorPos > 0 && !Character.isDigit(formattedValue.charAt(setCusorPos -1))){
setCusorPos--;
}
mWeakEditText.get().setSelection(setCusorPos < 0 ? 0 : setCusorPos);
}
mFormatting = false;
}
}
private String formatUsNumber(Editable text) {
StringBuilder formattedString = new StringBuilder();
// Remove everything except digits
int p = 0;
while (p < text.length()) {
char ch = text.charAt(p);
if (!Character.isDigit(ch)) {
text.delete(p, p + 1);
} else {
p++;
}
}
// Now only digits are remaining
String allDigitString = text.toString();
int totalDigitCount = allDigitString.length();
if (totalDigitCount == 0
|| (totalDigitCount > 10 && !allDigitString.startsWith("1"))
|| totalDigitCount > 11) {
// May be the total length of input length is greater than the
// expected value so we'll remove all formatting
text.clear();
text.append(allDigitString);
return allDigitString;
}
int alreadyPlacedDigitCount = 0;
// Only '1' is remaining and user pressed backspace and so we clear
// the edit text.
if (allDigitString.equals("1") && clearFlag) {
text.clear();
clearFlag = false;
return "";
}
if (allDigitString.startsWith("1")) {
formattedString.append("1 ");
alreadyPlacedDigitCount++;
}
// The first 3 numbers beyond '1' must be enclosed in brackets "()"
if (totalDigitCount - alreadyPlacedDigitCount > 3) {
formattedString.append("("
+ allDigitString.substring(alreadyPlacedDigitCount,
alreadyPlacedDigitCount + 3) + ") ");
alreadyPlacedDigitCount += 3;
}
// There must be a '-' inserted after the next 3 numbers
if (totalDigitCount - alreadyPlacedDigitCount > 3) {
formattedString.append(allDigitString.substring(
alreadyPlacedDigitCount, alreadyPlacedDigitCount + 3)
+ "-");
alreadyPlacedDigitCount += 3;
}
// All the required formatting is done so we'll just copy the
// remaining digits.
if (totalDigitCount > alreadyPlacedDigitCount) {
formattedString.append(allDigitString
.substring(alreadyPlacedDigitCount));
}
text.clear();
text.append(formattedString.toString());
return formattedString.toString();
}
}
Maybe below sample project helps you;
https://github.com/reinaldoarrosi/MaskedEditText
That project contains a view class call MaskedEditText. As first, you should add it in your project.
Then you add below xml part in res/values/attrs.xml file of project;
<resources>
<declare-styleable name="MaskedEditText">
<attr name="mask" format="string" />
<attr name="placeholder" format="string" />
</declare-styleable>
</resources>
Then you will be ready to use MaskedEditText view.
As last, you should add MaskedEditText in your xml file what you want like below;
<packagename.currentfolder.MaskedEditText
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/maskedEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:text="5"
app:mask="(999) 999-9999"
app:placeholder="_" >
Of course that, you can use it programmatically.
After those steps, adding MaskedEditText will appear like below;
As programmatically, if you want to take it's text value as unmasked, you may use below row;
maskedEditText.getText(true);
To take masked value, you may send false value instead of true value in the getText method.
You need to create a class:
public class PhoneTextFormatter implements TextWatcher {
private final String TAG = this.getClass().getSimpleName();
private EditText mEditText;
private String mPattern;
public PhoneTextFormatter(EditText editText, String pattern) {
mEditText = editText;
mPattern = pattern;
//set max length of string
int maxLength = pattern.length();
mEditText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength)});
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
StringBuilder phone = new StringBuilder(s);
Log.d(TAG, "join");
if (count > 0 && !isValid(phone.toString())) {
for (int i = 0; i < phone.length(); i++) {
Log.d(TAG, String.format("%s", phone));
char c = mPattern.charAt(i);
if ((c != '#') && (c != phone.charAt(i))) {
phone.insert(i, c);
}
}
mEditText.setText(phone);
mEditText.setSelection(mEditText.getText().length());
}
}
#Override
public void afterTextChanged(Editable s) {
}
private boolean isValid(String phone)
{
for (int i = 0; i < phone.length(); i++) {
char c = mPattern.charAt(i);
if (c == '#') continue;
if (c != phone.charAt(i)) {
return false;
}
}
return true;
}
}
Use this as follows:
phone = view.findViewById(R.id.phone);
phone.addTextChangedListener(new PhoneTextFormatter(phone, "+7 (###) ###-####"));
If you're only interested in international numbers and you'd like to be able to show the flag of the country that matches the country code in the input, I wrote a small library for that:
https://github.com/tfcporciuncula/phonemoji
Here's how it looks:
Follow the instructions in this Answer to format the EditText mask.
https://stackoverflow.com/a/34907607/1013929
And after that, you can catch the original numbers from the masked string with:
String phoneNumbers = maskedString.replaceAll("[^\\d]", "");
//(123) 456 7890 formate set
private int textlength = 0;
public class MyPhoneTextWatcher implements TextWatcher {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
String text = etMobile.getText().toString();
textlength = etMobile.getText().length();
if (text.endsWith(" "))
return;
if (textlength == 1) {
if (!text.contains("(")) {
etMobile.setText(new StringBuilder(text).insert(text.length() - 1, "(").toString());
etMobile.setSelection(etMobile.getText().length());
}
} else if (textlength == 5) {
if (!text.contains(")")) {
etMobile.setText(new StringBuilder(text).insert(text.length() - 1, ")").toString());
etMobile.setSelection(etMobile.getText().length());
}
} else if (textlength == 6 || textlength == 10) {
etMobile.setText(new StringBuilder(text).insert(text.length() - 1, " ").toString());
etMobile.setSelection(etMobile.getText().length());
}
}
#Override
public void afterTextChanged(Editable editable) {
}
}
More like clean:
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
String text = etyEditText.getText();
int textlength = etyEditText.getText().length();
if (text.endsWith("(") ||text.endsWith(")")|| text.endsWith(" ") || text.endsWith("-") )
return;
switch (textlength){
case 1:
etyEditText.setEditText(new StringBuilder(text).insert(text.length() - 1, "(").toString());
etyEditText.setSelection(etyEditText.getText().length());
break;
case 5:
etyEditText.setEditText(new StringBuilder(text).insert(text.length() - 1, ")").toString());
etyEditText.setSelection(etyEditText.getText().length());
break;
case 6:
etyEditText.setEditText(new StringBuilder(text).insert(text.length() - 1, " ").toString());
etyEditText.setSelection(etyEditText.getText().length());
break;
case 10:
etyEditText.setEditText(new StringBuilder(text).insert(text.length() - 1, "-").toString());
etyEditText.setSelection(etyEditText.getText().length());
break;
}
}
You can use spawns to format phone numbers in Android. This solution is better than the others because it does not change input text. Formatting remains purely visual.
implementation 'com.googlecode.libphonenumber:libphonenumber:7.0.4'
Formatter class:
open class PhoneNumberFormatter : TransformationMethod {
private val mFormatter: AsYouTypeFormatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(Locale.getDefault().country)
override fun getTransformation(source: CharSequence, view: View): CharSequence {
val formatted = format(source)
if (source is Spannable) {
setSpans(source, formatted)
return source
}
return formatted
}
override fun onFocusChanged(view: View?, sourceText: CharSequence?, focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) = Unit
private fun setSpans(spannable: Spannable, formatted: CharSequence): CharSequence {
spannable.clearSpawns()
var charterIndex = 0
var formattedIndex = 0
var spawn = ""
val spawns: List<String> = spannable
.map {
spawn = ""
charterIndex = formatted.indexOf(it, formattedIndex)
if (charterIndex != -1){
spawn = formatted.substring(formattedIndex, charterIndex-1)
formattedIndex = charterIndex+1
}
spawn
}
spawns.forEachIndexed { index, sequence ->
spannable.setSpan(CharterSpan(sequence), index, index + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
return formatted
}
private fun Spannable.clearSpawns() =
this
.getSpans(0, this.length, CharterSpan::class.java)
.forEach { this.removeSpan(it) }
private fun format(spannable: CharSequence): String {
mFormatter.clear()
var formated = ""
for (i in 0 until spannable.length) {
formated = mFormatter.inputDigit(spannable[i])
}
return formated
}
private inner class CharterSpan(private val charters: String) : ReplacementSpan() {
var space = 0
override fun getSize(paint: Paint, text: CharSequence, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int {
space = Math.round(paint.measureText(charters, 0, charters.length))
return Math.round(paint.measureText(text, start, end)) + space
}
override fun draw(canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) {
space = Math.round(paint.measureText(charters, 0, charters.length))
canvas.drawText(text, start, end, x + space, y.toFloat(), paint)
canvas.drawText(charters, x, y.toFloat(), paint)
}
}
}
Uasge:
editText.transformationMethod = formatter
You can use a Regular Expression with pattern matching to extract number from a string.
String s="";
Pattern p = Pattern.compile("\\d+");
Matcher m = p.matcher("(1111)123-456-789"); //editText.getText().toString()
while (m.find()) {
s=s+m.group(0);
}
System.out.println("............"+s);
Output : ............1111123456789
Don't worry. I have make a most of better solution for you. You can see this simple app link below.
private EditText mPasswordField;
public int textLength = 0;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPasswordField = (EditText) findViewById(R.id.password_field);
mPasswordField.addTextChangedListener(this);
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
String text = mPasswordField.getText().toString();
textLength = mPasswordField.getText().length();
if (text.endsWith("-") || text.endsWith(" ") || text.endsWith(" "))
return;
if (textLength == 1) {
if (!text.contains("(")) {
mPasswordField.setText(new StringBuilder(text).insert(text.length() - 1, "(").toString());
mPasswordField.setSelection(mPasswordField.getText().length());
}
} else if (textLength == 5) {
if (!text.contains(")")) {
mPasswordField.setText(new StringBuilder(text).insert(text.length() - 1, ")").toString());
mPasswordField.setSelection(mPasswordField.getText().length());
}
} else if (textLength == 6) {
mPasswordField.setText(new StringBuilder(text).insert(text.length() - 1, " ").toString());
mPasswordField.setSelection(mPasswordField.getText().length());
} else if (textLength == 10) {
if (!text.contains("-")) {
mPasswordField.setText(new StringBuilder(text).insert(text.length() - 1, "-").toString());
mPasswordField.setSelection(mPasswordField.getText().length());
}
} else if (textLength == 15) {
if (text.contains("-")) {
mPasswordField.setText(new StringBuilder(text).insert(text.length() - 1, "-").toString());
mPasswordField.setSelection(mPasswordField.getText().length());
}
}else if (textLength == 18) {
if (text.contains("-")) {
mPasswordField.setText(new StringBuilder(text).insert(text.length() - 1, "-").toString());
mPasswordField.setSelection(mPasswordField.getText().length());
}
} else if (textLength == 20) {
Intent i = new Intent(MainActivity.this, Activity2.class);
startActivity(i);
}
}
#Override
public void afterTextChanged(Editable s) {
}
Not: Don't forget "implement TextWatcher" with your activity class.
Link :https://drive.google.com/open?id=0B-yo9VvU7jyBMjJpT29xc2k5bnc
Hope you are feeling cool for this solution.
You can accept only numbers and phone number type using java code
EditText number1 = (EditText) layout.findViewById(R.id.edittext);
number1.setInputType(InputType.TYPE_CLASS_NUMBER|InputType.TYPE_CLASS_PHONE);
number1.setKeyListener(DigitsKeyListener.getInstance("0123456789”));
number1.setFilters(new InputFilter[] {new InputFilter.LengthFilter(14)}); // 14 is max digits
This code will avoid lot of validations after reading input
This code is work for me for (216) 555-5555
etphonenumber.addTextChangedListener(new TextWatcher()
{
#Override
public void onTextChanged(CharSequence s, int start, int before, int count)
{
// TODO Auto-generated method stub
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after)
{
// TODO Auto-generated method stub
}
#Override
public void afterTextChanged(Editable s)
{
String text = etphonenumber.getText().toString();
int textLength = etphonenumber.getText().length();
if (text.endsWith("-") || text.endsWith(" ") || text.endsWith(" "))
return;
if (textLength == 1) {
if (!text.contains("("))
{
etphonenumber.setText(new StringBuilder(text).insert(text.length() - 1, "(").toString());
etphonenumber.setSelection(etphonenumber.getText().length());
}
}
else if (textLength == 5)
{
if (!text.contains(")"))
{
etphonenumber.setText(new StringBuilder(text).insert(text.length() - 1, ")").toString());
etphonenumber.setSelection(etphonenumber.getText().length());
}
}
else if (textLength == 6)
{
etphonenumber.setText(new StringBuilder(text).insert(text.length() - 1, " ").toString());
etphonenumber.setSelection(etphonenumber.getText().length());
}
else if (textLength == 10)
{
if (!text.contains("-"))
{
etphonenumber.setText(new StringBuilder(text).insert(text.length() - 1, "-").toString());
etphonenumber.setSelection(etphonenumber.getText().length());
}
}
}
});

Categories

Resources