Search code examples
androidandroid-preferencesandroid-settings

How to display a RadioButton list in PreferenceScreen (not in a Dialog)


How can a list of radio buttons be shown within a PreferenceSecreen rather than in a dialog? It's really annoying having to click more than once just to select an item from this list.

Expected result

enter image description here

Current result

enter image description here

enter image description here

app_preferences.xml

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">

    <ListPreference
            android:key="prefPhotoFilter"
            android:title="Photo filter"
            android:entries="@array/photoFilters" />

</PreferenceScreen>

strings.xml

<string-array name="photoFilters">
    <item name="1">Natural</item>
    <item name="2">Boosted</item>
    <item name="3">Saturated</item>
</string-array>

enter image description here


Solution

  • According to this Gist, you can extend CheckBoxPreference and create a layout with a radio button.

    Like this: First, create a new layout containing an only radio button let's call it

    preference_widget_radiobutton.xml

    <?xml version="1.0" encoding="utf-8"?>
    <RadioButton xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@android:id/checkbox"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:clickable="false"
            android:focusable="false" />
    

    then create a subclass of CheckBoxPreference:

    class RadioButtonPreference : CheckBoxPreference {
    
        constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) { setView() }
    
        constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { setView() }
    
    
        private fun setView(){
            widgetLayoutResource = R.layout.preference_widget_radiobutton
        }
    
        override fun onClick() {
            if (this.isChecked) 
                return
    
            super.onClick()
        }
    }
    

    in your app_preferences.xml screen:

        <PreferenceCategory android:title="Photo Filters">
            <Your_class_package_dirctory.RadioButtonPreference
                    android:key="naturals"
                    android:title="Natural" />
            <Your_class_package_dirctory.RadioButtonPreference
                    android:key="boosted"
                    android:title="Boosted" />
            <Your_class_package_dirctory.RadioButtonPreference
                    android:key="saturated"
                    android:title="Saturated" />
        </PreferenceCategory>
    

    Now as you can see this will behave like normal CheckBox, it won't get uncheck the previous radio button, to solve this issue:

    In your preference screen code:

    
    class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceClickListener{
        private val sharedPreference = AppPreferences()
    
    
        private var oldCheckedPreference: RadioButtonPreference? = null
    
        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
            setPreferencesFromResource(R.xml.app_preferences, rootKey)
    
            findPreference<RadioButtonPreference>("naturals")?.apply {
                /*
                You can set the defualt button to be checked by:
                  updateCheckedRadioButton(this)
                */
                onPreferenceClickListener = this@SettingsFragment
            }
            findPreference<RadioButtonPreference>("boosted")?.onPreferenceClickListener = this
            findPreference<RadioButtonPreference>("saturated")?.onPreferenceClickListener = this
    
        }
    
        private fun updateCheckedRadioButton(radioButtonPreference: RadioButtonPreference) {
    
            //Uncheck the previous selected button if there is.
            oldCheckedPreference?.isChecked = false
            radioButtonPreference.isChecked = true
            oldCheckedPreference = radioButtonPreference
    
        }
    
        override fun onPreferenceClick(preference: Preference): Boolean {
            if (preference is RadioButtonPreference)
                updateCheckedRadioButton(preference)
    
            return true
        }
    
    }
    
    

    and the result is:

    result