SwitchCompact拦截点击事件

为了解决项目中点击SwitchCompact时根据条件控制是否滚动的问题,本文深入源码分析了Toggle方法的调用路径。从CompoundButton的performClick方法到TextView的action_click,最终找到点击事件的源头。通过重写toggle方法,可以实现点击事件的拦截和自定义行为,例如禁止或允许滑动。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

因为项目需要,点击的时候,根据相应的条件判断,如果条件成立,滑块滚动,否则不滚动。然后网上查了一圈,没有什么好的办法,只能看源码,解决这个问题。思路是这样的,我找了一下那个check的改变位置,找到两处,一个是toggle方法,另外一个是setCheck方法。我们知道setCheck方法是我们可以自己设置,这个显示不是点击的时候,做的事,然后我就跟踪toggle方法,看谁调用了它。这个是switchcompact的toggle方法源码

 public void toggle() {
        this.setChecked(!this.isChecked());
    }

那谁调用了呢?SwitchCompact没有调用,应该是父类调用了这个方法,父类是谁呢CompoundButton,它也有toggle方法,

@Override
    public void toggle() {
        setChecked(!mChecked);
    }

发现该类的performClick方法调用,这个不是点击事件嘛?

    @Override
    public boolean performClick() {
        toggle();

        final boolean handled = super.performClick();
//这个方法调用,onclicklistener.onClick此才会调用,此时toggle已经调用了,
//所以onClick里面进行事件间隔进行拦截的方案不可行
        if (!handled) {
            // View only makes a sound effect if the onClickListener was
            // called, so we'll need to make one here instead.
            playSoundEffect(SoundEffectConstants.CLICK);
        }

        return handled;
    }

其实performclick这个方法,熟悉事件分发机制的同学应该知道,是我们处理处理点击事件的时候

//View.java 方法以及代码片段 


private boolean performClickInternal() {
        // Must notify autofill manager before performing the click actions to avoid scenarios where
        // the app has a click listener that changes the state of views the autofill service might
        // be interested on.
        notifyAutofillManagerOnClick();

        return performClick();
    }

   public boolean performClick() {
        // We still need to call this method to handle the cases where performClick() was called
        // externally, instead of through performClickInternal()
        notifyAutofillManagerOnClick();

        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
//平时setOnclicklistener的业务调用位置,所以在onlcicklistener里面进行时间间隔判断,这个时候checked已经调用了
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        notifyEnterOrExitForAutoFillIfNeeded(true);

        return result;
    }

//点击事件执行Runable

 private final class PerformClick implements Runnable {
        @Override
        public void run() {
            recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
            performClickInternal();
        }
    }

而Runbale的执行则在onTouchEvent里面post执行的,由于代码太多,则截取片段
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClickInternal();
                                }
                            }
                        }
     

而更改的逻辑在toggle里面,所以我们重写perfomClick或者toggle都可以拦截对状态的更改。

class CustomSwitch : SwitchCompat {

    private var action: (() -> Boolean)? = null

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    var canClick: Boolean = false

    private var lastClickTime: Long = 0


    fun setClickAction(clickAction: () -> Boolean) {
        action = clickAction
    }

    override fun toggle() {
        //两次点击间隔超过300ms才给执行performClick时间
        val currentTime = System.currentTimeMillis()
        if (Math.abs(currentTime - lastClickTime) > 300) {
            lastClickTime = currentTime
            if (action == null) {
                super.toggle()
            } else {
                action?.invoke()?.apply {
                    if (this && !canClick) {
                        canClick = true
                        super.toggle()
                    }
                }
            }
        } else {
            lastClickTime = currentTime
        }
    }


    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        action = null
    }


    override fun onTouchEvent(ev: MotionEvent?): Boolean {
        //过滤move事件
        if (ev?.action == MotionEvent.ACTION_MOVE) return false
        return super.onTouchEvent(ev)
    }


}

对于该类可以继续扩展,比如滑动啊,我这里做的处理是禁止滑动,你也可以根据自己的情况,让它滑动。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值