{"id":2106,"date":"2023-04-01T22:21:40","date_gmt":"2023-04-01T14:21:40","guid":{"rendered":"https:\/\/www.appblog.cn\/?p=2106"},"modified":"2023-04-06T13:55:01","modified_gmt":"2023-04-06T05:55:01","slug":"android-customize-support-for-multilayer-nesting-of-radiobuttons-in-radiogroup","status":"publish","type":"post","link":"https:\/\/www.appblog.cn\/index.php\/2023\/04\/01\/android-customize-support-for-multilayer-nesting-of-radiobuttons-in-radiogroup\/","title":{"rendered":"Android\u81ea\u5b9a\u4e49\u652f\u6301\u591a\u5c42\u5d4c\u5957RadioButton\u7684RadioGroup"},"content":{"rendered":"<p>Android\u539f\u751f\u81ea\u5e26\u7684RadioGroup\u4e0d\u652f\u6301\u5d4c\u5957RadioButton\uff08\u4ece\u6e90\u7801\u53ef\u770b\u51fa\u4ec5\u4ec5\u662f\u5224\u65ad\u5b50\u63a7\u4ef6\u662f\u4e0d\u662fRadioButton\uff09\uff0c\u53c2\u8003RadioGroup\u81ea\u5b9a\u4e49\u4e00\u4e2a\u652f\u6301\u5d4c\u5957CompoundButton\u7684\u63a7\u4ef6XRadioGroup\u3002<\/p>\n<h2>\u81ea\u5b9a\u4e49XRadioGroup<\/h2>\n<p><!-- more --><\/p>\n<pre><code class=\"language-java\">public class XRadioGroup extends LinearLayout {\n    \/\/ holds the checked id; the selection is empty by default\n    private int mCheckedId = -1;\n    \/\/ tracks children radio buttons checked state\n    private CompoundButton.OnCheckedChangeListener mChildOnCheckedChangeListener;\n    \/\/ when true, mOnCheckedChangeListener discards events\n    private boolean mProtectFromCheckedChange = false;\n    private OnCheckedChangeListener mOnCheckedChangeListener;\n    private PassThroughHierarchyChangeListener mPassThroughListener;\n\n    public XRadioGroup(Context context) {\n        super(context);\n        init();\n    }\n\n    public XRadioGroup(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        init();\n    }\n\n    public XRadioGroup(Context context, AttributeSet attrs, int defStyleAttr) {\n        super(context, attrs, defStyleAttr);\n        init();\n    }\n\n    @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n    public XRadioGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {\n        super(context, attrs, defStyleAttr, defStyleRes);\n        init();\n    }\n\n    private void init() {\n        mChildOnCheckedChangeListener = new CheckedStateTracker();\n        mPassThroughListener = new PassThroughHierarchyChangeListener();\n        super.setOnHierarchyChangeListener(mPassThroughListener);\n    }\n\n    \/**\n     * {@inheritDoc}\n     *\/\n    @Override\n    public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {\n        \/\/ the user listener is delegated to our pass-through listener\n        mPassThroughListener.mOnHierarchyChangeListener = listener;\n    }\n\n    \/**\n     * {@inheritDoc}\n     *\/\n    @Override\n    protected void onFinishInflate() {\n        super.onFinishInflate();\n\n        \/\/ checks the appropriate radio button as requested in the XML file\n        if (mCheckedId != -1) {\n            mProtectFromCheckedChange = true;\n            setCheckedStateForView(mCheckedId, true);\n            mProtectFromCheckedChange = false;\n            setCheckedId(mCheckedId);\n        }\n    }\n\n    private void setViewState(View child) {\n        if (child instanceof RadioButton) {\n            final RadioButton button = (RadioButton) child;\n            if (button.isChecked()) {\n                mProtectFromCheckedChange = true;\n                if (mCheckedId != -1) {\n                    setCheckedStateForView(mCheckedId, false);\n                }\n                mProtectFromCheckedChange = false;\n                setCheckedId(button.getId());\n            }\n        } else if (child instanceof ViewGroup) {\n            ViewGroup view = (ViewGroup) child;\n            for (int i = 0; i &lt; view.getChildCount(); i++) {\n                setViewState(view.getChildAt(i));\n            }\n        }\n    }\n\n    @Override\n    public void addView(View child, int index, ViewGroup.LayoutParams params) {\n        setViewState(child);\n        super.addView(child, index, params);\n    }\n\n    \/**\n     * &lt;p&gt;Sets the selection to the radio button whose identifier is passed in\n     * parameter. Using -1 as the selection identifier clears the selection;\n     * such an operation is equivalent to invoking {@link #clearCheck()}.&lt;\/p&gt;\n     *\n     * @param id the unique id of the radio button to select in this group\n     * @see #getCheckedRadioButtonId()\n     * @see #clearCheck()\n     *\/\n    public void check(@IdRes int id) {\n        \/\/ don&#039;t even bother\n        if (id != -1 &amp;&amp; (id == mCheckedId)) {\n            return;\n        }\n\n        if (mCheckedId != -1) {\n            setCheckedStateForView(mCheckedId, false);\n        }\n\n        if (id != -1) {\n            setCheckedStateForView(id, true);\n        }\n\n        setCheckedId(id);\n    }\n\n    private void setCheckedId(@IdRes int id) {\n        mCheckedId = id;\n        if (mOnCheckedChangeListener != null) {\n            mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);\n        }\n    }\n\n    private void setCheckedStateForView(int viewId, boolean checked) {\n        View checkedView = findViewById(viewId);\n        if (checkedView != null &amp;&amp; checkedView instanceof RadioButton) {\n            ((RadioButton) checkedView).setChecked(checked);\n        }\n    }\n\n    \/**\n     * &lt;p&gt;Returns the identifier of the selected radio button in this group.\n     * Upon empty selection, the returned value is -1.&lt;\/p&gt;\n     *\n     * @return the unique id of the selected radio button in this group\n     * @attr ref android.R.styleable#RadioGroup_checkedButton\n     * @see #check(int)\n     * @see #clearCheck()\n     *\/\n    @IdRes\n    public int getCheckedRadioButtonId() {\n        return mCheckedId;\n    }\n\n    \/**\n     * &lt;p&gt;Clears the selection. When the selection is cleared, no radio button\n     * in this group is selected and {@link #getCheckedRadioButtonId()} returns\n     * null.&lt;\/p&gt;\n     *\n     * @see #check(int)\n     * @see #getCheckedRadioButtonId()\n     *\/\n    public void clearCheck() {\n        check(-1);\n    }\n\n    \/**\n     * &lt;p&gt;Register a callback to be invoked when the checked radio button\n     * changes in this group.&lt;\/p&gt;\n     *\n     * @param listener the callback to call on checked state change\n     *\/\n    public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {\n        mOnCheckedChangeListener = listener;\n    }\n\n    \/**\n     * {@inheritDoc}\n     *\/\n    @Override\n    public LayoutParams generateLayoutParams(AttributeSet attrs) {\n        return new LayoutParams(getContext(), attrs);\n    }\n\n    \/**\n     * {@inheritDoc}\n     *\/\n    @Override\n    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {\n        return p instanceof LayoutParams;\n    }\n\n    @Override\n    protected LinearLayout.LayoutParams generateDefaultLayoutParams() {\n        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);\n    }\n\n    @Override\n    public CharSequence getAccessibilityClassName() {\n        return XRadioGroup.class.getName();\n    }\n\n    \/**\n     * &lt;p&gt;This set of layout parameters defaults the width and the height of\n     * the children to {@link #WRAP_CONTENT} when they are not specified in the\n     * XML file. Otherwise, this class ussed the value read from the XML file.&lt;\/p&gt;\n     * &lt;p\/&gt;\n     * &lt;p&gt;See\n     * {@link  LinearLayout Attributes}\n     * for a list of all child view attributes that this class supports.&lt;\/p&gt;\n     *\/\n    public static class LayoutParams extends LinearLayout.LayoutParams {\n        \/**\n         * {@inheritDoc}\n         *\/\n        public LayoutParams(Context c, AttributeSet attrs) {\n            super(c, attrs);\n        }\n\n        \/**\n         * {@inheritDoc}\n         *\/\n        public LayoutParams(int w, int h) {\n            super(w, h);\n        }\n\n        \/**\n         * {@inheritDoc}\n         *\/\n        public LayoutParams(int w, int h, float initWeight) {\n            super(w, h, initWeight);\n        }\n\n        \/**\n         * {@inheritDoc}\n         *\/\n        public LayoutParams(ViewGroup.LayoutParams p) {\n            super(p);\n        }\n\n        \/**\n         * {@inheritDoc}\n         *\/\n        public LayoutParams(MarginLayoutParams source) {\n            super(source);\n        }\n\n        \/**\n         * &lt;p&gt;Fixes the child&#039;s width to\n         * {@link ViewGroup.LayoutParams#WRAP_CONTENT} and the child&#039;s\n         * height to  {@link ViewGroup.LayoutParams#WRAP_CONTENT}\n         * when not specified in the XML file.&lt;\/p&gt;\n         *\n         * @param a          the styled attributes set\n         * @param widthAttr  the width attribute to fetch\n         * @param heightAttr the height attribute to fetch\n         *\/\n        @Override\n        protected void setBaseAttributes(TypedArray a,\n                                         int widthAttr, int heightAttr) {\n\n            if (a.hasValue(widthAttr)) {\n                width = a.getLayoutDimension(widthAttr, &quot;layout_width&quot;);\n            } else {\n                width = WRAP_CONTENT;\n            }\n\n            if (a.hasValue(heightAttr)) {\n                height = a.getLayoutDimension(heightAttr, &quot;layout_height&quot;);\n            } else {\n                height = WRAP_CONTENT;\n            }\n        }\n    }\n\n    \/**\n     * &lt;p&gt;Interface definition for a callback to be invoked when the checked\n     * radio button changed in this group.&lt;\/p&gt;\n     *\/\n    public interface OnCheckedChangeListener {\n        \/**\n         * &lt;p&gt;Called when the checked radio button has changed. When the\n         * selection is cleared, checkedId is -1.&lt;\/p&gt;\n         *\n         * @param group     the group in which the checked radio button has changed\n         * @param checkedId the unique identifier of the newly checked radio button\n         *\/\n        public void onCheckedChanged(XRadioGroup group, @IdRes int checkedId);\n    }\n\n    private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {\n        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {\n            \/\/ prevents from infinite recursion\n            if (mProtectFromCheckedChange) {\n                return;\n            }\n\n            mProtectFromCheckedChange = true;\n            if (mCheckedId != -1) {\n                setCheckedStateForView(mCheckedId, false);\n            }\n            mProtectFromCheckedChange = false;\n\n            int id = buttonView.getId();\n            setCheckedId(id);\n        }\n    }\n\n    \/**\n     * &lt;p&gt;A pass-through listener acts upon the events and dispatches them\n     * to another listener. This allows the table layout to set its own internal\n     * hierarchy change listener without preventing the user to setup his.&lt;\/p&gt;\n     *\/\n    private class PassThroughHierarchyChangeListener implements\n            OnHierarchyChangeListener {\n        private OnHierarchyChangeListener mOnHierarchyChangeListener;\n\n        \/**\n         * {@inheritDoc}\n         *\/\n        public void onChildViewAdded(View parent, View child) {\n            setListener(child);\n\n            if (mOnHierarchyChangeListener != null) {\n                mOnHierarchyChangeListener.onChildViewAdded(parent, child);\n            }\n        }\n\n        \/**\n         * {@inheritDoc}\n         *\/\n        public void onChildViewRemoved(View parent, View child) {\n            removeListener(child);\n\n            if (mOnHierarchyChangeListener != null) {\n                mOnHierarchyChangeListener.onChildViewRemoved(parent, child);\n            }\n        }\n    }\n\n    \/**\n     * \u8bbe\u7f6e\u76d1\u542c\n     *\n     * @param child\n     *\/\n    private void setListener(View child) {\n        if (child instanceof RadioButton) {\n            int id = child.getId();\n            \/\/ generates an id if it&#039;s missing\n            if (id == View.NO_ID) {\n                id = child.hashCode();\n                child.setId(id);\n            }\n            ((RadioButton) child).setOnCheckedChangeListener(\n                    mChildOnCheckedChangeListener);\n        } else if (child instanceof ViewGroup) {\n            ViewGroup view = (ViewGroup) child;\n            for (int i = 0; i &lt; view.getChildCount(); i++) {\n                setListener(view.getChildAt(i));\n            }\n        }\n    }\n\n    \/**\n     * \u79fb\u9664\u76d1\u542c\n     *\n     * @param child\n     *\/\n    private void removeListener(View child) {\n        if (child instanceof RadioButton) {\n            ((RadioButton) child).setOnCheckedChangeListener(null);\n        } else if (child instanceof ViewGroup) {\n            ViewGroup view = (ViewGroup) child;\n            for (int i = 0; i &lt; view.getChildCount(); i++) {\n                removeListener(view.getChildAt(i));\n            }\n        }\n    }\n}<\/code><\/pre>\n<h2>\u5f15\u7528XRadioGroup<\/h2>\n<pre><code class=\"language-java\">xRadioGroup.setOnCheckedChangeListener(new XRadioGroup.OnCheckedChangeListener() {\n    @Override\n    public void onCheckedChanged(XRadioGroup group, int checkedId) {\n        switch (checkedId) {\n            case R.id.rb_no_check:\n\n                break;\n            case R.id.rb_pass:\n\n                break;\n            case R.id.rb_no_pass:\n\n                break;\n        }\n    }\n});<\/code><\/pre>\n<pre><code class=\"language-xml\">&lt;me.yezhou.lib.ui_widget.layout.XRadioGroup\n    android:id=&quot;@+id\/x_radio_group&quot;\n    android:layout_width=&quot;0dp&quot;\n    android:layout_height=&quot;wrap_content&quot;\n    app:layout_constraintTop_toBottomOf=&quot;@id\/iv_photo&quot;\n    app:layout_constraintLeft_toLeftOf=&quot;parent&quot;\n    app:layout_constraintRight_toRightOf=&quot;parent&quot;\n    android:paddingTop=&quot;10dp&quot;\n    android:paddingBottom=&quot;10dp&quot;\n    android:orientation=&quot;horizontal&quot;\n    &gt;\n    &lt;android.support.constraint.ConstraintLayout\n        android:id=&quot;@+id\/layout_no_check&quot;\n        android:layout_width=&quot;match_parent&quot;\n        android:layout_height=&quot;wrap_content&quot;\n        android:layout_weight=&quot;1&quot;\n        &gt;\n        &lt;RadioButton\n            android:id=&quot;@+id\/rb_no_check&quot;\n            android:layout_width=&quot;25dp&quot;\n            android:layout_height=&quot;25dp&quot;\n            app:layout_constraintTop_toTopOf=&quot;parent&quot;\n            app:layout_constraintLeft_toLeftOf=&quot;parent&quot;\n            android:layout_marginLeft=&quot;20dp&quot;\n            android:button=&quot;@null&quot;\n            android:background=&quot;@drawable\/bg_checkbox&quot;\n            android:checked=&quot;true&quot;\n            \/&gt;\n        &lt;TextView\n            android:layout_width=&quot;wrap_content&quot;\n            android:layout_height=&quot;wrap_content&quot;\n            app:layout_constraintLeft_toRightOf=&quot;@id\/rb_no_check&quot;\n            app:layout_constraintTop_toTopOf=&quot;@id\/rb_no_check&quot;\n            app:layout_constraintBottom_toBottomOf=&quot;@id\/rb_no_check&quot;\n            android:layout_marginLeft=&quot;5dp&quot;\n            android:text=&quot;\u672a\u9009\u62e9&quot;\n            \/&gt;\n    &lt;\/android.support.constraint.ConstraintLayout&gt;\n    &lt;android.support.constraint.ConstraintLayout\n        android:id=&quot;@+id\/layout_pass&quot;\n        android:layout_width=&quot;match_parent&quot;\n        android:layout_height=&quot;wrap_content&quot;\n        android:layout_weight=&quot;1&quot;\n        &gt;\n        &lt;RadioButton\n            android:id=&quot;@+id\/rb_pass&quot;\n            android:layout_width=&quot;25dp&quot;\n            android:layout_height=&quot;25dp&quot;\n            app:layout_constraintTop_toTopOf=&quot;parent&quot;\n            app:layout_constraintLeft_toLeftOf=&quot;parent&quot;\n            android:layout_marginLeft=&quot;20dp&quot;\n            android:button=&quot;@null&quot;\n            android:background=&quot;@drawable\/bg_checkbox&quot;\n            \/&gt;\n        &lt;TextView\n            android:layout_width=&quot;wrap_content&quot;\n            android:layout_height=&quot;wrap_content&quot;\n            app:layout_constraintLeft_toRightOf=&quot;@id\/rb_pass&quot;\n            app:layout_constraintTop_toTopOf=&quot;@id\/rb_pass&quot;\n            app:layout_constraintBottom_toBottomOf=&quot;@id\/rb_pass&quot;\n            android:layout_marginLeft=&quot;5dp&quot;\n            android:text=&quot;\u901a\u8fc7&quot;\n            \/&gt;\n    &lt;\/android.support.constraint.ConstraintLayout&gt;\n    &lt;android.support.constraint.ConstraintLayout\n        android:id=&quot;@+id\/layout_no_pass&quot;\n        android:layout_width=&quot;match_parent&quot;\n        android:layout_height=&quot;wrap_content&quot;\n        android:layout_weight=&quot;1&quot;\n        &gt;\n        &lt;RadioButton\n            android:id=&quot;@+id\/rb_no_pass&quot;\n            android:layout_width=&quot;25dp&quot;\n            android:layout_height=&quot;25dp&quot;\n            app:layout_constraintTop_toTopOf=&quot;parent&quot;\n            app:layout_constraintLeft_toLeftOf=&quot;parent&quot;\n            android:layout_marginLeft=&quot;20dp&quot;\n            android:button=&quot;@null&quot;\n            android:background=&quot;@drawable\/bg_checkbox&quot;\n            \/&gt;\n        &lt;TextView\n            android:layout_width=&quot;wrap_content&quot;\n            android:layout_height=&quot;wrap_content&quot;\n            app:layout_constraintLeft_toRightOf=&quot;@id\/rb_no_pass&quot;\n            app:layout_constraintTop_toTopOf=&quot;@id\/rb_no_pass&quot;\n            app:layout_constraintBottom_toBottomOf=&quot;@id\/rb_no_pass&quot;\n            android:layout_marginLeft=&quot;5dp&quot;\n            android:text=&quot;\u672a\u9009\u62e9&quot;\n            \/&gt;\n    &lt;\/android.support.constraint.ConstraintLayout&gt;\n&lt;\/me.yezhou.lib.ui_widget.layout.XRadioGroup&gt;<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Android\u539f\u751f\u81ea\u5e26\u7684RadioGroup\u4e0d\u652f\u6301\u5d4c\u5957RadioButton\uff08\u4ece\u6e90\u7801\u53ef\u770b\u51fa\u4ec5\u4ec5\u662f\u5224\u65ad\u5b50\u63a7\u4ef6\u662f\u4e0d [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[311],"tags":[],"class_list":["post-2106","post","type-post","status-publish","format-standard","hentry","category-android-advance"],"_links":{"self":[{"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/posts\/2106","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/comments?post=2106"}],"version-history":[{"count":0,"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/posts\/2106\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/media?parent=2106"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/categories?post=2106"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/tags?post=2106"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}