关于RadioGroup的OnCheckChangeListener多次回调的分析及解决方案

        今天和朋友聊天,被问到了一个RadioGroup的回调问题,说在调用clearCheck方法的时候,OnCheckChangeListener回调了两次,对他的业务逻辑造成了影响,我们一起看了看源码中这里的实现,然后想到了一个比较合适的解决方案,在这里给大家分享一下,供遇到类似问题的朋友参考。

        

一、相关的源代码

        想要解决问题,必须要先知道问题是如何产生的,这样才是最高效的,所以我们先看一下为什么会有两次回调!

        我们调用的是clearCheck方法,那么他的源码呢?

    /**
     * <p>Clears the selection. When the selection is cleared, no radio button
     * in this group is selected and {@link #getCheckedRadioButtonId()} returns
     * null.</p>
     *
     * @see #check(int)
     * @see #getCheckedRadioButtonId()
     */
    public void clearCheck() {
        check(-1);
    }

        直接调用check方法的,传参-1

    /**
     * <p>Sets the selection to the radio button whose identifier is passed in
     * parameter. Using -1 as the selection identifier clears the selection;
     * such an operation is equivalent to invoking {@link #clearCheck()}.</p>
     *
     * @param id the unique id of the radio button to select in this group
     *
     * @see #getCheckedRadioButtonId()
     * @see #clearCheck()
     */
    public void check(@IdRes int id) {
        // don't even bother
        if (id != -1 && (id == mCheckedId)) {
            return;
        }

        if (mCheckedId != -1) {
            setCheckedStateForView(mCheckedId, false);
        }

        if (id != -1) {
            setCheckedStateForView(id, true);
        }

        setCheckedId(id);
    }

        -1是RadioGroup的初始值,也是什么都不选中状态的值。mCheckId是当前选中的Button的Id,所以此时认为不是-1的(不选中调用clearCheck也没有意义),那么我们接着看setCheckedStateForView方法是做什么的。

    private void setCheckedStateForView(int viewId, boolean checked) {
        View checkedView = findViewById(viewId);
        if (checkedView != null && checkedView instanceof RadioButton) {
            ((RadioButton) checkedView).setChecked(checked);
        }
    }
        和名称一样,就是改变选中Button的状态,然后继续跟进看后面有什么动作。
    /**
     * <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();
            notifyViewAccessibilityStateChangedIfNeeded(
                    AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);

            // 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;
        }
    }

        这个方法是RadioButton的父类CompoundButton的,看起来有可能和RadioGroup交互的地方就是那两个回调了。

        没错!重点就在mOnCheckChangeWidgetListener上面,这个回调是在RadioGroup上面设置过的,我们在RadioGroup的源码中搜索setOnCheckedChangeWidgetListener,可以看到在onChildViewAdded的回调中有添加监听的代码,看名字就知道是在添加RadioButton后设置的回调。

        /**
         * {@inheritDoc}
         */
        @Override
        public void onChildViewAdded(View parent, View child) {
            if (parent == RadioGroup.this && child instanceof RadioButton) {
                int id = child.getId();
                // generates an id if it's missing
                if (id == View.NO_ID) {
                    id = View.generateViewId();
                    child.setId(id);
                }
                ((RadioButton) child).setOnCheckedChangeWidgetListener(
                        mChildOnCheckedChangeListener);
            }

            if (mOnHierarchyChangeListener != null) {
                mOnHierarchyChangeListener.onChildViewAdded(parent, child);
            }
        }
        继续寻找这个mChildOnCheckedChangeListener的实现是什么样的,可以看到在init的时候,初始化成了CheckedStateTracker对象,我们来看CheckedStateTracker是如何实现的。
    private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            // prevents from infinite recursion
            if (mProtectFromCheckedChange) {
                return;
            }

            mProtectFromCheckedChange = true;
            if (mCheckedId != -1) {
                setCheckedStateForView(mCheckedId, false);
            }
            mProtectFromCheckedChange = false;

            int id = buttonView.getId();
            setCheckedId(id);
        }
    }

        最后,将会执行setCheckedId方法,这个方法在最初调用check(-1)的时候,最后一行也是这样的,所以这个方法被调用了两次,先传入被取消checked的id,再传入-1,我们来看一下具体实现。

    private void setCheckedId(@IdRes int id) {
        mCheckedId = id;
        if (mOnCheckedChangeListener != null) {
            mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
        }
        final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
        if (afm != null) {
            afm.notifyValueChanged(this);
        }
    }

        mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);疑惑解决,在这里进行了对应的回调,这就是我们看到的两次回调。

        根据源码我们看到,只有在使用check方法的时候,才会调用两次setCheckedId方法,所以平时可以直接对RadioButton进行setChecked。

二、解决方案

        知道原因之后,我们的解决方案就很简单了。

        个人觉得比较合适的一种:

        我们可以在OnCheckedChangeListener的回调中对相应的场景做一下处理,简单的示例代码如下:

        rg.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(RadioGroup radioGroup, int i) {
                if(-1 == i) {
                    //此处是清除选中的回调。
                    return;
                }
                View checkedView = findViewById(i);
                if (checkedView != null && checkedView instanceof RadioButton) {
                    if(!((RadioButton) checkedView).isChecked()) {
                        //此处是某个选中按钮被取消的回调,在调用check方法修改选中的时候会触发
                        return;
                    }
                }
                //正常选中我们要进行的处理
            }
        });
    }


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值