Android 自定义控件Android 自定义viewUI效果仿写

带有一键清空功能的EditText

2016-07-04  本文已影响398人  ShenJC

介绍#

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

一键清空

常见实现方法#

继承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

上一篇 下一篇

猜你喜欢

热点阅读