带有一键清空功能的EditText

Posted Shen_JC

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了带有一键清空功能的EditText相关的知识,希望对你有一定的参考价值。

介绍

  很常见的一个功能,大部分app在登录界面都会实现这个功能了。因为在掘金上看了一篇类似的文章,所以决定自己实践一下。
  下图为实现效果:

常见实现方法

  • 组合控件,EditText + Button
     实现简单,可以单独使用。
  • 自定义View,继承EditText,通过EditText自带的Drawable来实现。
     布局复杂度低

继承EditText来实现一键清功能

需要考虑的问题

根据业务场景,有以下几个问题需要我们在实现中考虑到:
1. 怎么添加清空按钮
2. 怎么处理点击事件
3. 处理清空按钮的显示状态
3.1 有文字时才显示清空按钮,没有文字则掩藏。
3.2 获取焦点时才显示清空按钮,没有焦点时则隐藏
3.3 EditText的setErrot方法调用后,清空按钮怎么处理
4. 添加自定义的属性

实现流程

带着上面的问题我们开始一步步实现自定义View。

步骤1:继承EditText,实现构造方法

public class ClearableEditText extends EditText
        implements EditText.OnFocusChangeListener 

    public static final String TAG = "ClearableEditText";

    public ClearableEditText(Context context) 
       this(context, null);
    

    public ClearableEditText(Context context, AttributeSet attrs) 
        this(context, attrs, android.R.attr.editTextStyle);
    

    public ClearableEditText(Context context, AttributeSet attrs, int defStyleAttr) 
        this(context, attrs, defStyleAttr, 0);
    

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public ClearableEditText(Context context, AttributeSet attrs, int defStyleAttr, int
            defStyleRes) 
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context, attrs, defStyleAttr, defStyleRes);
    

步骤二:添加清空按钮

TextView中有一个静态内部类Drawables, 这个类的作用是用来保存和显示TextView上下左右的Drawable。通过android:drawable* 来设置的drawable icon就保存在Drawables中。

通过调用 Drawable drawables[] = getCompoundDrawables();方法可以获取到Drawables中的left, top, right, and bottom的Drawable数组。而drawables[2]正好就是显示在TextView右边的drawable icon,所以这个drawable 正好满足我们的需求。


    private Drawable mClearDrawable;
    /**
     * Right Drawable 是否可见
     */
    private boolean mIsClearVisible;

    private void init(Context context, AttributeSet attrs, int defStyleAttr, int
            defStyleRes) 

        Drawable drawables[] = getCompoundDrawables();
        mClearDrawable = drawables[2]; // Right Drawable;

        // 第一次隐藏
        setClearDrawableVisible(false);
    

步骤三:处理点击事件

由于Drawable没办法接收处理TouchEvent,所以我们只能通过触摸区域来判断,当触摸事件的坐标在right drawable的范围内的时候就触发点击事件。

覆写onTouchEvent事件,这里我只判断了x轴的范围。那为什么不加上y轴的判断呢?个人认为没什么必要。

    @Override
    public boolean onTouchEvent(MotionEvent event) 

        // error drawable 不显示 && clear drawable 显示 && action up
        if (getError() == null && mIsClearVisible && event.getAction() == MotionEvent.ACTION_UP) 

            float x = event.getX();
            if (x >= getWidth() - getTotalPaddingRight() && x <= getWidth() - getPaddingRight()) 
                Log.d(TAG, "点击清除按钮!");

                clearText();
            
        

        return super.onTouchEvent(event);
    


步骤四:处理清空按钮的显示状态

有三种情况需要考虑:
1 有文字时才显示清空按钮,没有文字则掩藏。
2 获取焦点时才显示清空按钮,没有焦点时则隐藏
3 EditText的setErrot方法调用后,清空按钮怎么处理

为了解决1和2的两个问题,我们需要为EditText监听文字输入的状态和获取失去焦点的状态,因此我们需要实现onFocusChange和addTextChangedListener。

    @Override
    public void onFocusChange(View v, boolean hasFocus) 

        if (getError() == null) 
            if (hasFocus) 
                if (getText().length() > 0) 
                    setClearDrawableVisible(true);
                
             else 
                setClearDrawableVisible(false);
            
        
    
        // 添加TextChangedListener
        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) 
                Log.d(TAG, "onTextChanged " + s);

                setClearDrawableVisible(s.length() > 0);
            

            @Override
            public void afterTextChanged(Editable s) 

            
        );

setClearDrawableVisible的具体实现:
我们可以通过setCompoundDrawables来设置TextView的left、top、right、bottom drawable。传递null表示不需要显示。

    /**
     * 设置Right Drawable是否可见
     *
     * @param isVisible true for visible , false for invisible
     */
    public void setClearDrawableVisible(boolean isVisible) 

        setCompoundDrawables(getCompoundDrawables()[0], getCompoundDrawables()[1],
                isVisible ? mClearDrawable : null, getCompoundDrawables()[3]);

        mIsClearVisible = isVisible;
    

最后考虑第三种情况,EditText的setErrot方法可以给出校验提示,从下图中大致可以看出这个提示由两部分组成,一个icon和一个popup window,而这个icon正好占据了我们上面提到的right drawable的位置也就是我们的清除按钮需要用到的位置。那么error icon是不是也用了right drawable来实现的呢?

在TextView中有这样一个方法applyErrorDrawableIfNeeded,从方法名就可以猜到它就是用来设置error drawable的,最终通过mShowing[Drawables.RIGHT] = mDrawableError; 把error drawable 放到了Drawables.RIGHT中。哦,对了。。这一切都是基于LTR的Layout方向。

            // then, if needed, assign the Error drawable to the correct location
            if (mDrawableError != null) 
                switch(layoutDirection) 
                    case LAYOUT_DIRECTION_RTL:
                        mDrawableSaved = DRAWABLE_LEFT;

                        mDrawableTemp = mShowing[Drawables.LEFT];
                        mDrawableSizeTemp = mDrawableSizeLeft;
                        mDrawableHeightTemp = mDrawableHeightLeft;

                        mShowing[Drawables.LEFT] = mDrawableError;
                        mDrawableSizeLeft = mDrawableSizeError;
                        mDrawableHeightLeft = mDrawableHeightError;
                        break;
                    case LAYOUT_DIRECTION_LTR:
                    default:
                        mDrawableSaved = DRAWABLE_RIGHT;

                        mDrawableTemp = mShowing[Drawables.RIGHT];
                        mDrawableSizeTemp = mDrawableSizeRight;
                        mDrawableHeightTemp = mDrawableHeightRight;

                        mShowing[Drawables.RIGHT] = mDrawableError;
                        mDrawableSizeRight = mDrawableSizeError;
                        mDrawableHeightRight = mDrawableHeightError;
                        break;
                
            

那么我们就可以处理第三种情况。覆写setError方法。想要知道error drawable是否显示,可以通过“getError() == null”来判断,为ture则表示不显示,false表示已显示。

    @Override
    public void setError(CharSequence error, Drawable icon) 
        if (error != null) 
            setClearDrawableVisible(true);
        
        super.setError(error, icon);
    

为什么是覆写setError方法?我们可以通过TextView.sendAfterTextChanged来找到答案。当EditText中重新输入文字的后,sendAfterTextChanged会被调用,在sendAfterTextChanged方法中会调用hideErrorIfUnchanged,而 hideErrorIfUnchanged则是直接调用了setError(null, null)。通过setError(null, null)方法隐藏 error drawable。

步骤五:添加自定义属性

attrs.xml中申明style

    <declare-styleable name="ClearableEditText">

        <attr name="right_drawable_color" format="color|reference" />

    </declare-styleable>

在init函数中获取自定义属性并做相关处理。

        final Resources.Theme theme = context.getTheme();

        TypedArray a = theme.obtainStyledAttributes(attrs, R.styleable.ClearableEditText,
                defStyleAttr, defStyleRes);

        int rightDrawableColor = a.getColor(R.styleable.ClearableEditText_right_drawable_color,
                Color.BLACK);

        a.recycle();

        // 给mRightDrawable上色
        DrawableCompat.setTint(mClearDrawable, rightDrawableColor);

实例效果

源码

ClearableEditText

参考

和我一起实现EditText一键清空功能
Android自定义View示例(一)—带有删除按钮的EditText

以上是关于带有一键清空功能的EditText的主要内容,如果未能解决你的问题,请参考以下文章

登录界面AutoUtils 屏幕适配自定义Edittext(显示密码可见和一键清空)和 TextInputLayout的使用。

java 带清空功能的的EditText

自定义EditText实现一键删除数据

Android开源实战:手把手带你实现一个简单好用的搜索框(含历史搜索记录)

将EditText过滤器设置为自定义范围内的数字

在 EditText 背景外绘制复合可绘制对象