25

I have seen many customized solutions and answers to this question. I need something very simple, I have a preference activity and all I need is that one of the options will open dialog with a number picker and save the results. Can you please guide me step by step with how to accomplish this?

public class SettingsActivity extends PreferenceActivity
{
    @Override
    protected void onCreate(final Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        getFragmentManager().beginTransaction().replace(android.R.id.content, new MyPreferenceFragment()).commit();
        //requestWindowFeature(Window.FEATURE_NO_TITLE);    
    }

    public static class MyPreferenceFragment extends PreferenceFragment
    {
        @Override
        public void onCreate(final Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);
            addPreferencesFromResource(R.xml.prefs);

        }
    }

}

XML:

    <SwitchPreference
        android:key="cross"
        android:summaryOff="Cross is invisible"
        android:summaryOn="Cross is visible"
        android:switchTextOff="OFF"
        android:switchTextOn="ON"
        android:title="Cross" 
        android:defaultValue="true"/>

    <SwitchPreference
        android:key="autoP"
        android:summaryOff="App will go to sleep"
        android:summaryOn="App will not go to sleep"
        android:switchTextOff="OFF"
        android:switchTextOn="ON"
        android:title="Always On" 
        android:defaultValue="true"/>

    <SwitchPreference
        android:key="tempD"
        android:summaryOff="Temprature not displayed"
        android:summaryOn="Temprature displayed"
        android:switchTextOff="OFF"
        android:switchTextOn="ON"
        android:title="Tempature Display" 
        android:defaultValue="true"/>

    <ListPreference
        android:entries="@array/units"
        android:entryValues="@array/lunits"
        android:key="listUnits"
        android:summary="Units schosssing"
        android:title="Units" android:defaultValue="C"/>

     <!--Need to add button to open dialog-->       

</PreferenceScreen>

Number Picker XML:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <NumberPicker
        android:id="@+id/numberPicker1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="64dp" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/numberPicker1"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="98dp"
        android:layout_toRightOf="@+id/numberPicker1"
        android:text="Cancel" />

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/button2"
        android:layout_alignBottom="@+id/button2"
        android:layout_marginRight="16dp"
        android:layout_toLeftOf="@+id/numberPicker1"
        android:text="Set" />

</RelativeLayout>
Sergii Lagutin
  • 10,561
  • 1
  • 34
  • 43
Dim
  • 4,527
  • 15
  • 80
  • 139

4 Answers4

57

Subclass DialogPreference to build your own NumberPickerPreference.

I have implemented one below for you. It works perfectly fine, but is not feature complete. For example the minimum and maximum values are hard-coded constants. These should really be attributes on the preference xml declaration. To get that to work you would need to add an attrs.xml file specifying your custom attributes.

For the full implementation of the NumberPicker preference widget that supports custom xml attributes in a library project and a demo app showing how to use it, see GitHub: https://github.com/Alobar/AndroidPreferenceTest

You would use the widget as any other preference widget, except you have to fully qualify the name:

preferences.xml

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">

    <com.example.preference.NumberPickerPreference
        android:key="key_number"
        android:title="Give me a number"
        android:defaultValue="55" />

</PreferenceScreen>

NumberPickerPreference.java

package com.example.preference;

import android.content.Context;
import android.content.res.TypedArray;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.NumberPicker;

/**
 * A {@link android.preference.Preference} that displays a number picker as a dialog.
 */
public class NumberPickerPreference extends DialogPreference {

    // allowed range
    public static final int MAX_VALUE = 100;
    public static final int MIN_VALUE = 0;
    // enable or disable the 'circular behavior'
    public static final boolean WRAP_SELECTOR_WHEEL = true; 

    private NumberPicker picker;
    private int value;

    public NumberPickerPreference(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public NumberPickerPreference(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected View onCreateDialogView() {
        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        layoutParams.gravity = Gravity.CENTER;

        picker = new NumberPicker(getContext());
        picker.setLayoutParams(layoutParams);

        FrameLayout dialogView = new FrameLayout(getContext());
        dialogView.addView(picker);

        return dialogView;
    }

    @Override
    protected void onBindDialogView(View view) {
        super.onBindDialogView(view);
        picker.setMinValue(MIN_VALUE);
        picker.setMaxValue(MAX_VALUE);
        picker.setWrapSelectorWheel(WRAP_SELECTOR_WHEEL);
        picker.setValue(getValue());
    }

    @Override
    protected void onDialogClosed(boolean positiveResult) {
        if (positiveResult) {
            picker.clearFocus();
            int newValue = picker.getValue();
            if (callChangeListener(newValue)) {
                setValue(newValue);
            }
        }
    }

    @Override
    protected Object onGetDefaultValue(TypedArray a, int index) {
        return a.getInt(index, MIN_VALUE);
    }

    @Override
    protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
        setValue(restorePersistedValue ? getPersistedInt(MIN_VALUE) : (Integer) defaultValue);
    }

    public void setValue(int value) {
        this.value = value;
        persistInt(this.value);
    }

    public int getValue() {
        return this.value;
    }
}
Rob Meeuwisse
  • 2,847
  • 1
  • 17
  • 21
  • 1
    onDialogClosed should call if (callChangeListener(value)) { setValue(picker.getValue()); } In order to get proper change notification (Note that the stock Preferences implementation in Andoird Studio relies on this to update preference summaries). – Robin Davies Jul 02 '15 at 17:11
  • @RobinDavies Thanks, I totally missed that bit of infrastructure and I have updated the code. – Rob Meeuwisse Jul 03 '15 at 07:35
  • Is there a way that I can make this picker "non-circular" that is it should stop at the max value. – Srujan Barai Aug 27 '15 at 21:03
  • 1
    @SrujanBarai, yes you can by using `setWrapSelectorWheel()` on the number picker. I've updated the code to include this option. Have a look, you will want to set it to false. – Rob Meeuwisse Aug 30 '15 at 16:03
  • Add picker.clearFocus(); at onDialogClosed before picker.getValue(). So the text change of a manual input will be also persisted if it has focus at this time. – Lunero Oct 05 '15 at 17:58
  • @Lunero, thanks. I've done as you suggested. If you input a number manually it is now still persisted when you tap on the ok button. If you tap cancel or outside the dialog it is ignored. If you input a number that is out of range, it will be clipped to the allowed range. – Rob Meeuwisse Oct 05 '15 at 21:08
  • 1
    How can I change this to show the currently selected value in the preferences list? – Jonno_FTW Dec 13 '16 at 05:15
  • @Jonno_FTW Have a look at this [question](http://stackoverflow.com/questions/4823442/android-list-preferences-have-summary-as-selected-value). – Rob Meeuwisse Dec 13 '16 at 09:36
  • @Jonno_FTW you can use setSummary(String.valueOf(getValue())) e.g. in onSetInitialValue(..) and onDialogClosed(..) – v0rin Oct 29 '21 at 10:49
6

Here's how I did it in androidx and kotlin:

NumberPickerPreference.kt

import android.content.Context
import android.util.AttributeSet
import androidx.preference.DialogPreference

class NumberPickerPreference(context: Context?, attrs: AttributeSet?) :
    DialogPreference(context, attrs) {

    override fun getSummary(): CharSequence {
        return getPersistedInt(INITIAL_VALUE).toString()
    }

    fun getPersistedInt() = super.getPersistedInt(INITIAL_VALUE)

    fun doPersistInt(value: Int) {
        super.persistInt(value)
        notifyChanged()
    }

    companion object {
        // allowed range
        const val INITIAL_VALUE = 50
        const val MIN_VALUE = 12
        const val MAX_VALUE = 100
    }
}

NumberPickerPreferenceDialog.kt

import android.content.Context
import android.os.Bundle
import android.view.View
import android.widget.NumberPicker
import androidx.preference.PreferenceDialogFragmentCompat


class NumberPickerPreferenceDialog : PreferenceDialogFragmentCompat() {
    lateinit var numberPicker: NumberPicker

    override fun onCreateDialogView(context: Context?): View {
        numberPicker = NumberPicker(context)
        numberPicker.minValue = NumberPickerPreference.MIN_VALUE
        numberPicker.maxValue = NumberPickerPreference.MAX_VALUE

        return numberPicker
    }

    override fun onBindDialogView(view: View?) {
        super.onBindDialogView(view)
        numberPicker.value = (preference as NumberPickerPreference).getPersistedInt()
    }

    override fun onDialogClosed(positiveResult: Boolean) {
        if (positiveResult) {
            numberPicker.clearFocus()
            val newValue: Int = numberPicker.value
            if (preference.callChangeListener(newValue)) {
                (preference as NumberPickerPreference).doPersistInt(newValue)
                preference.summary
            }
        }
    }

    companion object {
        fun newInstance(key: String): NumberPickerPreferenceDialog {
            val fragment = NumberPickerPreferenceDialog()
            val bundle = Bundle(1)
            bundle.putString(ARG_KEY, key)
            fragment.arguments = bundle

            return fragment
        }
    }
}

SettingsFragment.kt

import android.os.Bundle
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat

class SettingsFragment : PreferenceFragmentCompat() {
    private val DIALOG_FRAGMENT_TAG = "NumberPickerDialog"

    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
        setPreferencesFromResource(R.xml.settings, rootKey)
    }

    override fun onDisplayPreferenceDialog(preference: Preference?) {
        if (parentFragmentManager.findFragmentByTag(DIALOG_FRAGMENT_TAG) != null) {
            return
        }
        if (preference is NumberPickerPreference) {
            val dialog = NumberPickerPreferenceDialog.newInstance(preference.key)
            dialog.setTargetFragment(this, 0)
            dialog.show(parentFragmentManager, DIALOG_FRAGMENT_TAG)
        } else
            super.onDisplayPreferenceDialog(preference)
    }
}

settings.xml

<your.package.NumberPickerPreference
        app:key="your_pref_key"
        app:title="@string/your_pref_title" />

Hope this helps.

  • 1
    hey man this code is running well on my android emulator but not on my phone my android emulator is using android 7 and my phone android 11 and i am getting java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer on this line return getPersistedInt(INITIAL_VALUE).toString()...any fix for this?seems like i have to change the value of that line to integer? – HiDd3N Jan 26 '21 at 16:35
2

Implementing DialogPreference is a solution:

smarroufin
  • 135
  • 1
  • 9
0

A simple solution based on ListPreference, add values/entries on the fly:

root_preferences.xml:

<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
    ...

    <ListPreference
        app:key="myNumber"
        app:title="my title"
        app:useSimpleSummaryProvider="true"/>

SettingsActivity.java:

public  class SettingsFragment extends PreferenceFragmentCompat {

    @Override
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        setPreferencesFromResource(R.xml.root_preferences, rootKey);

        ListPreference e = findPreference("myNumber");
        if (e != null) {
            String[] vals = new String[100];
            for (int i = 0; i < vals.length; i++)
                vals[i] = String.valueOf(i + 1);
            e.setEntries(vals);
            e.setEntryValues(vals);
            e.setDefaultValue("1");
        }
    }
...
}
steve
  • 615
  • 6
  • 14