Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Android

SeekBar Preference

4.80/5 (10 votes)
1 Mar 2011CPOL3 min read 91K   3.1K  
How to build preference widget with SeekBar

Introduction

In this article, I’ll try to show how to create your own preference widget with SeekBar inside.

seekbarpreferencedialog.png

Background

Android provides a number of handy widgets like CheckBoxPreference, EditTextPreference, ListPreference to build nice preference screens. But if existing widgets somehow don’t fit your requirements, you can easily create your own based on existing.

Often some integer preferences have meaningful limitations like volume or brightness level. In this case, it makes sense to build your own widget to manage such preferences and then reuse this widget across applications.

Preparation

DialogPreference is the best super class for such widgets. It provides a preference which shows two lines of text in preference list (title and summary) and shows dialog with Ok / Cancel buttons on click.

seekbarpreference.png

Let's call it SeekBarPreference. Parameters for this widget can be minimum value, maximum value and current default value. Real current value will be stored in associated shared preferences by given key.

In this case, file /res/xml/preferences.xml can look like this:

XML
<PreferenceScreen 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:example="http://schemas.android.com/apk/res/com.mnm.seekbarpreference">
    
    <com.mnm.seekbarpreference.SeekBarPreference
        android:key="seekBarPreference"
	android:title="@string/dialog_title"
	android:dialogTitle="@string/dialog_title"
	android:summary="@string/summary"
	android:persistent="true"
	android:defaultValue="20"
	example:minValue="10"
	example:maxValue="50" />
    
</PreferenceScreen>

Existing tag can be used to setup default current value. But minimum and maximum values need their own tags. For this /res/values/attrs.xml file should be extended with the next declaration:

XML
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="com.mnm.seekbarpreference.SeekBarPreference">
        <attr name="minValue" format="integer" />
        <attr name="maxValue" format="integer" />
    </declare-styleable>
</resources>

Name attribute of <declare-styleable> tag should contain qualified widget class name.

The same name but in more full format (http://schemas.android.com/apk/res/qualified_class_name) should be defined in preferences.xml as additional namespace (see above).

The last stage of work with XML is the creation of layout for dialog launching on widget click. It is a really common code and could be omitted without any impact. Just mention that it contains minimum, maximum and current values’ TexViews and SeekBar.

Now, we can continue with SeekBarPreference class implementation.

Implementation

At first, all mentioned attributes shall be read in widget constructor:

Java
mMinValue = attrs.getAttributeIntValue
	(PREFERENCE_NS, ATTR_MIN_VALUE, DEFAULT_MIN_VALUE);
mMaxValue = attrs.getAttributeIntValue
	(PREFERENCE_NS, ATTR_MAX_VALUE, DEFAULT_MAX_VALUE);
mDefaultValue = attrs.getAttributeIntValue
		(ANDROID_NS, ATTR_DEFAULT_VALUE, DEFAULT_CURRENT_VALUE);

where constants are names of namespaces, attributes and default values for attributes’ values (in case they are omitted in preference declaration):

Java
private static final String PREFERENCE_NS = 
"http://schemas.android.com/apk/res/com.mnm.seekbarpreference";
private static final String ANDROID_NS = "http://schemas.android.com/apk/res/android";

private static final String ATTR_DEFAULT_VALUE = "defaultValue";
private static final String ATTR_MIN_VALUE = "minValue";
private static final String ATTR_MAX_VALUE = "maxValue";

For dialog setup, onCreateDialogView method should be overridden:

Java
@Override
protected View onCreateDialogView() {

    // Get current value from settings
    mCurrentValue = getPersistedInt(mDefaultValue);

    // Inflate layout
    LayoutInflater inflater = 
	(LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    View view = inflater.inflate(R.layout.dialog_slider, null);

    // Put minimum and maximum
    ((TextView) view.findViewById(R.id.min_value)).setText(Integer.toString(mMinValue));
    ((TextView) view.findViewById(R.id.max_value)).setText(Integer.toString(mMaxValue));

    // Setup SeekBar
    mSeekBar = (SeekBar) view.findViewById(R.id.seek_bar);
    mSeekBar.setMax(mMaxValue - mMinValue);
    mSeekBar.setProgress(mCurrentValue - mMinValue);
    mSeekBar.setOnSeekBarChangeListener(this);

    // Put current value
    mValueText = (TextView) view.findViewById(R.id.current_value);
    mValueText.setText(Integer.toString(mCurrentValue));

    return view;
}

Current value is gotten by key stated in preferences.xml for a particular widget instance. Also during SeekBar setup should be taken into account that minimum value for it is zero. This is the reason why subtraction is used when minimum value of preference differs from zero. By the way, this code is correct for non-negative numbers only and when maximum value is really greater than minimum one.

After the previous step, the application can be launched and user can drag thumb but text on the current value label wouldn’t change because position change handler should be introduced.

For this purpose, SeekBarPreference should implement OnSeekBarChangeListener interface. In the previous code, exactly this interface was passed via “mSeekBar.setOnSeekBarChangeListener(this);” call to SeekBar. Only one method should be implemented from three possible:

Java
public void onProgressChanged(SeekBar seek, int value, boolean fromTouch) {
    mCurrentValue = value + mMinValue;
    mValueText.setText(Integer.toString(mCurrentValue));
}

And again, addition should be used because SeekBar minimum value is zero.

Next step is to persist changed value. On dialog close, it calls onDialogClosed method that should be overridden:

Java
@Override
protected void onDialogClosed(boolean positiveResult) {
    super.onDialogClosed(positiveResult);

    if (!positiveResult) {
        return;
    }
    if (shouldPersist()) {
        persistInt(mCurrentValue);
    }

    notifyChanged();
}

On positive result value is persisted and shouldPersist() check analyzes if it is necessary according to flag android:persistent from preferences.xml.

Last line is needed for a small trick. The point is that by default, summary line of the widget is static and if it should reflect current value, the next code should be added:

Java
@Override
public CharSequence getSummary() {
    String summary = super.getSummary().toString();
    int value = getPersistedInt(mDefaultValue);
    return String.format(summary, value);
}

Here on summary request, the original string is treated like a template to put current value into it. It works perfectly on preferences screen open. But to get the same behavior after preference change via dialog, notifyChanged() should be called.

Conclusion

Implemented widget is suitable for a wide range of preferences and it elegantly extends existing set of preference widgets. Dynamic summary line approach can be used across different preference widgets.

Links

History

  • 1st March, 2011: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)