功能场景

最近做了一个远程控制车辆的功能

1)每个功能定义了一个组合型的自定义View A,里面有一个CheckBox B,点击A或者B能控制开关B的状态。但是“闪灯鸣笛”这一项,如果点击了B,并不是一点击B就去反转CheckBox状态,而是不处理事件。
2)整个的点击交由热区A的点击事件处理,弹出Dialog,让用户选择“闪灯”、“闪灯鸣笛”、“鸣笛”三个选项,由于这三个选项可能都不会选中,所以无法使用RadioButton,仍然选择使用CheckBox处理。

“闪灯”、“闪灯鸣笛”、“鸣笛”三个选项选择状态的变化,对于组合型的自定义View A要对应地联动变化,所以每个选项都监听setOnCheckedChangeListener。“闪灯”、“闪灯鸣笛”、“鸣笛”三个选项选择状态的变化的原因可能有2种:第1种,自身被点击了;第2种,其它的选项点击了,导致自己状态的变化。对于第2种变化,并不需要处理setOnCheckedChangeListener的逻辑,只要选项的状态变化了就行。

问题:正常情况下,CheckBox一点击就会反转状态,回调onCheckedChanged方法。
需求:我在这个场景下的业务需求有2个

1)屏蔽CheckBox的点击事件,可以设置不允许手动点击改变状态的CheckBox,只能通过调用setChecked或者toggle()方法改变选中状态。
2)可以监听只有在手动点击下状态改变,通过setChecked或者toggle()触发的状态变化则不监听。

分析:

1) CheckBox为什么点击会反转状态?
2)CheckBox是如何反转状态的?

所以带着上面的两个问题去查看CheckBox的源码:
CheckBox继承CompoundButton,CompoundButton复写了performClick方法,里面调用了
toggle()方法,toggle()方法调用了setChecked(!mChecked)。

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

        final boolean handled = super.performClick();
        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;
    }
/**
     * <p>Changes the checked state of this button.</p>
     *
     * @param checked true to check the button, false to uncheck it
     */
    @Override
    public void setChecked(boolean checked) {
        if (mChecked != checked) {
            mCheckedFromResource = false;
            mChecked = checked;
            refreshDrawableState();

            // Avoid infinite recursions if setChecked() is called from a listener
            if (mBroadcasting) {
                return;
            }

            mBroadcasting = true;
            if (mOnCheckedChangeListener != null) {
                mOnCheckedChangeListener.onCheckedChanged(this, mChecked);
            }
            if (mOnCheckedChangeWidgetListener != null) {
                mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);
            }
            final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
            if (afm != null) {
                afm.notifyValueChanged(this);
            }

            mBroadcasting = false;
        }
        // setStateDescription will not send out event if the description is unchanged.
        setDefaultStateDescritption();
    }
处理办法:

1.自定义CheckBox,复写其onTouchEvent方法,控制是否处理事件。

   @Override
    public boolean onTouchEvent(MotionEvent event) {
        isTouch = true;
        if(canToggle){
            return super.onTouchEvent(event);
        }else{
            //这里要设置成false,因为我想点击CheckBox时,CheckBox不要拦截事件。
            return false;
        }
    }

2.自定义OnTouchCheckedChangeListener,限制只有Touch事件触发的onChange才去回调。

public void setOnTouchCheckedChangeListener(OnTouchCheckedChangeListener onTouchCheckedChangeListener) {
        this.onTouchCheckedChangeListener = onTouchCheckedChangeListener;
        this.setOnCheckedChangeListener(new OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if(isTouch && onTouchCheckedChangeListener != null){
                    onTouchCheckedChangeListener.onCheckedChanged(buttonView, isChecked);
                }
                isTouch = false;
            }
        });
    }

    /**
     * 只监听Touch状态下的状态变化
     */
    public abstract static class OnTouchCheckedChangeListener implements OnCheckedChangeListener{}

最终自定义CheckBox的源码

package com.szlanyou.baseappmodule.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.CheckBox;
import android.widget.CompoundButton;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

/**
 * @author 陈章
 * create at 2020/12/4 14:32
 * desc:
 * 1)可以设置不允许手动点击改变状态的CheckBox,只能通过调用{@link #setChecked(boolean)}或者{@link #toggle()}方法改变选中状态。
 * 2)可以监听只有在手动点击下状态改变,通过{@link #setChecked(boolean)}或者{@link #toggle()}触发的状态变化则不监听。
 */
public class ToggleControlCheckBox extends androidx.appcompat.widget.AppCompatCheckBox {
    private boolean canToggle = true;
    private boolean isTouch = false;

    private OnTouchCheckedChangeListener onTouchCheckedChangeListener;

    public ToggleControlCheckBox(@NonNull Context context) {
        super(context);
    }

    public ToggleControlCheckBox(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public ToggleControlCheckBox(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        isTouch = true;
        if(canToggle){
            return super.onTouchEvent(event);
        }else{
            //这里要设置成false,因为我想点击CheckBox时,CheckBox不要拦截事件。
            return false;
        }
    }

    public void setCanToggle(boolean canToggle) {
        this.canToggle = canToggle;
    }

    public OnTouchCheckedChangeListener getOnTouchCheckedChangeListener() {
        return onTouchCheckedChangeListener;
    }

    public void setOnTouchCheckedChangeListener(OnTouchCheckedChangeListener onTouchCheckedChangeListener) {
        this.onTouchCheckedChangeListener = onTouchCheckedChangeListener;
        this.setOnCheckedChangeListener(new OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if(isTouch && onTouchCheckedChangeListener != null){
                    onTouchCheckedChangeListener.onCheckedChanged(buttonView, isChecked);
                }
                isTouch = false;
            }
        });
    }

    /**
     * 只监听Touch状态下的状态变化
     */
    public abstract static class OnTouchCheckedChangeListener implements OnCheckedChangeListener{}
}


0 条评论

发表回复

您的电子邮箱地址不会被公开。