Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2006 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.widget;
     18 
     19 import com.android.internal.R;
     20 
     21 import android.content.Context;
     22 import android.content.res.TypedArray;
     23 import android.util.AttributeSet;
     24 import android.view.View;
     25 import android.view.ViewGroup;
     26 
     27 
     28 /**
     29  * <p>This class is used to create a multiple-exclusion scope for a set of radio
     30  * buttons. Checking one radio button that belongs to a radio group unchecks
     31  * any previously checked radio button within the same group.</p>
     32  *
     33  * <p>Intially, all of the radio buttons are unchecked. While it is not possible
     34  * to uncheck a particular radio button, the radio group can be cleared to
     35  * remove the checked state.</p>
     36  *
     37  * <p>The selection is identified by the unique id of the radio button as defined
     38  * in the XML layout file.</p>
     39  *
     40  * <p><strong>XML Attributes</strong></p>
     41  * <p>See {@link android.R.styleable#RadioGroup RadioGroup Attributes},
     42  * {@link android.R.styleable#LinearLayout LinearLayout Attributes},
     43  * {@link android.R.styleable#ViewGroup ViewGroup Attributes},
     44  * {@link android.R.styleable#View View Attributes}</p>
     45  * <p>Also see
     46  * {@link android.widget.LinearLayout.LayoutParams LinearLayout.LayoutParams}
     47  * for layout attributes.</p>
     48  *
     49  * @see RadioButton
     50  *
     51  */
     52 public class RadioGroup extends LinearLayout {
     53     // holds the checked id; the selection is empty by default
     54     private int mCheckedId = -1;
     55     // tracks children radio buttons checked state
     56     private CompoundButton.OnCheckedChangeListener mChildOnCheckedChangeListener;
     57     // when true, mOnCheckedChangeListener discards events
     58     private boolean mProtectFromCheckedChange = false;
     59     private OnCheckedChangeListener mOnCheckedChangeListener;
     60     private PassThroughHierarchyChangeListener mPassThroughListener;
     61 
     62     /**
     63      * {@inheritDoc}
     64      */
     65     public RadioGroup(Context context) {
     66         super(context);
     67         setOrientation(VERTICAL);
     68         init();
     69     }
     70 
     71     /**
     72      * {@inheritDoc}
     73      */
     74     public RadioGroup(Context context, AttributeSet attrs) {
     75         super(context, attrs);
     76 
     77         // retrieve selected radio button as requested by the user in the
     78         // XML layout file
     79         TypedArray attributes = context.obtainStyledAttributes(
     80                 attrs, com.android.internal.R.styleable.RadioGroup, com.android.internal.R.attr.radioButtonStyle, 0);
     81 
     82         int value = attributes.getResourceId(R.styleable.RadioGroup_checkedButton, View.NO_ID);
     83         if (value != View.NO_ID) {
     84             mCheckedId = value;
     85         }
     86 
     87         final int index = attributes.getInt(com.android.internal.R.styleable.RadioGroup_orientation, VERTICAL);
     88         setOrientation(index);
     89 
     90         attributes.recycle();
     91         init();
     92     }
     93 
     94     private void init() {
     95         mChildOnCheckedChangeListener = new CheckedStateTracker();
     96         mPassThroughListener = new PassThroughHierarchyChangeListener();
     97         super.setOnHierarchyChangeListener(mPassThroughListener);
     98     }
     99 
    100     /**
    101      * {@inheritDoc}
    102      */
    103     @Override
    104     public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
    105         // the user listener is delegated to our pass-through listener
    106         mPassThroughListener.mOnHierarchyChangeListener = listener;
    107     }
    108 
    109     /**
    110      * {@inheritDoc}
    111      */
    112     @Override
    113     protected void onFinishInflate() {
    114         super.onFinishInflate();
    115 
    116         // checks the appropriate radio button as requested in the XML file
    117         if (mCheckedId != -1) {
    118             mProtectFromCheckedChange = true;
    119             setCheckedStateForView(mCheckedId, true);
    120             mProtectFromCheckedChange = false;
    121             setCheckedId(mCheckedId);
    122         }
    123     }
    124 
    125     @Override
    126     public void addView(View child, int index, ViewGroup.LayoutParams params) {
    127         if (child instanceof RadioButton) {
    128             final RadioButton button = (RadioButton) child;
    129             if (button.isChecked()) {
    130                 mProtectFromCheckedChange = true;
    131                 if (mCheckedId != -1) {
    132                     setCheckedStateForView(mCheckedId, false);
    133                 }
    134                 mProtectFromCheckedChange = false;
    135                 setCheckedId(button.getId());
    136             }
    137         }
    138 
    139         super.addView(child, index, params);
    140     }
    141 
    142     /**
    143      * <p>Sets the selection to the radio button whose identifier is passed in
    144      * parameter. Using -1 as the selection identifier clears the selection;
    145      * such an operation is equivalent to invoking {@link #clearCheck()}.</p>
    146      *
    147      * @param id the unique id of the radio button to select in this group
    148      *
    149      * @see #getCheckedRadioButtonId()
    150      * @see #clearCheck()
    151      */
    152     public void check(int id) {
    153         // don't even bother
    154         if (id != -1 && (id == mCheckedId)) {
    155             return;
    156         }
    157 
    158         if (mCheckedId != -1) {
    159             setCheckedStateForView(mCheckedId, false);
    160         }
    161 
    162         if (id != -1) {
    163             setCheckedStateForView(id, true);
    164         }
    165 
    166         setCheckedId(id);
    167     }
    168 
    169     private void setCheckedId(int id) {
    170         mCheckedId = id;
    171         if (mOnCheckedChangeListener != null) {
    172             mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
    173         }
    174     }
    175 
    176     private void setCheckedStateForView(int viewId, boolean checked) {
    177         View checkedView = findViewById(viewId);
    178         if (checkedView != null && checkedView instanceof RadioButton) {
    179             ((RadioButton) checkedView).setChecked(checked);
    180         }
    181     }
    182 
    183     /**
    184      * <p>Returns the identifier of the selected radio button in this group.
    185      * Upon empty selection, the returned value is -1.</p>
    186      *
    187      * @return the unique id of the selected radio button in this group
    188      *
    189      * @see #check(int)
    190      * @see #clearCheck()
    191      */
    192     public int getCheckedRadioButtonId() {
    193         return mCheckedId;
    194     }
    195 
    196     /**
    197      * <p>Clears the selection. When the selection is cleared, no radio button
    198      * in this group is selected and {@link #getCheckedRadioButtonId()} returns
    199      * null.</p>
    200      *
    201      * @see #check(int)
    202      * @see #getCheckedRadioButtonId()
    203      */
    204     public void clearCheck() {
    205         check(-1);
    206     }
    207 
    208     /**
    209      * <p>Register a callback to be invoked when the checked radio button
    210      * changes in this group.</p>
    211      *
    212      * @param listener the callback to call on checked state change
    213      */
    214     public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
    215         mOnCheckedChangeListener = listener;
    216     }
    217 
    218     /**
    219      * {@inheritDoc}
    220      */
    221     @Override
    222     public LayoutParams generateLayoutParams(AttributeSet attrs) {
    223         return new RadioGroup.LayoutParams(getContext(), attrs);
    224     }
    225 
    226     /**
    227      * {@inheritDoc}
    228      */
    229     @Override
    230     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    231         return p instanceof RadioGroup.LayoutParams;
    232     }
    233 
    234     @Override
    235     protected LinearLayout.LayoutParams generateDefaultLayoutParams() {
    236         return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    237     }
    238 
    239     /**
    240      * <p>This set of layout parameters defaults the width and the height of
    241      * the children to {@link #WRAP_CONTENT} when they are not specified in the
    242      * XML file. Otherwise, this class ussed the value read from the XML file.</p>
    243      *
    244      * <p>See
    245      * {@link android.R.styleable#LinearLayout_Layout LinearLayout Attributes}
    246      * for a list of all child view attributes that this class supports.</p>
    247      *
    248      */
    249     public static class LayoutParams extends LinearLayout.LayoutParams {
    250         /**
    251          * {@inheritDoc}
    252          */
    253         public LayoutParams(Context c, AttributeSet attrs) {
    254             super(c, attrs);
    255         }
    256 
    257         /**
    258          * {@inheritDoc}
    259          */
    260         public LayoutParams(int w, int h) {
    261             super(w, h);
    262         }
    263 
    264         /**
    265          * {@inheritDoc}
    266          */
    267         public LayoutParams(int w, int h, float initWeight) {
    268             super(w, h, initWeight);
    269         }
    270 
    271         /**
    272          * {@inheritDoc}
    273          */
    274         public LayoutParams(ViewGroup.LayoutParams p) {
    275             super(p);
    276         }
    277 
    278         /**
    279          * {@inheritDoc}
    280          */
    281         public LayoutParams(MarginLayoutParams source) {
    282             super(source);
    283         }
    284 
    285         /**
    286          * <p>Fixes the child's width to
    287          * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and the child's
    288          * height to  {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
    289          * when not specified in the XML file.</p>
    290          *
    291          * @param a the styled attributes set
    292          * @param widthAttr the width attribute to fetch
    293          * @param heightAttr the height attribute to fetch
    294          */
    295         @Override
    296         protected void setBaseAttributes(TypedArray a,
    297                 int widthAttr, int heightAttr) {
    298 
    299             if (a.hasValue(widthAttr)) {
    300                 width = a.getLayoutDimension(widthAttr, "layout_width");
    301             } else {
    302                 width = WRAP_CONTENT;
    303             }
    304 
    305             if (a.hasValue(heightAttr)) {
    306                 height = a.getLayoutDimension(heightAttr, "layout_height");
    307             } else {
    308                 height = WRAP_CONTENT;
    309             }
    310         }
    311     }
    312 
    313     /**
    314      * <p>Interface definition for a callback to be invoked when the checked
    315      * radio button changed in this group.</p>
    316      */
    317     public interface OnCheckedChangeListener {
    318         /**
    319          * <p>Called when the checked radio button has changed. When the
    320          * selection is cleared, checkedId is -1.</p>
    321          *
    322          * @param group the group in which the checked radio button has changed
    323          * @param checkedId the unique identifier of the newly checked radio button
    324          */
    325         public void onCheckedChanged(RadioGroup group, int checkedId);
    326     }
    327 
    328     private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {
    329         public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    330             // prevents from infinite recursion
    331             if (mProtectFromCheckedChange) {
    332                 return;
    333             }
    334 
    335             mProtectFromCheckedChange = true;
    336             if (mCheckedId != -1) {
    337                 setCheckedStateForView(mCheckedId, false);
    338             }
    339             mProtectFromCheckedChange = false;
    340 
    341             int id = buttonView.getId();
    342             setCheckedId(id);
    343         }
    344     }
    345 
    346     /**
    347      * <p>A pass-through listener acts upon the events and dispatches them
    348      * to another listener. This allows the table layout to set its own internal
    349      * hierarchy change listener without preventing the user to setup his.</p>
    350      */
    351     private class PassThroughHierarchyChangeListener implements
    352             ViewGroup.OnHierarchyChangeListener {
    353         private ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener;
    354 
    355         /**
    356          * {@inheritDoc}
    357          */
    358         public void onChildViewAdded(View parent, View child) {
    359             if (parent == RadioGroup.this && child instanceof RadioButton) {
    360                 int id = child.getId();
    361                 // generates an id if it's missing
    362                 if (id == View.NO_ID) {
    363                     id = child.hashCode();
    364                     child.setId(id);
    365                 }
    366                 ((RadioButton) child).setOnCheckedChangeWidgetListener(
    367                         mChildOnCheckedChangeListener);
    368             }
    369 
    370             if (mOnHierarchyChangeListener != null) {
    371                 mOnHierarchyChangeListener.onChildViewAdded(parent, child);
    372             }
    373         }
    374 
    375         /**
    376          * {@inheritDoc}
    377          */
    378         public void onChildViewRemoved(View parent, View child) {
    379             if (parent == RadioGroup.this && child instanceof RadioButton) {
    380                 ((RadioButton) child).setOnCheckedChangeWidgetListener(null);
    381             }
    382 
    383             if (mOnHierarchyChangeListener != null) {
    384                 mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
    385             }
    386         }
    387     }
    388 }
    389