如何屏蔽 EditText 以显示 dd/mm/yyyy 日期格式

Posted

技术标签:

【中文标题】如何屏蔽 EditText 以显示 dd/mm/yyyy 日期格式【英文标题】:How to mask an EditText to show the dd/mm/yyyy date format 【发布时间】:2013-05-29 04:14:28 【问题描述】:

我如何格式化EditText 以遵循“dd/mm/yyyy”格式,就像我们可以使用TextWatcher 格式化一样掩码用户输入看起来像“0.05€ ”。我不是在谈论限制字符或验证日期,只是屏蔽为以前的格式。

【问题讨论】:

【参考方案1】:

我为一个项目写了这个TextWatcher,希望它对某人有所帮助。请注意,它验证用户输入的日期,您应该在焦点更改时处理它,因为用户可能还没有完成输入日期。

25/06 更新 将其设为 wiki,看看我们是否能找到更好的最终代码。

07/06 更新 我终于为观察者本身添加了某种验证。它将对无效日期执行以下操作:

如果月份大于 12,则为 12(十二月) 如果日期大于所选月份的日期,请将其设为该月份的最大值。 如果年份不在1900-2100范围内,请将其更改为在范围内

这个验证符合我的需要,但是你们中的一些人可能想稍微改变一下,范围很容易改变,你可以把这个验证挂到Toast消息,例如,通知用户我们已经修改了他的/她的日期,因为它是无效的。

在这段代码中,我假设我们有一个名为 dateEditText 的引用,它附加了这个 TextWatcher,可以这样做:

EditText date;
date = (EditText)findViewById(R.id.whichdate);
date.addTextChangedListener(tw);

TextWatcher tw = new TextWatcher() 
    private String current = "";
    private String ddmmyyyy = "DDMMYYYY";
    private Calendar cal = Calendar.getInstance();

当用户更改EditText的文本时

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) 
        if (!s.toString().equals(current)) 
            String clean = s.toString().replaceAll("[^\\d.]|\\.", "");
            String cleanC = current.replaceAll("[^\\d.]|\\.", "");

            int cl = clean.length();
            int sel = cl;
            for (int i = 2; i <= cl && i < 6; i += 2) 
                sel++;
            
            //Fix for pressing delete next to a forward slash
            if (clean.equals(cleanC)) sel--;

            if (clean.length() < 8)
               clean = clean + ddmmyyyy.substring(clean.length());
            else
               //This part makes sure that when we finish entering numbers
               //the date is correct, fixing it otherwise
               int day  = Integer.parseInt(clean.substring(0,2));
               int mon  = Integer.parseInt(clean.substring(2,4));
               int year = Integer.parseInt(clean.substring(4,8));

               mon = mon < 1 ? 1 : mon > 12 ? 12 : mon;
               cal.set(Calendar.MONTH, mon-1);
               year = (year<1900)?1900:(year>2100)?2100:year;
               cal.set(Calendar.YEAR, year); 
               // ^ first set year for the line below to work correctly
               //with leap years - otherwise, date e.g. 29/02/2012
               //would be automatically corrected to 28/02/2012 

               day = (day > cal.getActualMaximum(Calendar.DATE))? cal.getActualMaximum(Calendar.DATE):day;
               clean = String.format("%02d%02d%02d",day, mon, year);
            

            clean = String.format("%s/%s/%s", clean.substring(0, 2),
                clean.substring(2, 4),
                clean.substring(4, 8));

            sel = sel < 0 ? 0 : sel;
            current = clean;
            date.setText(current);
            date.setSelection(sel < current.length() ? sel : current.length());
        
    

我们还实现了其他两个功能,因为我们必须这样做

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) 

    @Override
    public void afterTextChanged(Editable s) 
;

这会产生以下效果,删除或插入字符将显示或隐藏dd/mm/yyyy 掩码。它应该很容易修改以适应其他格式掩码,因为我试图让代码尽可能简单。

【讨论】:

对不起。如何访问完整代码?你的 cal 变量是什么? @joao2fast4u - cal 变量是 java.util.Calendar 对象 @joao2fast4u @LeonardoOtto 对于像我这样的新手:我很难过,直到我将cal 声明为这样:private Calendar cal = Calendar.getInstance();,否则我会在@987654337 获得NullPointerException @部分 对不起,代码不是新的证明,如果有人有时间编辑它,请随意。这就是我把它做成维基的原因 @Juan-devtopia.coop 你说得对。我做出了改变。感谢您的代码,它帮助很大。【参考方案2】:

当前的答案非常好,帮助引导我找到自己的解决方案。即使这个问题已经有一个有效的答案,我还是决定发布自己的解决方案有几个原因:

我在 Kotlin 工作,而不是 Java。发现自己遇到同样问题的人必须翻译当前的解决方案。 我想写一个更清晰的答案,以便人们可以更轻松地适应自己的问题。 根据 dengue8830 的建议,我将这个问题的解决方案封装在一个类中,这样任何人都可以使用,甚至不用担心实现。

要使用它,只需执行以下操作:

DateInputMask(mEditText).listen()

解决方法如下图:

class DateInputMask(val input : EditText) 

    fun listen() 
        input.addTextChangedListener(mDateEntryWatcher)
    

    private val mDateEntryWatcher = object : TextWatcher 

        var edited = false
        val dividerCharacter = "/"

        override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) 
            if (edited) 
                edited = false
                return
            

            var working = getEditText()

            working = manageDateDivider(working, 2, start, before)
            working = manageDateDivider(working, 5, start, before)

            edited = true
            input.setText(working)
            input.setSelection(input.text.length)
        

        private fun manageDateDivider(working: String, position : Int, start: Int, before: Int) : String
            if (working.length == position) 
                return if (before <= position && start < position)
                    working + dividerCharacter
                else
                    working.dropLast(1)
            
            return working
        

        private fun getEditText() : String 
            return if (input.text.length >= 10)
                input.text.toString().substring(0,10)
            else
                input.text.toString()
        

        override fun afterTextChanged(s: Editable) 
        override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) 
    

【讨论】:

【参考方案3】:

使用 Juan Cortés 的代码的更简洁的方法是将其放在一个类中:

public class DateInputMask implements TextWatcher 

private String current = "";
private String ddmmyyyy = "DDMMYYYY";
private Calendar cal = Calendar.getInstance();
private EditText input;

public DateInputMask(EditText input) 
    this.input = input;
    this.input.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) 
    if (s.toString().equals(current)) 
        return;
    

    String clean = s.toString().replaceAll("[^\\d.]|\\.", "");
    String cleanC = current.replaceAll("[^\\d.]|\\.", "");

    int cl = clean.length();
    int sel = cl;
    for (int i = 2; i <= cl && i < 6; i += 2) 
        sel++;
    
    //Fix for pressing delete next to a forward slash
    if (clean.equals(cleanC)) sel--;

    if (clean.length() < 8)
        clean = clean + ddmmyyyy.substring(clean.length());
    else
        //This part makes sure that when we finish entering numbers
        //the date is correct, fixing it otherwise
        int day  = Integer.parseInt(clean.substring(0,2));
        int mon  = Integer.parseInt(clean.substring(2,4));
        int year = Integer.parseInt(clean.substring(4,8));

        mon = mon < 1 ? 1 : mon > 12 ? 12 : mon;
        cal.set(Calendar.MONTH, mon-1);
        year = (year<1900)?1900:(year>2100)?2100:year;
        cal.set(Calendar.YEAR, year);
        // ^ first set year for the line below to work correctly
        //with leap years - otherwise, date e.g. 29/02/2012
        //would be automatically corrected to 28/02/2012

        day = (day > cal.getActualMaximum(Calendar.DATE))? cal.getActualMaximum(Calendar.DATE):day;
        clean = String.format("%02d%02d%02d",day, mon, year);
    

    clean = String.format("%s/%s/%s", clean.substring(0, 2),
            clean.substring(2, 4),
            clean.substring(4, 8));

    sel = sel < 0 ? 0 : sel;
    current = clean;
    input.setText(current);
    input.setSelection(sel < current.length() ? sel : current.length());


@Override
public void afterTextChanged(Editable s) 



然后你可以重复使用它

new DateInputMask(myEditTextInstance);

【讨论】:

【参考方案4】:

尝试使用解决此问题的库,因为掩蔽它不是开箱即用的。 有很多极端情况(比如在已经被屏蔽的文本中间添加/删除字符),要正确处理这个问题,您最终会遇到很多代码(和错误)。

以下是一些可用的库: https://github.com/egslava/edittext-mask https://github.com/dimitar-zabaznoski/MaskedEditText https://github.com/pinball83/Masked-Edittext https://github.com/RedMadRobot/input-mask-android https://github.com/santalu/mask-edittext

** 请注意,在编写这些库时并非没有问题,因此您有责任选择最适合您的库并测试代码。

【讨论】:

【参考方案5】:

Juan Cortés 的 wiki 就像一个魅力 https://***.com/a/16889503/3480740

这是我的 Kotlin 版本

fun setBirthdayEditText() 

    birthdayEditText.addTextChangedListener(object : TextWatcher 

        private var current = ""
        private val ddmmyyyy = "DDMMYYYY"
        private val cal = Calendar.getInstance()

        override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) 
            if (p0.toString() != current) 
                var clean = p0.toString().replace("[^\\d.]|\\.".toRegex(), "")
                val cleanC = current.replace("[^\\d.]|\\.", "")

                val cl = clean.length
                var sel = cl
                var i = 2
                while (i <= cl && i < 6) 
                    sel++
                    i += 2
                
                //Fix for pressing delete next to a forward slash
                if (clean == cleanC) sel--

                if (clean.length < 8) 
                    clean = clean + ddmmyyyy.substring(clean.length)
                 else 
                    //This part makes sure that when we finish entering numbers
                    //the date is correct, fixing it otherwise
                    var day = Integer.parseInt(clean.substring(0, 2))
                    var mon = Integer.parseInt(clean.substring(2, 4))
                    var year = Integer.parseInt(clean.substring(4, 8))

                    mon = if (mon < 1) 1 else if (mon > 12) 12 else mon
                    cal.set(Calendar.MONTH, mon - 1)
                    year = if (year < 1900) 1900 else if (year > 2100) 2100 else year
                    cal.set(Calendar.YEAR, year)
                    // ^ first set year for the line below to work correctly
                    //with leap years - otherwise, date e.g. 29/02/2012
                    //would be automatically corrected to 28/02/2012

                    day = if (day > cal.getActualMaximum(Calendar.DATE)) cal.getActualMaximum(Calendar.DATE) else day
                    clean = String.format("%02d%02d%02d", day, mon, year)
                

                clean = String.format("%s/%s/%s", clean.substring(0, 2),
                        clean.substring(2, 4),
                        clean.substring(4, 8))

                sel = if (sel < 0) 0 else sel
                current = clean
                birthdayEditText.setText(current)
                birthdayEditText.setSelection(if (sel < current.count()) sel else current.count())
            
        

        override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) 
        

        override fun afterTextChanged(p0: Editable) 

        
    )

【讨论】:

可以,最好把方法定义为Edittext类的扩展函数 你也错过了一件事val cleanC = current.replace("[^\\d.]|\\.", "")应该是val cleanC = current.replace("[^\\d.]|\\.".toRegex(), "")【参考方案6】:

未经验证的 Kotlin 版本

        editText.addTextChangedListener(object : TextWatcher

            var sb : StringBuilder = StringBuilder("")

            var _ignore = false

            override fun afterTextChanged(s: Editable?) 

            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) 

            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) 

            if(_ignore)
                _ignore = false
                return
            

            sb.clear()
            sb.append(if(s!!.length > 10) s.subSequence(0,10) else s )

            if(sb.lastIndex == 2)
                if(sb[2] != '/')
                    sb.insert(2,"/")
                
             else if(sb.lastIndex == 5)
                if(sb[5] != '/')
                    sb.insert(5,"/")
                
            

            _ignore = true
            editText.setText(sb.toString())
            editText.setSelection(sb.length)

        
    )

【讨论】:

【参考方案7】:

此答案不会对剩余的未键入数字应用完整掩码。但是,它是相关的,并且是我需要的解决方案。它的工作方式类似于PhoneNumberFormattingTextWatcher 的工作方式。

当您键入时,它会添加斜杠以分隔格式为mm/dd/yyyy 的日期。 它不做任何验证 - 只是格式化。

不需要EditText 引用。 只需设置监听器,它就可以工作。 myEditText.addTextChangedListener(new DateTextWatcher());

import android.text.Editable;
import android.text.TextWatcher;

import java.util.Locale;

/**
 * Adds slashes to a date so that it matches mm/dd/yyyy.
 *
 * Created by Mark Miller on 12/4/17.
 */
public class DateTextWatcher implements TextWatcher 

    public static final int MAX_FORMAT_LENGTH = 8;
    public static final int MIN_FORMAT_LENGTH = 3;

    private String updatedText;
    private boolean editing;


    @Override
    public void beforeTextChanged(CharSequence charSequence, int start, int before, int count) 

    

    @Override
    public void onTextChanged(CharSequence text, int start, int before, int count) 
        if (text.toString().equals(updatedText) || editing) return;

        String digitsOnly = text.toString().replaceAll("\\D", "");
        int digitLen = digitsOnly.length();

        if (digitLen < MIN_FORMAT_LENGTH || digitLen > MAX_FORMAT_LENGTH) 
            updatedText = digitsOnly;
            return;
        

        if (digitLen <= 4) 
            String month = digitsOnly.substring(0, 2);
            String day = digitsOnly.substring(2);

            updatedText = String.format(Locale.US, "%s/%s", month, day);
        
        else 
            String month = digitsOnly.substring(0, 2);
            String day = digitsOnly.substring(2, 4);
            String year = digitsOnly.substring(4);

            updatedText = String.format(Locale.US, "%s/%s/%s", month, day, year);
        
    

    @Override
    public void afterTextChanged(Editable editable) 

        if (editing) return;

        editing = true;

        editable.clear();
        editable.insert(0, updatedText);

        editing = false;
    

【讨论】:

【参考方案8】:

android:inputType="date" 添加到您的EditText

【讨论】:

【参考方案9】:

您可以使用下面的代码,它还添加了日期的所有验证以使其有效。喜欢天数超过 31;月份不能大于 12 等等。

class DateMask : TextWatcher 

private var updatedText: String? = null
private var editing: Boolean = false

companion object 

    private const val MAX_LENGTH = 8
    private const val MIN_LENGTH = 2



override fun beforeTextChanged(charSequence: CharSequence, start: Int, before: Int, count: Int) 



override fun onTextChanged(text: CharSequence, start: Int, before: Int, count: Int) 
    if (text.toString() == updatedText || editing) return

    var digits = text.toString().replace("\\D".toRegex(), "")
    val length = digits.length

    if (length <= MIN_LENGTH) 
        digits = validateMonth(digits)
        updatedText = digits
        return
    

    if (length > MAX_LENGTH) 
        digits = digits.substring(0, MAX_LENGTH)
    

    updatedText = if (length <= 4) 

        digits = validateDay(digits.substring(0, 2), digits.substring(2))
        val month = digits.substring(0, 2)
        val day = digits.substring(2)

        String.format(Locale.US, "%s/%s", month, day)
     else 
        digits = digits.substring(0, 2) + digits.substring(2, 4) + validateYear(digits.substring(4))
        val month = digits.substring(0, 2)
        val day = digits.substring(2, 4)
        val year = digits.substring(4)

        String.format(Locale.US, "%s/%s/%s", month, day, year)
    


private fun validateDay(month: String, day: String): String 

    val arr31 = intArrayOf(1, 3, 5, 7, 8, 10, 12)
    val arr30 = intArrayOf(4, 6, 9, 11)
    val arrFeb = intArrayOf(2)

    if (day.length == 1 &&
            ((day.toInt() > 3 && month.toInt() !in arrFeb)
                    || (day.toInt() > 2 && month.toInt() in arrFeb))) 
        return month
    

    return when (month.toInt()) 
        in arr31 -> validateDay(month, arr31, day, 31)
        in arr30 -> validateDay(month, arr30, day, 30)
        in arrFeb -> validateDay(month, arrFeb, day, 29)
        else -> "$month$day"
    



private fun validateDay(month: String, arr: IntArray, day: String, maxDay: Int): String 
    if (month.toInt() in arr) 
        if (day.toInt() > maxDay) 
            return "$month$day.substring(0, 1)"
        
    
    return "$month$day"


private fun validateYear(year: String): String 
    if (year.length == 1 && (year.toInt() in 3..9 || year.toInt() == 0)) 
        return ""
    

    if (year.length == 2 && year.toInt() !in 19..20) 
        return year.substring(0, 1)
    

    return year


private fun validateMonth(month: String): String 

    if (month.length == 1 && month.toInt() in 2..9) 
        return "0$month"
    

    if (month.length == 2 && month.toInt() > 12) 
        return month.substring(0, 1)
    
    return month


override fun afterTextChanged(editable: Editable) 

    if (editing) return

    editing = true

    editable.clear()
    editable.insert(0, updatedText)

    editing = false

在您的fragmentActivity 中,您可以这样使用DateMaskmEditText?.addTextChangedListener(dateMask)

【讨论】:

【参考方案10】:

我花了 6 个小时制作自己的格式。试一试,如果你喜欢它,然后通过代码。 您可以在任何地方编辑日期,例如仅限日、仅限月。它会自动转义'/'字符并更新下一个数字。

// initialized with the current date
String date = new SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()).format(new Date());
edit_date_editEntity.setText(date);

public void formatDate()
    edit_date_editEntity.addTextChangedListener(new TextWatcher() 
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) 
            String ss = s.toString();
            if (after==0)          // when a single character is deleted
                if (s.charAt(start) == '/')        // if the character is '/' , restore it and put the cursor at correct position
                    edit_date_editEntity.setText(s);
                    edit_date_editEntity.setSelection(start);
                
                else if (s.charAt(start) == '-')   // if the character is '-' , restore it and put the cursor at correct position
                    edit_date_editEntity.setText(s);
                    edit_date_editEntity.setSelection(start);
                
                else if (ss.charAt(start) >= '0' && ss.charAt(start) <= '9')   // if the character is a digit, replace it with '-'
                    ss = ss.substring(0, start) + "-" + ss.substring(start +1, ss.length());
                    edit_date_editEntity.setText(ss);
                    edit_date_editEntity.setSelection(start);
                
            
        

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) 
            String ss = s.toString();
            if (before==0 )    // when a single character is added
                if (edit_date_editEntity.getSelectionStart()==3 || edit_date_editEntity.getSelectionStart()==6) 
                    // if the new character was just before '/' character
                    // getSelection value gets incremented by 1, because text has been changed and hence cursor position updated
                    // Log.d("test", ss);
                    ss = ss.substring(0, start) + "/" + ss.substring(start, start + 1) + ss.substring(start + 3, ss.length());
                    // Log.d("test", ss);
                    edit_date_editEntity.setText(ss);
                    edit_date_editEntity.setSelection(start + 2);
                
                else 
                    if (edit_date_editEntity.getSelectionStart()==11)
                        // if cursor was at last, do not add anything
                        ss = ss.substring(0,ss.length()-1);
                        edit_date_editEntity.setText(ss);
                        edit_date_editEntity.setSelection(10);
                    
                    else 
                        // else replace the next digit with the entered digit
                        ss = ss.substring(0, start + 1) + ss.substring(start + 2, ss.length());
                        edit_date_editEntity.setText(ss);
                        edit_date_editEntity.setSelection(start + 1);
                    
                
            
        

        @Override
        public void afterTextChanged(Editable s) 

        
    );

【讨论】:

【参考方案11】:

使用 TextWatcher 编辑文本。 isDeleting 标志是重要的 addTextChangedListener。 像这样添加变量-

  EditText edtDateFormat;
    private boolean isDeleting=false;
   private boolean isWrongDate=false;
    private boolean isWrongMonth=false;





edtDateFormat.addTextChangedListener(new TextWatcher() 
            @Override
            public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) 
                Log.e("beforeTextChanged","-->"+charSequence);
                Log.e("start",""+start);
                Log.e("after",""+after);
                Log.e("count",""+count);
                isDeleting = count > after;

            

            @Override
            public void onTextChanged(CharSequence charSequence, int start, int before, int count) 
                String text=charSequence.toString();
                Log.e("onTextChanged","-->"+charSequence);
                Log.e("start1",""+start);
                Log.e("before1",""+before);
                Log.e("count1",""+count);
                Log.e("isDeleting ",""+isDeleting);
                char subChar = 'T';
                if(text.length()>0)
                    subChar=text.charAt(text.length()-1);
                    Log.e("LastChar","-->"+subChar);
                

                if(isDeleting)
                    return;
                
                if(text.length()==1)
                    return;
                
                if(text.length()==4)
                    return;
                

                if(subChar=='/')
                    return;
                
                    if(charSequence.length()==2)
                        int date=Integer.parseInt(String.valueOf(charSequence));
                        if(date<1 || date >31)
                            edtDateFormat.setError("Please enter correct date");
                            isWrongDate=true;
                            return;
                        
                        isWrongDate=false;
                        isDeleting=false;
                        charSequence=charSequence+"/";
                        edtDateFormat.setText(charSequence);
                        isRunning=true;
                        edtDateFormat.setSelection(edtDateFormat.getText().length());
                        isDeleting=true;
                    

                   if(text.length()==5)
                        String month=text.substring(3,5);
                        Log.e("Month","-->"+month);
                        int monthVal=Integer.parseInt(month);
                        if(monthVal<0 || monthVal>12)
                            edtDateFormat.setError("Please enter correct month");
                            isWrongMonth=true;
                            return;
                        
                       isWrongMonth=false;
                       isDeleting=false;
                       charSequence=charSequence+"/";
                       edtDateFormat.setText(charSequence);
                       isRunning=true;
                       edtDateFormat.setSelection(edtDateFormat.getText().length());
                       isDeleting=true;
                    

                if(text.length()==10)
                    String year=text.substring(6,10);
                    Log.e("year","-->"+year);
                   int yearVal=Integer.parseInt(year);
                    if(yearVal<1900 || yearVal>2050)
                        edtDateFormat.setError("Please enter correct year");
                        isWrongYear=true;
                        return;
                    
                



                if(isWrongDate)
                    Log.e("isWrongDate","-->"+isWrongDate);
                    if(text.length()>2)
                        isDeleting=false;
                        edtDateFormat.setText(text.substring(0, text.length() - 1));
                        isDeleting=true;
                       edtDateFormat.setSelection(edtDateFormat.getText().length());

                    

                


                if(isWrongMonth)
                    if(text.length()>2)
                        isDeleting=false;
                        edtDateFormat.setText(text.substring(0, text.length() - 1));
                        isDeleting=true;
                        edtDateFormat.setSelection(edtDateFormat.getText().length());

                    

                

            

            @Override
            public void afterTextChanged(Editable editable) 

            
        );

【讨论】:

以上是关于如何屏蔽 EditText 以显示 dd/mm/yyyy 日期格式的主要内容,如果未能解决你的问题,请参考以下文章

使用电话号码格式 NaN 屏蔽 EditText,如 PhoneNumberUtils

如何在Android应用里屏蔽鼠标点击事件

如何在 EditText 中隐藏下划线

设置 EditText 以设置电话号码值

如何获取edittext游标的绝对屏幕位置?

Android EditText中字符大小与光标位置