You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义EditText修改Compound Drawable触发无限Focus Change Event问题求助

嘿,这个问题我之前做自定义输入控件时也踩过坑!核心原因就是你在OnFocusChangeListener里修改CompoundDrawable时,系统会触发额外的焦点变更回调——这是因为修改Drawable会触发View的刷新/重布局逻辑,间接导致焦点状态被误判,进而形成无限循环。下面给你几个靠谱的解决思路:

方案1:添加标志位拦截重复回调

这是最直接的解决办法,用一个布尔变量标记当前是否正在主动更新Drawable,避免处理因修改Drawable引发的虚假焦点变更。

public class ClearableEditText extends EditText {
    private boolean isUpdatingDrawable = false;
    private Drawable clearDrawable;

    public ClearableEditText(Context context) {
        super(context);
        init();
    }

    // 别忘了实现其他构造方法(带AttributeSet、defStyleAttr的版本)

    private void init() {
        clearDrawable = getResources().getDrawable(R.drawable.ic_clear);
        clearDrawable.setBounds(0, 0, clearDrawable.getIntrinsicWidth(), clearDrawable.getIntrinsicHeight());
        
        setOnFocusChangeListener((v, hasFocus) -> {
            // 如果正在更新Drawable,直接跳过本次回调
            if (isUpdatingDrawable) {
                return;
            }
            updateClearIconVisibility(hasFocus);
        });

        // 处理清除图标的点击逻辑
        setOnTouchListener((v, event) -> {
            if (getCompoundDrawables()[2] != null) {
                boolean isClearIconClicked = event.getX() > (getWidth() - getPaddingRight() - clearDrawable.getIntrinsicWidth());
                if (isClearIconClicked && event.getAction() == MotionEvent.ACTION_UP) {
                    setText("");
                    return true;
                }
            }
            return false;
        });
    }

    private void updateClearIconVisibility(boolean hasFocus) {
        isUpdatingDrawable = true; // 开启标记
        if (hasFocus && !TextUtils.isEmpty(getText())) {
            setCompoundDrawables(getCompoundDrawables()[0], getCompoundDrawables()[1], clearDrawable, getCompoundDrawables()[3]);
        } else {
            setCompoundDrawables(getCompoundDrawables()[0], getCompoundDrawables()[1], null, getCompoundDrawables()[3]);
        }
        isUpdatingDrawable = false; // 关闭标记
    }
}

这个标志位就像一个“临时开关”,告诉系统:现在是我主动修改Drawable,这时候触发的焦点变化是假的,不用处理,直接切断无限循环的链条。

方案2:仅在必要时更新Drawable

另一种思路是减少不必要的setCompoundDrawables调用——只有当当前Drawable状态和预期不符时,才去修改它,从根源上避免触发额外的焦点回调。

@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
    boolean shouldShowIcon = gainFocus && !TextUtils.isEmpty(getText());
    Drawable currentRightDrawable = getCompoundDrawables()[2];

    // 只有当状态需要变更时,才更新Drawable
    if (shouldShowIcon && currentRightDrawable != clearDrawable) {
        setCompoundDrawables(getCompoundDrawables()[0], getCompoundDrawables()[1], clearDrawable, getCompoundDrawables()[3]);
    } else if (!shouldShowIcon && currentRightDrawable != null) {
        setCompoundDrawables(getCompoundDrawables()[0], getCompoundDrawables()[1], null, getCompoundDrawables()[3]);
    }

    // 必须调用父类方法,保证正常的焦点逻辑
    super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
}

这里我们重写了View自身的onFocusChanged方法,先判断当前是否需要显示图标,再对比现有Drawable的状态,只有不一致时才执行修改操作,避免了无意义的刷新。

方案3:结合TextWatcher优化状态判断

因为图标显示既依赖焦点状态,也依赖文本内容,我们可以把焦点变化和文本变化的逻辑合并,进一步减少不必要的更新:

private void init() {
    clearDrawable = getResources().getDrawable(R.drawable.ic_clear);
    clearDrawable.setBounds(0, 0, clearDrawable.getIntrinsicWidth(), clearDrawable.getIntrinsicHeight());

    // 监听文本变化
    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) {
            updateClearIcon(isFocused());
        }

        @Override
        public void afterTextChanged(Editable s) {}
    });

    // 监听焦点变化
    setOnFocusChangeListener((v, hasFocus) -> {
        updateClearIcon(hasFocus);
    });
}

private void updateClearIcon(boolean hasFocus) {
    boolean shouldShow = hasFocus && !TextUtils.isEmpty(getText());
    Drawable currentRight = getCompoundDrawables()[2];
    if (shouldShow && currentRight != clearDrawable) {
        setCompoundDrawables(getCompoundDrawables()[0], getCompoundDrawables()[1], clearDrawable, getCompoundDrawables()[3]);
    } else if (!shouldShow && currentRight != null) {
        setCompoundDrawables(getCompoundDrawables()[0], getCompoundDrawables()[1], null, getCompoundDrawables()[3]);
    }
}

这个方案把两种状态变化的逻辑统一到一个方法里,确保只有当真正需要变更图标时才执行修改,既解决了无限触发问题,也让代码更简洁。

内容的提问来源于stack exchange,提问作者Justin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 09:56:24