在Android中为EditText格式化电话号码

85

我正在制作一个简单的通讯录应用程序(针对4.2版本),包括姓名、地址、城市、州、邮编和电话号码。

我希望将电话号码的输入格式化为电话号码(XXX)XXX-XXXX,但我需要将值作为字符串提取出来,以便在保存时将其存储在我的数据库中。怎么做? 我已经将EditText设置为“电话号码”输入,但显然这并没有多大作用。


1
你需要的字符串全部是数字,还是包括空格、破折号和括号? - Charles Munger
我猜字符串的格式可以与电话号码相同,只要我能将其放入字符串变量中,然后插入到我的数据库表中即可。 - BackDoorNoBaby
1
你可以使用正则表达式的模式匹配功能。 - Raghunandan
你还应该记住,(xxx) xxx - xxxx 只是美国和其他一些地方的号码格式。 - draksia
15个回答

112

只需使用PhoneNumberFormattingTextWatcher,只需调用:

editText.addTextChangedListener(new PhoneNumberFormattingTextWatcher());

添加
要明确的是,PhoneNumberFormattingTextWatcher的核心代码是PhoneNumberUtils类。不同之处在于TextWatcher在您保持EditText不变的同时,必须每次更改其内容时调用PhoneNumberUtils.formatNumber()


4
我尝试过这样做,但它没有将文本设置为 (XXX) XXX-XXXX 的格式。它允许的格式是 xxx-xxx-xxxx。 - Vishal Thakkar
只有输入完整的电话号码才能正常工作,如果只输入了一半的电话号码,将无法获取任何结果。 - undefined

65

有一个名为PhoneNumberUtils的库可帮助你处理电话号码的转换和比较。例如,使用...

EditText text = (EditText) findViewById(R.id.editTextId);
PhoneNumberUtils.formatNumber(text.getText().toString())

...以标准格式对您的数字进行格式化。

PhoneNumberUtils.compare(String a, String b);

...有助于模糊比较。还有更多的方法,请查看http://developer.android.com/reference/android/telephony/PhoneNumberUtils.html 以了解更多信息。

p.s. 将EditText设置为phone已经是一个不错的选择;最终在布局中添加digits可能会有帮助,例如在您的布局中看起来像...

<EditText
    android:id="@+id/editTextId"
    android:inputType="phone"
    android:digits="0123456789+" 
/> 

1
这正是我要建议的。我使用那个库,迄今为止还没有遇到过什么问题。 - yarian
3
PhoneNumberUtils是PhoneNumberFormattingTextWatcher背后的类,因此我们得到了相同的基本答案。不同之处在于,您的方法只会发生一次,而TextWatcher会不断更新。(+1给您)。但是,为什么您要同时使用输入类型和数字? - Sam
嗯,如果用户想在主号码后保存/拨打分机号码怎么办?通过去除这些特殊字符,您已经防止了任何“高级”数字,并且忽略了使用 . 而不是 - 的地区。我个人认为,如果用户在不知道其含义的情况下在他们的号码中输入 N;,那就是他们的错。 :) - Sam
1
@André 如果我想在输入数字时格式化EditText怎么办?这样可以完美地格式化它在数据库中的存储方式,但当用户输入“123”时,我希望它显示为(123),然后是其他7个数字的(123)XXX-XXXX。 - BackDoorNoBaby
大家好,如果有人解决了这个问题,请分享正确的答案。 - Vishal Thakkar
显示剩余7条评论

33

只需使用这个:

在Java代码中:

editText.addTextChangedListener(new PhoneNumberFormattingTextWatcher());

在 XML 代码中:

<EditText
    android:id="@+id/etPhoneNumber"
    android:inputType="phone"/>

这段代码适用于我。当编辑文本中的文本更改时,它将自动格式化。


使用 String phoneNumbers = maskedString.replaceAll("[^\\d]", ""); 来获取没有任何空格的电话号码。 - Nur4I
请注意android:inputType="phone",因为一些手机如小米不会显示带有数字的键盘。当用户尝试输入某些字母(因为数字键盘未显示给他/她),用户无法得到反馈。这真的很奇怪。因此,为了最佳实践,我建议使用android:inputType="number"。 - Mladen Rakonjac
嘿,你可以使用 android:inputType="phone|number" 作为后备机制。 - Pulkit
inputType="phone" 显然不存在,这只是针对我吗? - speedox
在 API 23 中不工作。 - Vivek
显示剩余2条评论

22

我最近为 Android EditText 做了类似于 1(XXX)XXX-XXXX 的格式设置。请见下方的代码。只需将 TextWatcher 子类用作文本更改侦听器:

...
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();
    }

}

4
我收到一个错误信息:java.lang.IndexOutOfBoundsException: setSpan (2 ... 2)超出了长度为1的范围。 - Rao's
私有类的修改版本,以限制总可能输入的数字,请参见-> https://gist.github.com/CrandellWS/e254a215c54aa4be0400a3511a23f730 @Rao也遇到了同样的问题,请尝试使用我的NoExtraDigits修改... - CrandellWS

18
也许下面的示例项目会对您有所帮助;

https://github.com/reinaldoarrosi/MaskedEditText

这个项目包含一个名为MaskedEditText的视图类。首先,您需要将其添加到您的项目中

然后,在项目的res/values/attrs.xml文件中添加以下xml部分:

<resources>
    <declare-styleable name="MaskedEditText">
        <attr name="mask" format="string" />
        <attr name="placeholder" format="string" />
    </declare-styleable>
</resources>

然后,您就可以使用MaskedEditText视图了。最后,您应该像以下方式在您想要的xml文件中添加MaskedEditText;
<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="_" >

当然,您可以通过编程使用它。
完成这些步骤后,添加MaskedEditText将显示如下:

enter image description here

如果您想以未掩码的方式获取其文本值,则可以在编程中使用以下行:

maskedEditText.getText(true);

为了获取掩码值,您可以在getText方法中发送false值而不是true值。

更新:嗯,这个库不会将inputType=phone|number(至少在styles.xml中)应用于edittext。所以我选择了https://github.com/VicMikhailau/MaskedEditText - 运行良好。 - oxied

15
您需要创建一个类:
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;
    }
}

使用方法如下:

phone = view.findViewById(R.id.phone);
phone.addTextChangedListener(new PhoneTextFormatter(phone, "+7 (###) ###-####"));

这个解决方案更通用,因为用户可以添加格式。现在我还没有测试过它。 - Maxime Claude
谢谢!我还建议使用 setInputType(InputType.TYPE_CLASS_PHONE); setText("+7 ("); setSelection(4) - Miha_x64
非常感谢你,伙计。这个解决方案留下了操作数据格式的空间。 - Denny

7

这太棒了! - Olivér Raisz

4

按照此答案中的说明格式化EditText掩码。

https://dev59.com/LGUq5IYBdhLWcg3wNtyB#34907607

之后,您可以使用以下方法从掩码字符串中获取原始数字:

String phoneNumbers = maskedString.replaceAll("[^\\d]", "");

4

更像是清除:

@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;
    }

}

谢谢。这对于想按照美国标准格式化数字的人很有帮助。 - Mehul Ranpara

3

您可以使用Spans在Android中格式化电话号码。这种解决方案比其他解决方案更好,因为它不会改变输入文本。格式化只保留视觉效果。

implementation 'com.googlecode.libphonenumber:libphonenumber:7.0.4'

格式化器类:

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)
    }
    }

}

使用方法:

editText.transformationMethod = formatter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接