I am a newbie at android studio and am trying to make a simple todolist app. I have a daily alarm set up using Alarm Manager but I would like the user to be able to change the timing that this daily alarm goes off in the settings page. Therefore, I need a timepicker in preferencescreen and I have learnt that you can do this using custom DialogPreferences.
I have tried to follow this tutorial http://www.infiniteimprob.com/blog/custom-preferences/ but it shows this exceptioncom.example.todolist.TimePickerPreference cannot be cast to androidx.preference.Preference I have a feeling that this is due to compatibility issues because I used the androidx library for my other imports. I think that this will be solved if I were to import the androidx.preference.DialogPreference instead of android.preference.DialogPreference; Can someone tell me how I can go about this change? I have read this answer Difference between DialogPreference before and after AndroidX but I still do not really know how to execute it.
This is my timepickerpreference class as followed on the website:
import android.content.res.TypedArray;
import android.os.Build;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;
import android.widget.TimePicker;
public class TimePickerPreference extends DialogPreference {
private static final int DEFAULT_TIME = 0;
private TimePicker mTimePicker;
private int currentTime;
public TimePickerPreference(Context context, AttributeSet attrs){
super(context, attrs);
setDialogLayoutResource(R.layout.timepickerpreferencelayout);
setPositiveButtonText("ok");
setNegativeButtonText("cancel");
}
private static int getTime(int hour, int minute){
return hour*60*60*1000 + minute*60*1000;
}
private static int getHour(int value){
return (int)(value / (60*60*1000));
}
private static int getMinute(int value){
return (int)(value % (60*60*1000));
}
#Override
protected View onCreateDialogView() {
View view = super.onCreateDialogView();
mTimePicker = (TimePicker) view.findViewById(R.id.timePicker);
mTimePicker.setIs24HourView(true);
if (Build.VERSION.SDK_INT >= 23)
{
mTimePicker.setHour(getHour(currentTime));
mTimePicker.setMinute(getMinute(currentTime));
}
else
{
mTimePicker.setCurrentHour(getHour(currentTime));
mTimePicker.setCurrentMinute(getMinute(currentTime));
}
mTimePicker.setOnTimeChangedListener(new TimePicker.OnTimeChangedListener()
{
#Override
public void onTimeChanged(TimePicker timePicker, int hour, int minute)
{
TimePickerPreference.this.currentTime = getTime(hour, minute);
}
});
TextView messageTextView = (TextView) view.findViewById(R.id.messageTextView);
if (getDialogMessage() == null || getDialogMessage().toString().trim().length() == 0)
messageTextView.setVisibility(View.GONE);
messageTextView.setText(getDialogMessage());
return view;
}
#Override
protected Object onGetDefaultValue(TypedArray a, int index)
{
return a.getInt(index, DEFAULT_TIME);
}
#Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue)
{
if(restorePersistedValue)
{
currentTime = this.getPersistedInt((int) (defaultValue == null ? DEFAULT_TIME : defaultValue));
}
else
{
currentTime = (int) defaultValue;
if (shouldPersist())
persistInt(currentTime);
}
}
#Override
protected void onDialogClosed(boolean positiveResult)
{
if (positiveResult)
persistInt(currentTime);
}
}
Long story short, I have a class that handles my app shared preferences.
I call it from various other classes without issues, but when I try to call it from my service (from the same APK) I get a null exception. I am guessing that it's getting called from the wrong context or something like that. Here is the relevant code.
MainActivity.java
package com.deskwizard.audiomanager;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import com.deskwizard.audiomanager.DataSaveRestore;
public class MainActivity extends FragmentActivity {
public static Context contextOfApplication;
final FragmentManager fm = getFragmentManager();
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
contextOfApplication = getApplicationContext();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment_settings, new FadeBalanceFragment());
ft.commit();
// TODO: Load previously saved settings for all values
DataSaveRestore.restore_all();
// TODO: init I2C
}
public static Context getContextOfApplication() {
return contextOfApplication;
}
}
DataSaveRestore.java (defaultpreferences class)
package com.deskwizard.audiomanager;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
public class DataSaveRestore extends Application {
// Data variables
public static int Bass_level, Bass_CFreq, Bass_Qfact, Sub_level,
Sub_Lowpass, Treble_level, Treble_CFreq, Mid_level, Mid_CFreq,
Mid_Qfact, Fade, Balance, Loudness_level, Loudness_freq,
Loudness_boost;
static boolean Bass_DCMode, Loudness_state;
static Context applicationContext = MainActivity.getContextOfApplication();
public static void restore_all() {
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(applicationContext);
if (prefs.getInt("data_saved", 0) == 0) {
set_defaults();
load_defaults();
} else {
load_defaults();
}
//TODO: send settings to TDA7418
DS3903.set_lowpass(DataSaveRestore.Sub_Lowpass);
};
Service code snippet:
public class AudioManagerService extends Service {
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
// TODO do something useful
Log.d("com.deskwizard.audiomanager", "starting service...");
DataSaveRestore.restore_all(); // restore settings to TDA7418/DS3903
start();
return Service.START_STICKY;
}
The Null Exception error refers to this line, only when called from the service, it works properly from the main application and other classes:
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(applicationContext);
Let me know if any further code can help narrow it down.
Thanks,
DW
Because, In your service when you call, DataSaveRestore.restore_all(); It make reference on, (As there is no MainActivity context available from Servce)
static Context applicationContext = MainActivity.getContextOfApplication();
on this line, applicationContext will be null as it can't find MainActivity initialization
Simply, Just change your restore_all() method from Application class.
First remove static and and use getApplicationContext() of Android application class method to get application context as in Service,
public void restore_all() {
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(getApplicationContext());
if (prefs.getInt("data_saved", 0) == 0) {
set_defaults();
load_defaults();
} else {
load_defaults();
}
//TODO: send settings to TDA7418
DS3903.set_lowpass(DataSaveRestore.Sub_Lowpass);
};
Now call, restore_all(); by initializing object of Application class not a static way.
Like,
DataSaveRestore dataSaveRestore = (DataSaveRestore) getApplicationContext();
dataSaveRestore.restore_all();
I'm using a custom dialog preference to generate a seekbar in my preferences menu.
After researching seekbar implementation, to obtain the float value I need from the seekbar I have written the following code:
public void onProgressChanged(SeekBar seek, int newValue,
boolean fromTouch) {
// Round the value to the closest integer value.
if (stepSize >= 1) {
value = Math.round(newValue/stepSize)*stepSize;
}
else {
value = newValue;
}
// Set the valueText text.
float sValue = newValue*10;
sValue = sValue/100;
valueText.setText(Float.toString(sValue));
Which produces the float that I want. However, I want to be able to use this float in my main activity. I have attempted to store it using SharedPreferences using:
userPrefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
Editor editor = userPrefs.edit()
editor.putFloat("mpm", sValue);
editor.commit();
Which is how I've learned to use SharedPreferences in a class extending Activity.
However as this seekbar extends Dialogue Preference I cannot use
getBaseContext()
As I get the error that the method getBaseContext is undefined for this type.
I have tried changing getBaseContext() to getContext() but this has been unsuccessful although that may be because I am unfamiliar with this implementation.
How can I save this float from the dialogue preference and use the value in a different class?
The code I am using to retrieve SharedPreferences:
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.logbook);
initialise();
userPrefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
list = userPrefs.getString("list", "10");
userswallstring = userPrefs.getString("height", "10.0");
try {
usersWall = Float.valueOf(userswallstring.trim());
} catch (Exception e) {
// TODO: handle exception
}
mpm = userPrefs.getFloat("mpm", 2);
Mpm.class:
package com.gbclimber.ep;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.res.TypedArray;
import android.preference.DialogPreference;
import android.preference.PreferenceManager;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.SeekBar;
import android.widget.TextView;
public class Mpm extends
DialogPreference implements SeekBar.OnSeekBarChangeListener {
// Layout widgets.
private SeekBar seekBar = null;
private TextView valueText = null;
// Custom xml attributes.
private int maximumValue = 0;
private int minimumValue = 0;
private int stepSize = 0;
private String units = null;
private int value = 0;
SharedPreferences userPrefs;
/**
* The SeekBarDialogPreference constructor.
* #param context of this preference.
* #param attrs custom xml attributes.
*/
public Mpm(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs,
R.styleable.Mpm);
maximumValue = typedArray.getInteger(
R.styleable.Mpm_maximumValue, 0);
minimumValue = typedArray.getInteger(
R.styleable.Mpm_minimumValue, 0);
stepSize = typedArray.getInteger(
R.styleable.Mpm_stepSize, 1);
units = typedArray.getString(
R.styleable.Mpm_units);
typedArray.recycle();
}
/**
* {#inheritDoc}
*/
protected View onCreateDialogView() {
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
View view = layoutInflater.inflate(
R.layout.mpmdp, null);
seekBar = (SeekBar)view.findViewById(R.id.seekbar);
valueText = (TextView)view.findViewById(R.id.valueText);
// Get the persistent value and correct it for the minimum value.
value = getPersistedInt(minimumValue) - minimumValue;
// You're never know...
if (value < 0) {
value = 0;
}
seekBar.setOnSeekBarChangeListener(this);
seekBar.setKeyProgressIncrement(stepSize);
seekBar.setMax(maximumValue - minimumValue);
seekBar.setProgress(value);
return view;
}
/**
* {#inheritDoc}
*/
public void onProgressChanged(SeekBar seek, int newValue,
boolean fromTouch) {
// Round the value to the closest integer value.
if (stepSize >= 1) {
value = Math.round(newValue/stepSize)*stepSize;
}
else {
value = newValue;
}
// Set the valueText text.
float sValue = newValue*10;
sValue = sValue/100;
userPrefs = PreferenceManager
.getDefaultSharedPreferences(getContext());
Editor editor = userPrefs.edit();
editor.putFloat("mpm", sValue);
editor.commit();
valueText.setText(Float.toString(sValue));
callChangeListener(value);
}
/**
* {#inheritDoc}
*/
public void onStartTrackingTouch(SeekBar seek) {
}
/**
* {#inheritDoc}
*/
public void onStopTrackingTouch(SeekBar seek) {
}
/**
* {#inheritDoc}
*/
public void onClick(DialogInterface dialog, int which) {
// if the positive button is clicked, we persist the value.
if (which == DialogInterface.BUTTON_POSITIVE) {
if (shouldPersist()) {
persistInt(value + minimumValue);
}
}
super.onClick(dialog, which);
}
#Override
protected void onDialogClosed(boolean positiveResult) {
// TODO Auto-generated method stub
super.onDialogClosed(positiveResult);
persistInt(value + minimumValue);
}
}
Just did a quick search but for your context try
this.getDialog().getContext()
I want to remove the seconds and AM/PM indication from the digital clock widget but I don't know how to do this.
It is hard-coded in the DigitalClock class, so you can't change it programatically. The only way to change it is to re-implement DigitalClock widget. Yo can get source code from here.
You need to copy this code in your project, rename it like "MyDigitalClock" and use this one as your widget. As you will see in the implementation, there are two variables you should focus on to get what you want:
private final static String m12 = "h:mm:ss aa";
private final static String m24 = "k:mm:ss";
ss represents seconds and aa represents am/pm.
You could use addTextChangedListener() and then take out the seconds and AM/PM in afterTextChanged(). This example just removes the seconds:
DigitalClock dc = (DigitalClock) findViewById(R.id.currentTime);
dc.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
#Override
public void afterTextChanged(Editable s) {
if (s.charAt(4) == ':') {
s.delete(4, 7);
} else if (s.charAt(5) == ':') {
s.delete(5, 8);
}
}
});
Let me little extend and refresh perfect answer from Erol.
Actually you need add one file to your project and update xml layout.
Sample could be taken from here: /frameworks/base/core/java/android/widget/DigitalClock.java
Adapted DigitalClock.java:
import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.SystemClock;
import android.provider.Settings;
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.widget.TextView;
import java.text.SimpleDateFormat;
import java.util.Calendar;
/**
* Like AnalogClock, but digital. Shows seconds.
*
* #deprecated It is recommended you use {#link TextClock} instead.
*/
#Deprecated
public class DigitalClock extends TextView {
// FIXME: implement separate views for hours/minutes/seconds, so
// proportional fonts don't shake rendering
Calendar mCalendar;
#SuppressWarnings("FieldCanBeLocal") // We must keep a reference to this observer
private FormatChangeObserver mFormatChangeObserver;
private Runnable mTicker;
private Handler mHandler;
private boolean mTickerStopped = false;
java.text.DateFormat mFormat;
public DigitalClock(Context context) {
super(context);
initClock();
}
public DigitalClock(Context context, AttributeSet attrs) {
super(context, attrs);
initClock();
}
private void initClock() {
if (mCalendar == null) {
mCalendar = Calendar.getInstance();
}
mFormatChangeObserver = new FormatChangeObserver();
getContext().getContentResolver().registerContentObserver(
Settings.System.CONTENT_URI, true, mFormatChangeObserver);
setFormat();
}
#Override
protected void onAttachedToWindow() {
mTickerStopped = false;
super.onAttachedToWindow();
mHandler = new Handler();
/**
* requests a tick on the next hard-second boundary
*/
mTicker = new Runnable() {
public void run() {
if (mTickerStopped) return;
mCalendar.setTimeInMillis(System.currentTimeMillis());
// setText(DateFormat.format(mFormat, mCalendar));
setText(mFormat.format(mCalendar.getTime()));
invalidate();
long now = SystemClock.uptimeMillis();
long next = now + (1000 - now % 1000);
mHandler.postAtTime(mTicker, next);
}
};
mTicker.run();
}
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mTickerStopped = true;
}
private void setFormat() {
// mFormat = DateFormat.getTimeFormatString(getContext()); //<== ORIGINAL
// mFormat = DateFormat.getTimeFormat(getContext()); //<== Fixed with PM
// mFormat = new SimpleDateFormat("hh:mm a");
mFormat = new SimpleDateFormat("h:mm"); //<== Your variant
}
private class FormatChangeObserver extends ContentObserver {
public FormatChangeObserver() {
super(new Handler());
}
#Override
public void onChange(boolean selfChange) {
setFormat();
}
}
#Override
public CharSequence getAccessibilityClassName() {
//noinspection deprecation
return DigitalClock.class.getName();
}
}
layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".HomeActivity" >
<HERE_YOUR_PACKAGE_NAME.DigitalClock
android:id="#+id/digitalClock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#android:color/background_light"
android:layout_centerInParent="true"
android:textSize="130dp"/>
</RelativeLayout>
Result:
I want my preference menu to have something to change the duration of a vibration.
There is no slider tag for prefs.xml, so what is the best way to do this?
I improved the link provided by Macarse, so that the value is saved only on ok button click, and so that you can use #string/... values in the XML file.
Here is the code:
/* The following code was written by Matthew Wiggins
* and is released under the APACHE 2.0 license
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Improvements :
* - save the value on positive button click, not on seekbar change
* - handle #string/... values in xml file
*/
package fr.atcm.carpooling.views.utils;
import android.app.AlertDialog;
import android.content.Context;
import android.os.Bundle;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
public class SeekBarPreference extends DialogPreference implements SeekBar.OnSeekBarChangeListener, OnClickListener
{
// ------------------------------------------------------------------------------------------
// Private attributes :
private static final String androidns="http://schemas.android.com/apk/res/android";
private SeekBar mSeekBar;
private TextView mSplashText,mValueText;
private Context mContext;
private String mDialogMessage, mSuffix;
private int mDefault, mMax, mValue = 0;
// ------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------
// Constructor :
public SeekBarPreference(Context context, AttributeSet attrs) {
super(context,attrs);
mContext = context;
// Get string value for dialogMessage :
int mDialogMessageId = attrs.getAttributeResourceValue(androidns, "dialogMessage", 0);
if(mDialogMessageId == 0) mDialogMessage = attrs.getAttributeValue(androidns, "dialogMessage");
else mDialogMessage = mContext.getString(mDialogMessageId);
// Get string value for suffix (text attribute in xml file) :
int mSuffixId = attrs.getAttributeResourceValue(androidns, "text", 0);
if(mSuffixId == 0) mSuffix = attrs.getAttributeValue(androidns, "text");
else mSuffix = mContext.getString(mSuffixId);
// Get default and max seekbar values :
mDefault = attrs.getAttributeIntValue(androidns, "defaultValue", 0);
mMax = attrs.getAttributeIntValue(androidns, "max", 100);
}
// ------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------
// DialogPreference methods :
#Override
protected View onCreateDialogView() {
LinearLayout.LayoutParams params;
LinearLayout layout = new LinearLayout(mContext);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(6,6,6,6);
mSplashText = new TextView(mContext);
mSplashText.setPadding(30, 10, 30, 10);
if (mDialogMessage != null)
mSplashText.setText(mDialogMessage);
layout.addView(mSplashText);
mValueText = new TextView(mContext);
mValueText.setGravity(Gravity.CENTER_HORIZONTAL);
mValueText.setTextSize(32);
params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
layout.addView(mValueText, params);
mSeekBar = new SeekBar(mContext);
mSeekBar.setOnSeekBarChangeListener(this);
layout.addView(mSeekBar, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
if (shouldPersist())
mValue = getPersistedInt(mDefault);
mSeekBar.setMax(mMax);
mSeekBar.setProgress(mValue);
return layout;
}
#Override
protected void onBindDialogView(View v) {
super.onBindDialogView(v);
mSeekBar.setMax(mMax);
mSeekBar.setProgress(mValue);
}
#Override
protected void onSetInitialValue(boolean restore, Object defaultValue)
{
super.onSetInitialValue(restore, defaultValue);
if (restore)
mValue = shouldPersist() ? getPersistedInt(mDefault) : 0;
else
mValue = (Integer)defaultValue;
}
// ------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------
// OnSeekBarChangeListener methods :
#Override
public void onProgressChanged(SeekBar seek, int value, boolean fromTouch)
{
String t = String.valueOf(value);
mValueText.setText(mSuffix == null ? t : t.concat(" " + mSuffix));
}
#Override
public void onStartTrackingTouch(SeekBar seek) {}
#Override
public void onStopTrackingTouch(SeekBar seek) {}
public void setMax(int max) { mMax = max; }
public int getMax() { return mMax; }
public void setProgress(int progress) {
mValue = progress;
if (mSeekBar != null)
mSeekBar.setProgress(progress);
}
public int getProgress() { return mValue; }
// ------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------
// Set the positive button listener and onClick action :
#Override
public void showDialog(Bundle state) {
super.showDialog(state);
Button positiveButton = ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_POSITIVE);
positiveButton.setOnClickListener(this);
}
#Override
public void onClick(View v) {
if (shouldPersist()) {
mValue = mSeekBar.getProgress();
persistInt(mSeekBar.getProgress());
callChangeListener(Integer.valueOf(mSeekBar.getProgress()));
}
((AlertDialog) getDialog()).dismiss();
}
// ------------------------------------------------------------------------------------------
}
EDIT :
Here is a screenshot :
EDIT : on arlomedia's demand, here are all the needed pieces of code (I just recreated a new projet, it is perfectly working. I corrected some bugs in SeekBarPreference class, so don't forget to re-copy/paste it) :
MainActivity :
package fr.at.testsliderpref;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
switch(item.getItemId()) {
case R.id.menu_settings : {
startActivity(new Intent(this, SettingsActivity.class));
break;
}
}
return true;
}
}
SettingsActivity :
package fr.at.testsliderpref;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import fr.at.testsliderpref.utils.SeekBarPreference;
public class SettingsActivity extends PreferenceActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
// Call super :
super.onCreate(savedInstanceState);
// Set the activity's fragment :
getFragmentManager().beginTransaction().replace(android.R.id.content, new SettingsFragment()).commit();
}
public static class SettingsFragment extends PreferenceFragment implements OnSharedPreferenceChangeListener {
private SeekBarPreference _seekBarPref;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.activity_settings);
// Get widgets :
_seekBarPref = (SeekBarPreference) this.findPreference("SEEKBAR_VALUE");
// Set listener :
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
// Set seekbar summary :
int radius = PreferenceManager.getDefaultSharedPreferences(this.getActivity()).getInt("SEEKBAR_VALUE", 50);
_seekBarPref.setSummary(this.getString(R.string.settings_summary).replace("$1", ""+radius));
}
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
// Set seekbar summary :
int radius = PreferenceManager.getDefaultSharedPreferences(this.getActivity()).getInt("SEEKBAR_VALUE", 50);
_seekBarPref.setSummary(this.getString(R.string.settings_summary).replace("$1", ""+radius));
}
}
}
layout > activity_main.xml :
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/textview_text" />
</RelativeLayout>
menu > main.xml :
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="#+id/menu_settings"
android:title="#string/menu_settings"
android:icon="#android:drawable/ic_menu_preferences"/>
</menu>
xml > activity_settings.xml :
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<fr.at.testsliderpref.utils.SeekBarPreference
android:defaultValue="50"
android:dialogMessage="#string/settings_dialog_message"
android:key="SEEKBAR_VALUE"
android:max="100"
android:summary="#string/settings_summary"
android:text="#string/settings_unit"
android:title="#string/settings_title" />
</PreferenceScreen>
values > strings.xml :
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">TestSliderPref</string>
<string name="textview_text">SeekBarPreference test</string>
<string name="menu_settings">Settings</string>
<string name="settings_dialog_message">Here comes a message</string>
<string name="settings_summary">Current value is $1</string>
<string name="settings_unit">Km</string>
<string name="settings_title">Here comes the title</string>
</resources>
Don't forget to add your SettingsActivity to the manifest, and it should be OK.
You could create your own Preference class that extends DialogPreference and shows a SeekBar as the dialog view.
An easy way to achieve this is to add an empty preference to your preferences.xml which uses a layout including a seekbar.
In your preferences.xml add
<Preference android:layout="#layout/sliderlayout" />
under layouts add the sliderlayout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<SeekBar
android:id="#+id/seekBar1"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
If you already moved to androidX you can simply use androidx.preference.SeekBarPreference. Documentation here and here
Define it in XML like this:
<SeekBarPreference
app:defaultValue="8"
app:key="COUNT_SPEED"
app:title="Fast count speed"
/>
Note: Now in Android Studio (my current version is 3.2.1) auto-suggestions doesn't work for androidx.preference.PreferenceScreen in prefs.xml, but don't worry inflating settings from xml with setPreferencesFromResource() works fine. All needed attributes names and component references you can find here.
This is good Slider Preference component - Android Slider Preference Library
https://github.com/jayschwa/AndroidSliderPreference
One more implementation of this.
All Credit goes to Tim Autin.
I wanted to have the values displayed loaded from XML Arrays
It looks like this
Code - this now extends ListPreference
import android.app.AlertDialog;
import android.content.Context;
import android.os.Bundle;
import android.preference.ListPreference;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
public class SeekBarListPreference extends ListPreference implements SeekBar.OnSeekBarChangeListener, View.OnClickListener {
// ------------------------------------------------------------------------------------------
// Private attributes :
private static final String androidns = "http://schemas.android.com/apk/res/android";
private SeekBar mSeekBar;
private TextView mSplashText, mValueText;
private Context mContext;
private String mDialogMessage;
// ------------------------------------------------------------------------------------------
public SeekBarListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
// Get string value for dialogMessage :
int mDialogMessageId = attrs.getAttributeResourceValue(androidns, "dialogMessage", 0);
if (mDialogMessageId == 0)
mDialogMessage = attrs.getAttributeValue(androidns, "dialogMessage");
else mDialogMessage = mContext.getString(mDialogMessageId);
}
// ------------------------------------------------------------------------------------------
// DialogPreference methods :
#Override
protected View onCreateDialogView() {
LinearLayout.LayoutParams params;
LinearLayout layout = new LinearLayout(mContext);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(6, 6, 6, 6);
mSplashText = new TextView(mContext);
mSplashText.setPadding(30, 10, 30, 10);
if (mDialogMessage != null)
mSplashText.setText(mDialogMessage);
layout.addView(mSplashText);
mValueText = new TextView(mContext);
mValueText.setGravity(Gravity.CENTER_HORIZONTAL);
mValueText.setTextSize(32);
params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
layout.addView(mValueText, params);
mSeekBar = new SeekBar(mContext);
mSeekBar.setOnSeekBarChangeListener(this);
layout.addView(mSeekBar, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
setProgressBarValue();
return layout;
}
#Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
// do not call super
}
private void setProgressBarValue() {
String mValue = null;
if (shouldPersist()) {
mValue = getValue();
}
final int max = this.getEntries().length - 1;
mSeekBar.setMax(max);
mSeekBar.setProgress(this.findIndexOfValue(mValue));
}
#Override
protected void onBindDialogView(View v) {
super.onBindDialogView(v);
setProgressBarValue();
}
#Override
public void onProgressChanged(SeekBar seek, int value, boolean fromTouch) {
final CharSequence textToDisplay = getEntryFromValue(value);
mValueText.setText(textToDisplay);
}
private CharSequence getEntryFromValue(int value) {
CharSequence[] entries = getEntries();
return value >= 0 && entries != null ? entries[value] : null;
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
#Override
public void showDialog(Bundle state) {
super.showDialog(state);
Button positiveButton = ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_POSITIVE);
positiveButton.setOnClickListener(this);
}
#Override
public void onClick(View v) {
if (shouldPersist()) {
final int progressChoice = mSeekBar.getProgress();
setValueIndex(progressChoice);
}
getDialog().dismiss();
}
}
The usage in the preferences file is now
<com.yourfullpackage.SeekBarListPreference
android:defaultValue="0"
android:dialogMessage="#string/time_limit_pref"
android:entries="#array/timeListArray"
android:entryValues="#array/timeListValues"
android:key="time"
android:summary="Select time limit"
android:title="Time"
/>
And the arrays
<string-array name="timeListArray">
<item>10 Seconds</item>
<item>30 Seconds</item>
<item>1 Minute</item>
<item>2 Minutes</item>
<item>Unlimited</item>
</string-array>
<!--This is going to be in seconds-->
<string-array name="timeListValues">
<item>10</item>
<item>30</item>
<item>60</item>
<item>120</item>
<item>0</item>
</string-array>
As a bonus if you already have ListPreferences, you don't need to add anything extra to display the summary as the current value. So this works just fine
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
SetSummaryForPreferenceKey(key);
}
private void SetSummaryForPreferenceKey(String key) {
Preference preference = findPreference(key);
// This works with our new SeekBarPreference
if (preference instanceof ListPreference) {
ListPreference listPref = (ListPreference) preference;
listPref.setSummary(listPref.getEntry());
}
}
I've made a slight improvement on the code provided in the answer by Tim. This simply makes live adjustments to the output value as the user moves the slider, rather than requiring the user to click the "Ok" button for the changes to be made.
This is useful for things like a Music Volume slider, where the user should be able to hear the adjustment in volume as it's being made.
If the user clicks "OK" the new value is maintained. If the user clicks "Cancel" then the original pre-adjustment value is restored.
Thanks and credit should go to Tim, I just added the extra onClick listener and pushed the value updating into the onChange listener.
package fr.atcm.carpooling.views.utils;
/* The following code was written by Matthew Wiggins
* and is released under the APACHE 2.0 license
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Improvements :
* - save the value on positive button click and/or seekbar change
* - restore pre-adjustment value on negative button click
*/
import android.R;
import android.app.AlertDialog;
import android.content.Context;
import android.os.Bundle;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
public class SeekBarPreference extends DialogPreference implements
SeekBar.OnSeekBarChangeListener, OnClickListener {
// ------------------------------------------------------------------------------------------
// Private attributes :
private static final String androidns = "http://schemas.android.com/apk/res/android";
private SeekBar mSeekBar;
private TextView mSplashText, mValueText;
private Context mContext;
private String mDialogMessage, mSuffix;
private int mDefault, mMax, mValue, mOrig = 0;
// ------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------
// Constructor :
public SeekBarPreference(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
// Get string value for dialogMessage :
int mDialogMessageId = attrs.getAttributeResourceValue(androidns,
"dialogMessage", 0);
if (mDialogMessageId == 0)
mDialogMessage = attrs
.getAttributeValue(androidns, "dialogMessage");
else
mDialogMessage = mContext.getString(mDialogMessageId);
// Get string value for suffix (text attribute in xml file) :
int mSuffixId = attrs.getAttributeResourceValue(androidns, "text", 0);
if (mSuffixId == 0)
mSuffix = attrs.getAttributeValue(androidns, "text");
else
mSuffix = mContext.getString(mSuffixId);
// Get default and max seekbar values :
mDefault = attrs.getAttributeIntValue(androidns, "defaultValue", 0);
mMax = attrs.getAttributeIntValue(androidns, "max", 100);
}
// ------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------
// DialogPreference methods :
#Override
protected View onCreateDialogView() {
LinearLayout.LayoutParams params;
LinearLayout layout = new LinearLayout(mContext);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(6, 6, 6, 6);
mSplashText = new TextView(mContext);
mSplashText.setPadding(30, 10, 30, 10);
if (mDialogMessage != null)
mSplashText.setText(mDialogMessage);
layout.addView(mSplashText);
mValueText = new TextView(mContext);
mValueText.setGravity(Gravity.CENTER_HORIZONTAL);
mValueText.setTextSize(32);
params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
layout.addView(mValueText, params);
mSeekBar = new SeekBar(mContext);
mSeekBar.setOnSeekBarChangeListener(this);
layout.addView(mSeekBar, new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
if (shouldPersist())
mValue = getPersistedInt(mDefault);
mSeekBar.setMax(mMax);
mSeekBar.setProgress(mValue);
return layout;
}
#Override
protected void onBindDialogView(View v) {
super.onBindDialogView(v);
mSeekBar.setMax(mMax);
mSeekBar.setProgress(mValue);
}
#Override
protected void onSetInitialValue(boolean restore, Object defaultValue) {
super.onSetInitialValue(restore, defaultValue);
// Set adjustable value
if (restore)
mValue = shouldPersist() ? getPersistedInt(mDefault) : 0;
else
mValue = (Integer) defaultValue;
// Set original pre-adjustment value
mOrig = mValue;
}
// ------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------
// OnSeekBarChangeListener methods :
#Override
public void onProgressChanged(SeekBar seek, int value, boolean fromTouch) {
String t = String.valueOf(value);
mValueText.setText(mSuffix == null ? t : t.concat(" " + mSuffix));
if (shouldPersist()) {
mValue = mSeekBar.getProgress();
persistInt(mSeekBar.getProgress());
callChangeListener(Integer.valueOf(mSeekBar.getProgress()));
}
}
#Override
public void onStartTrackingTouch(SeekBar seek) {
}
#Override
public void onStopTrackingTouch(SeekBar seek) {
}
public void setMax(int max) {
mMax = max;
}
public int getMax() {
return mMax;
}
public void setProgress(int progress) {
mValue = progress;
if (mSeekBar != null)
mSeekBar.setProgress(progress);
}
public int getProgress() {
return mValue;
}
// ------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------
// Set the positive button listener and onClick action :
#Override
public void showDialog(Bundle state) {
super.showDialog(state);
Button positiveButton = ((AlertDialog) getDialog())
.getButton(AlertDialog.BUTTON_POSITIVE);
Button negativeButton = ((AlertDialog) getDialog())
.getButton(AlertDialog.BUTTON_NEGATIVE);
positiveButton.setOnClickListener(cListenPos);
negativeButton.setOnClickListener(cListenNeg);
}
View.OnClickListener cListenPos = new View.OnClickListener() {
public void onClick(View v) {
if (shouldPersist()) {
mValue = mSeekBar.getProgress();
mOrig = mSeekBar.getProgress();
persistInt(mSeekBar.getProgress());
callChangeListener(Integer.valueOf(mSeekBar.getProgress()));
}
((AlertDialog) getDialog()).dismiss();
}
};
View.OnClickListener cListenNeg = new View.OnClickListener() {
public void onClick(View v) {
if (shouldPersist()) {
mValue = mOrig;
persistInt(mOrig);
callChangeListener(Integer.valueOf(mOrig));
}
((AlertDialog) getDialog()).dismiss();
}
};
#Override
public void onClick(View v) {}
// ------------------------------------------------------------------------------------------
}
One small improvement of Tim's answer and its derivates:
protected void onBindDialogView(View v) {
super.onBindDialogView(v);
mSeekBar.setMax(mMax);
mSeekBar.setProgress(mValue);
String t = String.valueOf(mValue);
mValueText.setText(mSuffix == null ? t : t.concat(" " + mSuffix));
}
It will populate current value at start which is otherwise empty until you actually slide the slider.