// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.chrome.browser.widget; import android.content.Context; import android.content.res.TypedArray; import android.os.Bundle; import android.os.Parcelable; import android.util.AttributeSet; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.widget.RadioButton; import android.widget.RelativeLayout; import android.widget.TextView; import org.chromium.chrome.R; import java.util.List; /** * A RadioButton with a title and descriptive text to the right. */ public class RadioButtonWithDescription extends RelativeLayout implements OnClickListener { private RadioButton mRadioButton; private TextView mTitle; private TextView mDescription; private List<RadioButtonWithDescription> mGroup; private static final String SUPER_STATE_KEY = "superState"; private static final String CHECKED_KEY = "isChecked"; /** * Constructor for inflating via XML. */ public RadioButtonWithDescription(Context context, AttributeSet attrs) { super(context, attrs); LayoutInflater.from(context).inflate(R.layout.radio_button_with_description, this, true); mRadioButton = (RadioButton) findViewById(R.id.radio_button); mTitle = (TextView) findViewById(R.id.title); mDescription = (TextView) findViewById(R.id.description); if (attrs != null) applyAttributes(attrs); // We want RadioButtonWithDescription to handle the clicks itself. mRadioButton.setClickable(false); setOnClickListener(this); } private void applyAttributes(AttributeSet attrs) { TypedArray a = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.RadioButtonWithDescription, 0, 0); String titleText = a.getString(R.styleable.RadioButtonWithDescription_titleText); if (titleText != null) mTitle.setText(titleText); a.recycle(); } @Override public void onClick(View v) { if (mGroup != null) { for (RadioButtonWithDescription button : mGroup) { button.setChecked(false); } } setChecked(true); } /** * Sets the text shown in the title section. */ public void setTitleText(CharSequence text) { mTitle.setText(text); } /** * Sets the text shown in the description section. */ public void setDescriptionText(CharSequence text) { mDescription.setText(text); } /** * Returns true if checked. */ public boolean isChecked() { return mRadioButton.isChecked(); } /** * Sets the checked status. */ public void setChecked(boolean checked) { mRadioButton.setChecked(checked); } /** * Sets the group of RadioButtonWithDescriptions that should be unchecked when this button is * checked. * @param group A list containing all elements of the group. */ public void setRadioButtonGroup(List<RadioButtonWithDescription> group) { mGroup = group; } @Override protected Parcelable onSaveInstanceState() { // Since this View is designed to be used multiple times in the same layout and contains // children with ids, Android gets confused. This is because it will see two Views in the // hierarchy with the same id (eg, the RadioButton that makes up this View). // // eg: // LinearLayout (no id): // |-> RadioButtonWithDescription (id=sync_confirm_import_choice) // | |-> RadioButton (id=radio_button) // | |-> TextView (id=title) // | \-> TextView (id=description) // \-> RadioButtonWithDescription (id=sync_keep_separate_choice) // |-> RadioButton (id=radio_button) // |-> TextView (id=title) // \-> TextView (id=description) // // This causes the automagic state saving and recovery to do the wrong thing and restore all // of these Views to the state of the last one it saved. // Therefore we manually save the state of the child Views here so the state can be // associated with the id of the RadioButtonWithDescription, which should be unique and // not the id of the RadioButtons, which will be duplicated. // // Note: We disable Activity recreation on many config changes (such as orientation), // but not for all of them (eg, locale or font scale change). So this code will only be // called on the latter ones, or when the Activity is destroyed due to memory constraints. Bundle saveState = new Bundle(); saveState.putParcelable(SUPER_STATE_KEY, super.onSaveInstanceState()); saveState.putBoolean(CHECKED_KEY, isChecked()); return saveState; } @Override protected void onRestoreInstanceState(Parcelable state) { if (state instanceof Bundle) { super.onRestoreInstanceState(((Bundle) state).getParcelable(SUPER_STATE_KEY)); setChecked(((Bundle) state).getBoolean(CHECKED_KEY)); } else { super.onRestoreInstanceState(state); } } @Override protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { // This method and dispatchRestoreInstanceState prevent the Android automagic state save // and restore from touching this View's children. dispatchFreezeSelfOnly(container); } @Override protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { dispatchThawSelfOnly(container); } }