1. 程式人生 > >關於RadioGroup的OnCheckChangeListener多次回撥的分析及解決方案

關於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;
                    }
                }
                //正常選中我們要進行的處理
            }
        });
    }

相關推薦

android.database.ContentObserver#onChange(boolean, android.net.Uri) 的妥協處理方案

android.database.ContentObserver#onChange(boolean, android.net.Uri) 在呼叫資料庫監聽的方法的時候發現,該方法會被多次觸發。比如,只是

關於RadioGroup的OnCheckChangeListener分析解決方案

        今天和朋友聊天,被問到了一個RadioGroup的回撥問題,說在呼叫clearCheck方法的時候,OnCheckChangeListener回調了兩次,對他的業務邏輯造成了影響,我們一起看了看原始碼中這裡的實現,然後想到了一個比較合適的解決方案,在這裡給大家

微擎個平臺、域名共用一個公眾號Oauth域名終極解決方案

通宵搞了一晚,終於把這個問題解決了! 需求是這樣的 現在有3個不同的域名,一個認證的微信公眾號,由於公眾號授權回撥頁面域名只能寫一個,因此對於我有多個微擎系統分別部署在不同伺服器上(一臺做營銷業務、一臺做分銷系統、一臺做餐飲系統),分別部署的原因是主要是兩點:分流和防止一個域名被封(主要是微信營

Java Web開發中,自定義過濾器被執行兩的原因分析解決辦法

本文出處:http://blog.csdn.net/chaijunkun/article/details/7646338,轉載請註明。由於本人不定期會整理相關博文,會對相應內容作出完善。因此強烈建議在原始出處檢視此文。 在Java Web開發過程中,我們可以使用過濾器和Sp

RadioGroup呼叫check(id)方法時,OnCheckedChangedListener被問題

在程式碼中選中RadioGroup其中某一個RadioButton選項,有兩種方式: 1. RadioGroup.check(radioButtonId)方法; 2. 直接控制該RadioButton,使用RadioButtton.setChecked(true

微信網頁授權code請求的坑

在之前的專案裡需要用到微信授權,我也是一次第一次用,看著微信開發文件寫了,然後就掉進了一個坑裡。剛開始在授權頁面寫的微信授權連結:https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx5250b8b9d4cfd

AppStore IPv6-only審核被拒原因分析解決方案

穩定 eip ios 穩定性 only 應用服務器 http 一個 搭建 AppStore IPv6-only審核被拒原因分析及解決方案 http://www.jianshu.com/p/8edfdfa20b29 自2016年6月1日起,蘋果要求所有提交App St

MYSQL主從不同步延遲原理分析解決方案

run 步長 syn class ati 可能 16px 一點 表示 1.網絡的延遲由於mysql主從復制是基於binlog的一種異步復制,通過網絡傳送binlog文件,理所當然網絡延遲是主從不同步的絕大多數的原因,特別是跨機房的數據同步出現這種幾率非常的大,所以做讀寫分離

Vue資料繫結後文本閃爍問題分析解決方案

Vue文字閃爍問題 一、問題描述 程式碼示例: <div id="app"> <span>{{user.userName}}</span> </div> 頁面顯示: {{user.userName}} 問題詳情:在載

製造業MES系統需求分析解決方案

MES系統面向生產管理人員,滿足排產、排程,瞭解整體生產資訊的需求,支援線上生產資訊廣播,提供廠內精益物料管理,物料拉動,生產質量管控。排產和跟蹤系統制定上線順序,並下發到車間控制PLC裡負責執行。MES生成系統內部訂單數量範圍內的計劃,所以MES不用關心,由於生產模式造成的插單問題(上次訂單未生產完成,造成

制造業MES系統需求分析解決方案

及其 情況 系統架構 看板 分類 生產訂單 活性 軟件源代碼 一周 MES系統面向生產管理人員,滿足排產、調度,了解整體生產信息的需求,支持在線生產信息廣播,提供廠內精益物料管理,物料拉動,生產質量管控。排產和跟蹤系統制定上線順序,並下發到車間控制PLC裏負責執行。MES生

Android記憶體洩漏問題分析解決方案

大家新年好,由於工作繁忙原因,有好一段時間沒有更新博文了(當然Github是一直都有更新的),趁著年底有點放假時間,我覺得抽空更新下部落格,總結一下工作中最常見記憶體洩漏問題,也是自己之前踩過的坑,為了讓大家少走彎路,系統全面總結一下記憶體洩漏問題分析原因及尋找解決方案。 概念 首

AVR燒錯熔絲到恢復的一經驗----詳細分析解決方案

AVR燒錯熔絲到恢復的一次經驗----詳細分析與解決方案---winsu(ant,ant的筆記的blog)環境目標器件:MEGA64L燒錄軟體:PonyProg2000 (Version  2.06c Beta  Jul 27 2003)燒錄硬體:按

Spark 日誌錯誤資訊分析解決方案:log4j、SLF4j

Spark 日誌錯誤資訊 異常資訊:( 解決了好久的問題 ) 1、log4j錯誤類「org.apache.log4j.Appender」被載入,「org.apache.log4j.ConsoleAppender」不能分配給「org.apache.log4j.

Lost executor 原因分析解決方案-記錄

Lost executor node 丟失原因:         1 、有時候是物理機導致的 node 暫時丟失,物理機恢復後   容器也自己恢復。如io過高

virtualbox安裝redhat,正常關閉後需要重新安裝原因分析解決方案

環境:win10 virtualbox   X   redhat 裝了個虛擬機器,緊接著要給虛擬機器裝各種東西,然後yum install 發現因為沒有註冊環境,yum源是用不了的。 這當然是小嘍囉級的問題啦,配置一個本地yum源,然

【轉】SignalR站點瀏覽器連線數限制分析解決方案

SignalR 搭建實時重新整理應用雖然非常方便,但是有個問題你必須考慮到,就是一般的瀏覽器,對於SignalR的全雙工通訊方式,絕大多數瀏覽器都只支援6個新視窗,如果你開啟第7個,那麼新的框口頁面是不會載入的。 這其實是客戶端瀏覽器的限制,這是以連線的域名為單位來限制,

pausetimeout問題流程分析解決方案

這次出現問題的堆疊是處理activity的pausetimeout的堆疊,在分析問題之前我們先了解下pausetimeout。下其大致流程如下(Android P程式碼) pausetimeout觸發機制 在我們需要對當前resumed的activity做pause操作的時

談談Mysql主從同步延遲分析解決方案

一、MySQL的資料庫主從複製原理 MySQL主從複製實際上基於二進位制日誌,原理可以用一張圖來表示: 分為四步走: 1. 主庫對所有DDL和DML產生的日誌寫進binlog; 2. 主庫生成一個 log dump 執行緒,用來給從庫I/O執行緒讀取binlog;

【JVM學習筆記】(一)jvm初體驗-記憶體溢位問題分析解決方案

####1、開始 建立Main類和Demo類,在Main類的main方法中建立List,並向List中無限建立Demo物件,造成記憶體溢位, 並輸出記憶體溢位錯誤檔案在專案目錄下,為了使等待時間減小,設定執行堆記憶體大小。 ####2、建立Demo類 package com.ch