Reputation: 135
I've been trying to get a switch preference working in Android whereby I can intercept and handle differently, in certain cases, when they switch it on/off vs when they click the whole preference.
This is what I'm trying to accomplish: User goes into preferences tags are off and no tags are stored (ie: tag preference is empty) User turns on preference for tags, and since no tags are stored currently it launches a tag search activity for user to find the tag. - works fine.
If tag already exists, and they change the state ONLY then update the value as normal. - works fine
Here's my issue: If they click the preference though and they already have a tag saved, don't change the state (regardless if it's enabled or disabled), launch the tag search activity. - this DOESN'T work.
What I've found so far is that in the final scenario above, I get a call to onPreferenceChanged, followed by a call to onPreferenceClicked, followed by a subsequent call to onPreferenceChanged. This seems to be my problem. The first call to onPreferenceChanged causes my listener on my SharedPreferences to be called telling it that it's now enabled.
If I didn't receive the first call to onPreferenceChanged then I wouldn't have an issue.
Here is the relevant parts where I'm setting the listeners
SwitchPreference tagPref = (SwitchPreference) findPreference(PreferencesConstants.PREFERENCE_TAG_ENABLED);
tagPref.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
Log.e("BLAH", "onPrefChanged....is it handled by OnClick?" + Boolean.toString(handledByClick));
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext());
boolean enabled = prefs.getBoolean(PreferencesConstants.PREFERENCE_TAG_ENABLED, false);
Log.e("BLAH", "value stored in prefs? " + Boolean.toString(enabled));
if (newValue instanceof Boolean) {
enabled = (Boolean) newValue;
}
Log.e("BLAH", "New value? " + Boolean.toString(enabled));
if (!handledByClick) {
if (enabled && (currentTag == null || currentTag.isEmpty())) {
Log.e("BLAH", "Enabled and CurrentTag empty!");
Intent intent = new Intent(getActivity(), TagSearchActivity.class);
startActivityForResult(intent, 0);
return false; // always return false, we'll handle
// updating
// this value manually.
} else {
return true;
}
}
Log.e("BLAH", "returning false (AS IN WE HANDLED IT).");
return false;
}
});
tagPref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
handledByClick = true;
Log.e("BLAH", "onprefClick");
Intent intent = new Intent(getActivity(), TagSearchActivity.class);
startActivityForResult(intent, 0);
return true;
}
});
Here are the relevant log lines after running it with a saved tag, and clicking the preference.
01-18 15:55:05.593: E/BLAH(13261): onPrefChanged....is it handled by OnClick?false
01-18 15:55:05.593: E/BLAH(13261): value stored in prefs? true
01-18 15:55:05.593: E/BLAH(13261): New value? false
01-18 15:55:05.613: E/DifferentClass(13261): On Shared Preferences Changed - tagEnabled
01-18 15:55:05.652: E/DifferentClass(13261): disabled TAG in cancelAlarmService
01-18 15:55:05.662: E/AnotherClass(13261): Updating Feed List. Old Size: 33, New Size: 14
01-18 15:55:05.682: E/BLAH(13261): onprefClick
01-18 15:55:05.812: E/BLAH(13261): onPrefChanged....is it handled by OnClick?true
01-18 15:55:05.812: E/BLAH(13261): value stored in prefs? false
01-18 15:55:05.822: E/BLAH(13261): New value? false
01-18 15:55:05.822: E/BLAH(13261): returning false (AS IN WE HANDLED IT).
Upvotes: 8
Views: 15141
Reputation: 8775
This took me ages, and none of the answers here worked. I finally found the simplest answer, no custom layout required, in a Github Gist, which I'll preserve here, but I have modified it to work with SwitchPreferenceCompat instead of SwitchPreference.
First, create the custom preference.
package com.mendhak.gpslogger.ui.components;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import androidx.appcompat.widget.SwitchCompat;
import androidx.preference.PreferenceViewHolder;
import androidx.preference.SwitchPreferenceCompat;
/**
* Custom preference for handling a switch with a clickable preference area as well
*/
public class SwitchPlusClickPreference extends SwitchPreferenceCompat {
//
// Public interface
//
/**
* Sets listeners for the switch and the background container preference view cell
* @param listener A valid SwitchPlusClickListener
*/
public void setSwitchClickListener(SwitchPlusClickListener listener){
this.listener = listener;
}
private SwitchPlusClickListener listener = null;
/**
* Interface gives callbacks in to both parts of the preference
*/
public interface SwitchPlusClickListener {
/**
* Called when the switch is switched
* @param buttonView
* @param isChecked
*/
public void onCheckedChanged(SwitchCompat buttonView, boolean isChecked);
/**
* Called when the preference view is clicked
* @param view
*/
public void onClick(View view);
}
public SwitchPlusClickPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public SwitchPlusClickPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SwitchPlusClickPreference(Context context) {
super(context);
}
//
// Internal Functions
//
/**
* Recursively go through view tree until we find an android.widget.Switch
* @param view Root view to start searching
* @return A Switch class or null
*/
private SwitchCompat findSwitchWidget(View view){
if (view instanceof SwitchCompat){
return (SwitchCompat)view;
}
if (view instanceof ViewGroup){
ViewGroup viewGroup = (ViewGroup)view;
for (int i = 0; i < viewGroup.getChildCount();i++){
View child = viewGroup.getChildAt(i);
if (child instanceof ViewGroup){
SwitchCompat result = findSwitchWidget(child);
if (result!=null) return result;
}
if (child instanceof SwitchCompat){
return (SwitchCompat)child;
}
}
}
return null;
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
final SwitchCompat switchView = findSwitchWidget(holder.itemView);
if (switchView!=null){
switchView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null)
listener.onCheckedChanged((SwitchCompat) v, ((SwitchCompat)v).isChecked());
}
});
switchView.setChecked(getSharedPreferences().getBoolean(getKey(),false));
switchView.setFocusable(true);
switchView.setEnabled(true);
//Set the thumb drawable here if you need to. Seems like this code makes it not respect thumb_drawable in the xml.
}
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (listener!=null) listener.onClick(v);
}
});
}
// //Get a handle on the 2 parts of the switch preference and assign handlers to them
// @Override
// protected void onBindView (View view){
// super.onBindView(view);
//
// final Switch switchView = findSwitchWidget(view);
// if (switchView!=null){
// switchView.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
// if (listener != null)
// listener.onCheckedChanged((Switch) v, ((Switch)v).isChecked());
// }
// });
// switchView.setChecked(getSharedPreferences().getBoolean(getKey(),false));
// switchView.setFocusable(true);
// switchView.setEnabled(true);
// //Set the thumb drawable here if you need to. Seems like this code makes it not respect thumb_drawable in the xml.
// }
//
// view.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
// if (listener!=null) listener.onClick(v);
// }
// });
// }
}
Next in your preference XML, add an entry:
<com.mendhak.gpslogger.ui.components.SwitchPlusClickPreference
android:key="google_drive_enabled"
android:title="@string/google_drive_setup_title"
android:icon="@drawable/googledrive"/>
And then in the code for your Preference Activity class, the real magic is that you use the built-in callbacks for changed, and clicked.
((SwitchPlusClickPreference)findPreference(PreferenceNames.AUTOSEND_GOOGLE_DRIVE_ENABLED)).setSwitchClickListener(new SwitchPlusClickPreference.SwitchPlusClickListener() {
@Override
public void onCheckedChanged(SwitchCompat buttonView, boolean isChecked) {
//The switch bit changed. handle it.
}
@Override
public void onClick(View view) {
// The text bit was clicked on, handle it
}
});
Upvotes: 0
Reputation: 1326
I have been working with the same issue for ages now and you can go about it two ways.
Implementing a switchpreference with custom actions for every event:
forevercrashed made some good points. I tried follow them, but for me they didn't do it. I bet they work, but I needed more functionallity in an easier way. Xgouchet (second Link) uses Headers and custom xml layouts which uses custom placements and measurements (height, witdth, padding etc.). I needed a solution without altering Googles built in auto-generated layout.
The super easy and powerful way: implement your own SwitchPreference
!
Just make a class extend SwitchPreference
and then implement/override like so:
public class AutoUploadSwitchPreference extends SwitchPreference {
public AutoUploadSwitchPreference(Context context) {
super(context);
}
public AutoUploadSwitchPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AutoUploadSwitchPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onClick() {
//super.onClick(); THIS IS THE IMPORTANT PART!
}
By overriding onClick()
and commenting out / deleting super.onClick()
makes the SwitchPreference NOT call callChangeListener(Object newValue)
. Now you can click the preference and nothing happens, not until you want it to.
(One bug that would occur otherwise was having multiple calls to onPreferenceChange
in the fragment)
Now! To make things happen: Here is the structure I have used.
SettingsActivity
onCreate()
in your Activity - launch a PreferenceFragment
PreferenceFragment
, see how here : PreferenceFragmentIn your custom Fragment, get hold of your custom-preference. You can use findPreference("custom_switch_key")
.
OnPreferenceChangeListener
on the preferencethis
as argument.return false;
you can change the value with setChecked(true|false) on the switchpreference.when you implement onPreferenceChange(Preference preference, Object newValue)
you can add whatever functionality you want from pressing the switch-slider only
onClick()
further in the custom SwitchPreference
classonPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)
in the fragmentonPreferenceClickListener
like you did for the ChangeListener.Sorry if this is a long post. It is my first and I have been through so many stackoverflow-pages about this and no one was accurate, just wanted to get it right ;)
Upvotes: 8
Reputation: 135
After searching for hours more I came across a couple posts that will be helpful to others in this situation.
This one was the solution I opted for given my problem: How do I create one Preference with an EditTextPreference and a Togglebutton?
It's a very detailed answer and is very helpful in understanding preferences.
The other post I came across was this one: http://xgouchet.fr/android/index.php?article4/master-on-off-preferences-with-ice-cream-sandwich
It will give you pretty much the same look and feel as the one above, but requires more work and because of my requirements wouldn't work for me.
Upvotes: 2
Reputation: 116332
i think you are asking about a feature that doesn't exist.
however , since the preference activity uses a listView , you can use some tricks to customize it and handle it however you wish .
here's a post i've made about customizing it , based on this website . what i've asked there is how to add a listView , but i didn't know that a preference activity actually uses a listview .
Upvotes: 1