{"id":71,"date":"2011-08-13T21:52:46","date_gmt":"2011-08-14T01:52:46","guid":{"rendered":"http:\/\/robobunny.com\/wp\/?p=71"},"modified":"2011-08-13T21:52:46","modified_gmt":"2011-08-14T01:52:46","slug":"android-seekbar-preference","status":"publish","type":"post","link":"https:\/\/robobunny.com\/wp\/?p=71","title":{"rendered":"Android SeekBar preference"},"content":{"rendered":"<p><span style=\"font-size:x-large\"><span style=\"color:red\">Attention:<\/span> This version of the SeekBar is obsolete, check out the <a href=\"http:\/\/robobunny.com\/wp\/2013\/08\/24\/android-seekbar-preference-v2\/\">new version instead<\/a><\/span><\/p>\n<p>While working on my <a href=\"https:\/\/market.android.com\/details?id=com.robobunny.dislexicon\">Android implementation<\/a> of the <a href=\"http:\/\/robobunny.com\/cgi-bin\/dislexicon\">Dislexicon<\/a>, I realized I would need a SeekBar preference to adjust the text-to-speech speed. I found a couple of SeekBar prefs online, but none of them fit my needs. Specifically, I wanted it to:<\/p>\n<ol>\n<li>Appear on the main preference screen, instead of a separate window accessed via a button<\/li>\n<li>Fill the entire width of the screen<\/li>\n<li>Allow a minimum value other than zero<\/li>\n<\/ol>\n<p>Here&#8217;s what I ended up with, as it appears in Dislexicon:<\/p>\n<div id=\"attachment_97\" style=\"width: 490px\" class=\"wp-caption alignnone\"><a href=\"https:\/\/robobunny.com\/wp\/wp-content\/uploads\/2011\/08\/seekbar_pref.png\"><img aria-describedby=\"caption-attachment-97\" loading=\"lazy\" class=\"size-full wp-image-97\" title=\"SeekBar Preference\" src=\"https:\/\/robobunny.com\/wp\/wp-content\/uploads\/2011\/08\/seekbar_pref.png\" alt=\"\" width=\"480\" height=\"140\" srcset=\"https:\/\/robobunny.com\/wp\/wp-content\/uploads\/2011\/08\/seekbar_pref.png 480w, https:\/\/robobunny.com\/wp\/wp-content\/uploads\/2011\/08\/seekbar_pref-300x88.png 300w\" sizes=\"(max-width: 480px) 100vw, 480px\" \/><\/a><p id=\"caption-attachment-97\" class=\"wp-caption-text\">SeekBar Preference<\/p><\/div>\n<p><!--more--><br \/>\nYou&#8217;ll need two files, plus a preferences section. They are all inline below, but you can download them here:<\/p>\n<table class=\"highlight-box\" width=\"100%\" cellspacing=\"2\" cellpadding=\"2\">\n<thead>\n<tr>\n<th>File<\/th>\n<th><\/th>\n<th>Description<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><a href=\"\/blog_files\/android_seekbar_preference\/preferences.xml\">preferences.xml<\/a><\/td>\n<td style=\"width: 10px;\"><\/td>\n<td>Example of adding a config to your preferences.<\/td>\n<\/tr>\n<tr>\n<td><a href=\"\/blog_files\/android_seekbar_preference\/SeekBarPreference.java\">SeekBarPreference.java<\/a><\/td>\n<td style=\"width: 10px;\"><\/td>\n<td>The java class.<\/td>\n<\/tr>\n<tr>\n<td><a href=\"\/blog_files\/android_seekbar_preference\/seek_bar_preference.xml\">seek_bar_preference.xml<\/a><\/td>\n<td style=\"width: 10px;\"><\/td>\n<td>The XML layout of the preference. This should go in your res\/layout directory.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>&nbsp;<\/p>\n<p>Here&#8217;s an example of configuring it in your preferences.xml file. Most of the parameters work exactly the same as a standard preference, but there are two new parameters:<\/p>\n<ol>\n<li>min: Minimum value to use for the slider. The default is zero<\/li>\n<li>units: The characters to put to the right of the value, to indicate the units (eg. %, deg, .oz)<\/li>\n<\/ol>\n<p><a href=\"\/blog_files\/android_seekbar_preference\/preferences.xml\">preferences.xml<\/a><br \/>\n[cc lang=&#8221;xml&#8221;]<br \/>\n<?xml version=\"1.0\" encoding=\"UTF-8\"?><\/p>\n<p><PreferenceScreen\n        xmlns:android=\"http:\/\/schemas.android.com\/apk\/res\/android\"\n        xmlns:robobunny=\"http:\/\/robobunny.com\"\n        android:key=\"preference_screen\"><\/p>\n<p>\t<PreferenceCategory android:title=\"Speech\"><br \/>\n\t\t<com.robobunny.SeekBarPreference\n\t\t\tandroid:key=\"speechRate\"\n        \t\tandroid:title=\"Speech speed\"\n        \t\tandroid:summary=\"Adjust reading speed\"\n        \t\tandroid:defaultValue=\"80\"\n        \t\tandroid:max=\"200\"\n        \t\trobobunny:min=\"1\"\n\t\t\trobobunny:unitsLeft=\"\"\n        \t\trobobunny:unitsRight=\"%\"\n        \t\/><br \/>\n\t<\/PreferenceCategory><\/p>\n<p><\/PreferenceScreen><br \/>\n[\/cc]<\/p>\n<p>The java class itself.<br \/>\n<a href=\"\/blog_files\/android_seekbar_preference\/SeekBarPreference.java\">SeekBarPreference.java<\/a><br \/>\n[cc lang=&#8221;java&#8221;]<br \/>\npackage com.robobunny;<\/p>\n<p>import android.content.Context;<br \/>\nimport android.content.res.TypedArray;<br \/>\nimport android.preference.Preference;<br \/>\nimport android.util.AttributeSet;<br \/>\nimport android.util.Log;<br \/>\nimport android.view.LayoutInflater;<br \/>\nimport android.view.View;<br \/>\nimport android.view.ViewGroup;<br \/>\nimport android.view.ViewParent;<br \/>\nimport android.widget.RelativeLayout;<br \/>\nimport android.widget.SeekBar;<br \/>\nimport android.widget.SeekBar.OnSeekBarChangeListener;<br \/>\nimport android.widget.TextView;<\/p>\n<p>public class SeekBarPreference extends Preference implements OnSeekBarChangeListener {<\/p>\n<p>\tprivate final String TAG = getClass().getName();<\/p>\n<p>\tprivate static final String ANDROIDNS=&#8221;http:\/\/schemas.android.com\/apk\/res\/android&#8221;;<br \/>\n\tprivate static final String ROBOBUNNYNS=&#8221;http:\/\/robobunny.com&#8221;;<br \/>\n\tprivate static final int DEFAULT_VALUE = 50;<\/p>\n<p>\tprivate int mMaxValue      = 100;<br \/>\n\tprivate int mMinValue      = 0;<br \/>\n\tprivate int mInterval      = 1;<br \/>\n\tprivate int mCurrentValue;<br \/>\n\tprivate String mUnitsLeft  = &#8220;&#8221;;<br \/>\n\tprivate String mUnitsRight = &#8220;&#8221;;<br \/>\n\tprivate SeekBar mSeekBar;<\/p>\n<p>\tprivate TextView mStatusText;<\/p>\n<p>\tpublic SeekBarPreference(Context context, AttributeSet attrs) {<br \/>\n\t\tsuper(context, attrs);<br \/>\n\t\tinitPreference(context, attrs);<br \/>\n\t}<\/p>\n<p>\tpublic SeekBarPreference(Context context, AttributeSet attrs, int defStyle) {<br \/>\n\t\tsuper(context, attrs, defStyle);<br \/>\n\t\tinitPreference(context, attrs);<br \/>\n\t}<\/p>\n<p>\tprivate void initPreference(Context context, AttributeSet attrs) {<br \/>\n\t\tsetValuesFromXml(attrs);<br \/>\n\t\tmSeekBar = new SeekBar(context, attrs);<br \/>\n\t\tmSeekBar.setMax(mMaxValue &#8211; mMinValue);<br \/>\n\t\tmSeekBar.setOnSeekBarChangeListener(this);<br \/>\n\t}<\/p>\n<p>\tprivate void setValuesFromXml(AttributeSet attrs) {<br \/>\n\t\tmMaxValue = attrs.getAttributeIntValue(ANDROIDNS, &#8220;max&#8221;, 100);<br \/>\n\t\tmMinValue = attrs.getAttributeIntValue(ROBOBUNNYNS, &#8220;min&#8221;, 0);<\/p>\n<p>\t\tmUnitsLeft = getAttributeStringValue(attrs, ROBOBUNNYNS, &#8220;unitsLeft&#8221;, &#8220;&#8221;);<br \/>\n\t\tString units = getAttributeStringValue(attrs, ROBOBUNNYNS, &#8220;units&#8221;, &#8220;&#8221;);<br \/>\n\t\tmUnitsRight = getAttributeStringValue(attrs, ROBOBUNNYNS, &#8220;unitsRight&#8221;, units);<\/p>\n<p>\t\ttry {<br \/>\n\t\t\tString newInterval = attrs.getAttributeValue(ROBOBUNNYNS, &#8220;interval&#8221;);<br \/>\n\t\t\tif(newInterval != null)<br \/>\n\t\t\t\tmInterval = Integer.parseInt(newInterval);<br \/>\n\t\t}<br \/>\n\t\tcatch(Exception e) {<br \/>\n\t\t\tLog.e(TAG, &#8220;Invalid interval value&#8221;, e);<br \/>\n\t\t}<\/p>\n<p>\t}<\/p>\n<p>\tprivate String getAttributeStringValue(AttributeSet attrs, String namespace, String name, String defaultValue) {<br \/>\n\t\tString value = attrs.getAttributeValue(namespace, name);<br \/>\n\t\tif(value == null)<br \/>\n\t\t\tvalue = defaultValue;<\/p>\n<p>\t\treturn value;<br \/>\n\t}<\/p>\n<p>\t@Override<br \/>\n\tprotected View onCreateView(ViewGroup parent){<\/p>\n<p>\t\tRelativeLayout layout =  null;<\/p>\n<p>\t\ttry {<br \/>\n\t\t\tLayoutInflater mInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);<\/p>\n<p>\t\t\tlayout = (RelativeLayout)mInflater.inflate(R.layout.seek_bar_preference, parent, false);<br \/>\n\t\t}<br \/>\n\t\tcatch(Exception e)<br \/>\n\t\t{<br \/>\n\t\t\tLog.e(TAG, &#8220;Error creating seek bar preference&#8221;, e);<br \/>\n\t\t}<\/p>\n<p>\t\treturn layout;<\/p>\n<p>\t}<\/p>\n<p>\t@Override<br \/>\n\tpublic void onBindView(View view) {<br \/>\n\t\tsuper.onBindView(view);<\/p>\n<p>\t\ttry<br \/>\n\t\t{<br \/>\n\t\t\t\/\/ move our seekbar to the new view we&#8217;ve been given<br \/>\n\t        ViewParent oldContainer = mSeekBar.getParent();<br \/>\n\t        ViewGroup newContainer = (ViewGroup) view.findViewById(R.id.seekBarPrefBarContainer);<\/p>\n<p>\t        if (oldContainer != newContainer) {<br \/>\n\t        \t\/\/ remove the seekbar from the old view<br \/>\n\t            if (oldContainer != null) {<br \/>\n\t                ((ViewGroup) oldContainer).removeView(mSeekBar);<br \/>\n\t            }<br \/>\n\t            \/\/ remove the existing seekbar (there may not be one) and add ours<br \/>\n\t            newContainer.removeAllViews();<br \/>\n\t            newContainer.addView(mSeekBar, ViewGroup.LayoutParams.FILL_PARENT,<br \/>\n\t                    ViewGroup.LayoutParams.WRAP_CONTENT);<br \/>\n\t        }<br \/>\n\t\t}<br \/>\n\t\tcatch(Exception ex) {<br \/>\n\t\t\tLog.e(TAG, &#8220;Error binding view: &#8221; + ex.toString());<br \/>\n\t\t}<\/p>\n<p>\t\tupdateView(view);<br \/>\n\t}<\/p>\n<p>\t\/**<br \/>\n\t * Update a SeekBarPreference view with our current state<br \/>\n\t * @param view<br \/>\n\t *\/<br \/>\n\tprotected void updateView(View view) {<\/p>\n<p>\t\ttry {<br \/>\n\t\t\tRelativeLayout layout = (RelativeLayout)view;<\/p>\n<p>\t\t\tmStatusText = (TextView)layout.findViewById(R.id.seekBarPrefValue);<br \/>\n\t\t\tmStatusText.setText(String.valueOf(mCurrentValue));<br \/>\n\t\t\tmStatusText.setMinimumWidth(30);<\/p>\n<p>\t\t\tmSeekBar.setProgress(mCurrentValue &#8211; mMinValue);<\/p>\n<p>\t\t\tTextView unitsRight = (TextView)layout.findViewById(R.id.seekBarPrefUnitsRight);<br \/>\n\t\t\tunitsRight.setText(mUnitsRight);<\/p>\n<p>\t\t\tTextView unitsLeft = (TextView)layout.findViewById(R.id.seekBarPrefUnitsLeft);<br \/>\n\t\t\tunitsLeft.setText(mUnitsLeft);<\/p>\n<p>\t\t}<br \/>\n\t\tcatch(Exception e) {<br \/>\n\t\t\tLog.e(TAG, &#8220;Error updating seek bar preference&#8221;, e);<br \/>\n\t\t}<\/p>\n<p>\t}<\/p>\n<p>\t@Override<br \/>\n\tpublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {<br \/>\n\t\tint newValue = progress + mMinValue;<\/p>\n<p>\t\tif(newValue > mMaxValue)<br \/>\n\t\t\tnewValue = mMaxValue;<br \/>\n\t\telse if(newValue < mMinValue)\n\t\t\tnewValue = mMinValue;\n\t\telse if(mInterval != 1 &#038;&#038; newValue % mInterval != 0)\n\t\t\tnewValue = Math.round(((float)newValue)\/mInterval)*mInterval;  \n\t\t\n\t\t\/\/ change rejected, revert to the previous value\n\t\tif(!callChangeListener(newValue)){\n\t\t\tseekBar.setProgress(mCurrentValue - mMinValue); \n\t\t\treturn; \n\t\t}\n\n\t\t\/\/ change accepted, store it\n\t\tmCurrentValue = newValue;\n\t\tmStatusText.setText(String.valueOf(newValue));\n\t\tpersistInt(newValue);\n\n\t}\n\n\t@Override\n\tpublic void onStartTrackingTouch(SeekBar seekBar) {}\n\n\t@Override\n\tpublic void onStopTrackingTouch(SeekBar seekBar) {\n\t\tnotifyChanged();\n\t}\n\n\n\t@Override \n\tprotected Object onGetDefaultValue(TypedArray ta, int index){\n\t\t\n\t\tint defaultValue = ta.getInt(index, DEFAULT_VALUE);\n\t\treturn defaultValue;\n\t\t\n\t}\n\n\t@Override\n\tprotected void onSetInitialValue(boolean restoreValue, Object defaultValue) {\n\n\t\tif(restoreValue) {\n\t\t\tmCurrentValue = getPersistedInt(mCurrentValue);\n\t\t}\n\t\telse {\n\t\t\tint temp = 0;\n\t\t\ttry {\n\t\t\t\ttemp = (Integer)defaultValue;\n\t\t\t}\n\t\t\tcatch(Exception ex) {\n\t\t\t\tLog.e(TAG, \"Invalid default value: \" + defaultValue.toString());\n\t\t\t}\n\t\t\t\n\t\t\tpersistInt(temp);\n\t\t\tmCurrentValue = temp;\n\t\t}\n\t\t\n\t}\n\t\n}\n[\/cc]\n\nThe preference layout file. You can use this to tweek what the preference looks like.\n<a href=\"\/blog_files\/android_seekbar_preference\/seek_bar_preference.xml\">seek_bar_preference.xml<\/a><br \/>\n[cc lang=&#8221;xml&#8221;]<br \/>\n<?xml version=\"1.0\" encoding=\"UTF-8\"?><\/p>\n<p><RelativeLayout xmlns:android=\"http:\/\/schemas.android.com\/apk\/res\/android\"\n\tandroid:id=\"@android:id\/widget_frame\"\n\tandroid:layout_width=\"fill_parent\"\n\tandroid:layout_height=\"wrap_content\"\n\tandroid:paddingLeft=\"15dp\"\n\tandroid:paddingTop=\"5dp\"\n\tandroid:paddingRight=\"10dp\"\n\tandroid:paddingBottom=\"5dp\"\n\t><\/p>\n<p>\t<TextView android:id=\"@android:id\/title\"\n\t        android:layout_alignParentLeft=\"true\"\n\t        android:layout_alignParentTop=\"true\"\n\t        android:layout_width=\"fill_parent\"\n\t        android:layout_height=\"wrap_content\"\n\t        android:textSize=\"22dp\"\n\t        android:typeface=\"sans\"\n\t        android:textStyle=\"normal\"\n\t        android:textColor=\"#ffffff\"\n\t><\/TextView><\/p>\n<p>\t<TextView android:id=\"@android:id\/summary\"\n\t        android:layout_alignParentLeft=\"true\"\n\t        android:layout_width=\"fill_parent\"\n\t        android:layout_height=\"wrap_content\"\n\t        android:layout_below=\"@android:id\/title\"\n\t><\/TextView><\/p>\n<p>\t<TextView android:id=\"@+id\/seekBarPrefUnitsRight\"\n\t        android:layout_alignParentRight=\"true\"\n\t        android:layout_below=\"@android:id\/title\"\n\t        android:layout_width=\"wrap_content\"\n\t        android:layout_height=\"wrap_content\"\n\t><\/TextView><\/p>\n<p>\t<TextView android:id=\"@+id\/seekBarPrefValue\"\n\t        android:layout_width=\"wrap_content\"\n\t        android:layout_height=\"wrap_content\"\n\t        android:layout_toLeftOf=\"@id\/seekBarPrefUnitsRight\"\n\t        android:layout_below=\"@android:id\/title\"\n\t        android:gravity=\"right\"\n\t><\/TextView><\/p>\n<p>\t<TextView android:id=\"@+id\/seekBarPrefUnitsLeft\"\n\t        android:layout_below=\"@android:id\/title\"\n\t        android:layout_toLeftOf=\"@id\/seekBarPrefValue\"\n\t        android:layout_width=\"wrap_content\"\n\t        android:layout_height=\"wrap_content\"\n\t><\/TextView><\/p>\n<p>\t<LinearLayout android:id=\"@+id\/seekBarPrefBarContainer\"\n\t\t\tandroid:layout_alignParentLeft=\"true\"\n\t        android:layout_alignParentBottom=\"true\"\n\t        android:layout_width=\"fill_parent\"\n\t        android:layout_height=\"wrap_content\"\n\t        android:layout_below=\"@android:id\/summary\"><br \/>\n\t<\/LinearLayout><\/p>\n<p><\/RelativeLayout><br \/>\n[\/cc]<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Attention: This version of the SeekBar is obsolete, check out the new version instead While working on my Android implementation of the Dislexicon, I realized I would need a SeekBar preference to adjust the text-to-speech speed. I found a couple of SeekBar prefs online, but none of them fit my needs. Specifically, I wanted it [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[2],"tags":[13,26,28,29],"_links":{"self":[{"href":"https:\/\/robobunny.com\/wp\/index.php?rest_route=\/wp\/v2\/posts\/71"}],"collection":[{"href":"https:\/\/robobunny.com\/wp\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/robobunny.com\/wp\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/robobunny.com\/wp\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/robobunny.com\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=71"}],"version-history":[{"count":0,"href":"https:\/\/robobunny.com\/wp\/index.php?rest_route=\/wp\/v2\/posts\/71\/revisions"}],"wp:attachment":[{"href":"https:\/\/robobunny.com\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=71"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/robobunny.com\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=71"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/robobunny.com\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=71"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}