EditText setCompoundDrawablesWithIntrinsicBounds()在setError()之后不起作用

时间:2016-09-07 08:01:57

标签: android error-handling android-edittext android-drawable

我有一个自定义小部件,它使用setCompoundDrawablesWithIntrinsicBounds()方法将清除按钮设置为右侧可绘制。 现在我需要向窗口小部件添加错误,并且我尝试使用默认功能并依赖setError()方法。

我遇到的问题是,在我设置错误之后,即使我之后再次设置错误,我的右侧绘图也不会再次显示。

是否有人遇到此问题并可能找到问题的解决方案?

谢谢。

LE: 我忘了提及如果我使用setError("My error hint text", null)代替setError("Please choose an valid destination!"),一切正常,但您可以猜到错误图标不会显示。

LLE: 这是我的自定义类,它处理清除图标的显示及其动作。

/**
 * Class of {@link android.widget.AutoCompleteTextView} that includes a clear (dismiss / close) button with a
 * OnClearListener to handle the event of clicking the button
 * <br/>
 * Created by ionut on 26.04.2016.
 */
public class ClearableAutoCompleteTextView extends AppCompatAutoCompleteTextView implements View.OnTouchListener {

    /**
     * The time(in milliseconds) used for transitioning between the supported states.
     */
    private static final int TRANSITION_TIME = 500;

    /**
     * Flag for hide transition.
     */
    private static final int TRANSITION_HIDE = 101;

    /**
     * Flag for show transition.
     */
    private static final int TRANSITION_SHOW = 102;

    /**
     * Image representing the clear button. (will always be set to the right of the view).
     */
    @DrawableRes
    private static int mImgClearButtonRes = R.drawable.ic_clear_dark;

    /**
     * Task with the role of showing the clear action button.
     */
    private final Runnable showClearButtonRunnable = new Runnable() {
        @Override
        public void run() {
            Message msg = transitionHandler.obtainMessage(TRANSITION_SHOW);
            msg.sendToTarget();
        }
    };

    /**
     * Task with the role of hiding the clear action button.
     */
    private final Runnable hideClearButtonRunnable = new Runnable() {
        @Override
        public void run() {
            Message msg = transitionHandler.obtainMessage(TRANSITION_HIDE);
            msg.sendToTarget();
        }
    };

    /**
     * Flag indicating if the default clear functionality should be disabled.
     */
    private boolean mDisableDefaultFunc;
    // The default clear listener which will clear the input of any text
    private OnClearListener mDefaultClearListener = new OnClearListener() {
        @Override
        public void onClear() {
            getEditableText().clear();
            setText(null);
        }
    };

    /**
     * Touch listener which will receive any touch events that this view handles.
     */
    private OnTouchListener mOnTouchListener;

    /**
     * Custom listener which will be notified when an clear event was made from the clear button.
     */
    private OnClearListener onClearListener;

    /**
     * Transition drawable used when showing the clear action.
     */
    private TransitionDrawable mTransitionClearButtonShow = null;

    /**
     * Transition drawable used when hiding the clear action.
     */

    private TransitionDrawable mTransitionClearButtonHide = null;

    private AtomicBoolean isTransitionInProgress = new AtomicBoolean(false);

    private final Handler transitionHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case TRANSITION_HIDE:
                    setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
                    isTransitionInProgress.set(false);
                    break;
                case TRANSITION_SHOW:
                    setCompoundDrawablesWithIntrinsicBounds(0, 0, mImgClearButtonRes, 0);
                    isTransitionInProgress.set(false);
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    };

    /* Required methods, not used in this implementation */
    public ClearableAutoCompleteTextView(Context context) {
        super(context);
        init();
    }

    /* Required methods, not used in this implementation */
    public ClearableAutoCompleteTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    /* Required methods, not used in this implementation */
    public ClearableAutoCompleteTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (getCompoundDrawables()[2] == null) {
            // Pass the touch event to other listeners, if none, the normal flow is resumed
            return null != mOnTouchListener && mOnTouchListener.onTouch(v, event);
        }

        // React only when the UP event is detected
        if (event.getAction() != MotionEvent.ACTION_UP) {
            return false;
        }

        // Detect the clear button area of touch
        int x = (int) event.getX();
        int y = (int) event.getY();
        int left = getWidth() - getPaddingRight() - getCompoundDrawables()[2].getIntrinsicWidth();
        int right = getWidth();
        boolean tappedX = x >= left && x <= right && y >= 0 && y <= (getBottom() - getTop());
        if (tappedX) {
            // Allow clear events only when the transition is not in progress
            if (!isTransitionInProgress.get()) {
                if (!mDisableDefaultFunc) {
                    // Call the default functionality only if it wasn't disabled
                    mDefaultClearListener.onClear();
                }

                if (null != onClearListener) {
                    // Call the custom clear listener so that any member listening is notified of the clear event
                    onClearListener.onClear();
                }
            }
        }

        // Pass the touch event to other listeners, if none, the normal flow is resumed
        return null != mOnTouchListener && mOnTouchListener.onTouch(v, event);
    }

    @Override
    public void setOnTouchListener(OnTouchListener l) {
        // Instead of using the super, we manually handle the touch event (only one listener can exist normally at a
        // time)
        mOnTouchListener = l;
    }

    private void init() {
        // Set the bounds of the button
        setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
        if (getCompoundDrawablePadding() == 0) {
            // We want to have some default padding, in case no one is specified in the xml
            setCompoundDrawablePadding(Dimensions.dpToPx(getContext(), 5f));
        }
        enableTransitionClearButton();

        // if the clear button is pressed, fire up the handler. Otherwise do nothing
        super.setOnTouchListener(this);
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        super.onRestoreInstanceState(state);
        updateClearButton();
    }

    @Override
    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
        super.onTextChanged(text, start, lengthBefore, lengthAfter);
        updateClearButton();
    }

    /**
     * When hiding/showing the clear button an transition drawable will be used instead of the default drawable res.
     */
    public void enableTransitionClearButton() {
        mTransitionClearButtonShow =
                (TransitionDrawable) ContextCompat.getDrawable(getContext(), R.drawable.ic_clear_fade_in);
        mTransitionClearButtonHide =
                (TransitionDrawable) ContextCompat.getDrawable(getContext(), R.drawable.ic_clear_fade_out);
        mTransitionClearButtonShow.setCrossFadeEnabled(true);
        mTransitionClearButtonHide.setCrossFadeEnabled(true);
    }

    /**
     * When hiding/showing the clear button the default drawable res will be used instead of the transition drawable.
     */
    @SuppressWarnings("unused")
    public void disableTransitionClearButton() {
        mTransitionClearButtonShow = null;
        isTransitionInProgress.set(false);
        transitionHandler.removeCallbacks(hideClearButtonRunnable);
        transitionHandler.removeCallbacks(showClearButtonRunnable);
    }

    /**
     * Set an custom listener which will get notified when an clear event was triggered from the clear button.
     *
     * @param clearListener
     *         The listener
     * @param disableDefaultFunc
     *         {@code true} to disable the default clear functionality, usually meaning it will be
     *         handled by the
     *         calling member. {@code false} allow the default functionality of clearing the input.
     *
     * @see #setOnClearListener(OnClearListener)
     */
    public void setOnClearListener(final OnClearListener clearListener, boolean disableDefaultFunc) {
        this.onClearListener = clearListener;
        this.mDisableDefaultFunc = disableDefaultFunc;
    }

    /**
     * Set an custom listener which will get notified when an clear event was triggered from the clear button.
     *
     * @param clearListener
     *         The listener
     *
     * @see #setOnClearListener(OnClearListener, boolean)
     */
    public void setOnClearListener(final OnClearListener clearListener) {
        setOnClearListener(clearListener, false);
    }

    /**
     * Disable the default functionality of the clear event - calling this won't allow for the input to be
     * automatically
     * cleared (it will give the ability to make custom implementations and react to the event before the clear).
     */
    @SuppressWarnings("unused")
    public void disableDefaultClearFunctionality() {
        mDisableDefaultFunc = true;
    }

    /**
     * Enable the default functionality of the clear event. Will automatically clear the input when the clear button is
     * clicked.
     */
    @SuppressWarnings("unused")
    public void enableDefaultClearFunctionality() {
        mDisableDefaultFunc = false;
    }

    /**
     * Automatically show/hide the clear button based on the current input detected.
     * <br/>
     * If there is no input the clear button will be hidden. If there is input detected the clear button will be shown.
     */
    public void updateClearButton() {
        if (isEmpty()) {
            hideClearButton();
        } else {
            showClearButton();
        }
    }

    private boolean isEmpty() {
        if (null == getText()) {
            // Invalid editable text
            return true;
        } else if (TextUtils.isEmpty(getText().toString())) {
            // Empty
            return true;
        } else if (TextUtils.isEmpty(getText().toString().trim())) {
            // White spaces only
            return true;
        }
        return false;
    }

    /**
     * Hide the clear button.
     * <br/>
     * If an transition drawable was provided, it will be used to create an fade out effect, otherwise the default
     * drawable resource will be used.
     */
    public void hideClearButton() {
        if (getCompoundDrawables()[2] == null) {
            // The clear button was already hidden - do nothing
            return;
        }

        if (null == mTransitionClearButtonHide) {
            setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
            return;
        }

        mTransitionClearButtonHide.resetTransition();
        isTransitionInProgress.set(true);

        mTransitionClearButtonHide.startTransition(TRANSITION_TIME);
        setCompoundDrawablesWithIntrinsicBounds(null, null, mTransitionClearButtonHide, null);

        transitionHandler.removeCallbacks(showClearButtonRunnable);
        transitionHandler.removeCallbacks(hideClearButtonRunnable);
        transitionHandler.postDelayed(hideClearButtonRunnable, TRANSITION_TIME);
    }

    /**
     * Show the clear button.
     * <br/>
     * If an transition drawable was provided, it will be used to create an fade in effect, otherwise the default
     * drawable resource will be used.
     */
    public void showClearButton() {
        if (getCompoundDrawables()[2] != null) {
            // The clear button was already set - do nothing
            return;
        }

        if (null == mTransitionClearButtonShow) {
            setCompoundDrawablesWithIntrinsicBounds(0, 0, mImgClearButtonRes, 0);
            return;
        }

        isTransitionInProgress.set(true);

        mTransitionClearButtonShow.startTransition(TRANSITION_TIME);
        setCompoundDrawablesWithIntrinsicBounds(null, null, mTransitionClearButtonShow, null);

        transitionHandler.removeCallbacks(hideClearButtonRunnable);
        transitionHandler.removeCallbacks(showClearButtonRunnable);
        transitionHandler.postDelayed(showClearButtonRunnable, TRANSITION_TIME);
    }

    /**
     * Custom contract which is used to notify any listeners that an clear event was triggered.
     */
    public interface OnClearListener {

        /**
         * Clear event from the clear button triggered.
         */
        void onClear();
    }
}

当我想显示错误时,我使用以下命令:

 // Enable and show the error
mClearView.requestFocus(); // we need to request focus, otherwise the error won't be shown
mClearView.setError("Please choose an valid destination!", null);

如果我使用: mClearView.setError("Please choose an valid destination!"),以便显示错误图标,之后不再显示清除按钮。

1 个答案:

答案 0 :(得分:4)

由于setCompoundDrawablesWithIntrinsicBounds()的文档(不是这样)明确指出后续调用应覆盖先前设置的drawable,而setError()的文档说明如下:

  

右侧复合可绘制的TextView 设置为“错误”图标,并设置将显示在“错误”图标中的错误消息   当TextView具有焦点时弹出。

我认为这是一个错误。

在设置新的drawable之前,调用setError(null)并将先前设置的drawable设置为null是一种可能的解决方法:

void setError(String error) {
    editText.setError(error);
}

void setCompoundDrawableRight(Drawable rightDrawable) {
    editText.setError(null);
    editText.setCompoundDrawables(null, null, null, null);

    editText.setCompoundDrawablesWithIntrinsicBounds(null, null, rightDrawable, null);
}