带有一键清空功能的EditText

作者: ShenJC | 来源:发表于2016-07-04 17:57 被阅读398次

    介绍#

    很常见的一个功能,大部分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来实现的呢?


    setError.jpg

    在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

      本文链接:https://www.haomeiwen.com/subject/gkzcjttx.html