I'm doing an with a PreferenceActivity with two Fragments, each one containing a PreferenceScreen.
The thing I want to do is to create an event listener on a Custom preference that I have (a row of this custom Preference is a TextView with a SwitchView). I handle well the Switch preference, it keeps it saved as I want, but now what I want to do is add an event on the TextView between the SwitchView to show what I want on the other part of the screen (the second Fragment).
Let me show you my current code.
This is my CustomPreference (it's just a TextView and a Switch)
public class ItemPreference extends Preference{
private static TextView text; // this is the text at the left of the switch, it's where i want to handle the event
// to show an other preferencescreen in the fragment between
private Switch switcher; // the key of the preference is for the SWITCH !!
private boolean checked;
private Context context;
public TextView getCustomText(){
return text;
}
public ItemPreference(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
setLayoutResource(R.layout.row_setting); // the layout resource of my custom preference
}
#Override
protected void onBindView(View view) {
super.onBindView(view);
text = (TextView)view.findViewById(R.id.buttonRowSetting);
// to display the text in the TextView between the Switch, I use the key of the Switch
if (this.getKey().equals("pref_key_classification"))
text.setText(R.string.title_activity_classification);
// the switchview and it's preference saving
switcher = (Switch)view.findViewById(R.id.switchRowSetting);
Boolean value = this.getPersistedBoolean(false);
if (value){
switcher.setChecked(true);
} else {
switcher.setChecked(false);
}
switcher.setOnCheckedChangeListener(new OnCheckedChangeListener(){
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
setSwitchChecked(isChecked);
}
});
}
public void setSwitchChecked(boolean value) {
if (checked != value) {
checked = value;
persistBoolean(value);
notifyDependencyChange(shouldDisableDependents());
notifyChanged();
}
}
#Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return a.getBoolean(index, false);
}
#Override
protected void onClick() {
super.onClick();
}
#Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
setSwitchChecked(restorePersistedValue ? getPersistedBoolean(checked) : (Boolean) defaultValue);
}
#Override
protected Parcelable onSaveInstanceState() {
return super.onSaveInstanceState();
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
super.onRestoreInstanceState(state);
}
}
This is the SettingsFragment, on the left part of the screen, where I want to handle the event to show what I want on the other part of the screen.
public class SettingsFragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.settings); // the left preferencefragment
Preference stat = (Preference) findPreference(getString(R.string.pref_key_statistic));
stat.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
if(getActivity() instanceof OnSettingPageListener){
// if i click on this preference, this loads this preferencescreen on the other fragment, works well.
((OnSettingPageListener)getActivity()).onSettingPageChange(R.xml.settings_stat);
}
return true;
}
});
// this is the custompreference, i would like to handle here a listener on the TextView to display a specific preferencescreen on the other fragment
ItemPreference classif = (ItemPreference) findPreference(getString(R.string.pref_key_classification));
// i tried this, and i also tried to make my own Listener also, but doesn't works
classif.getCustomText().setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (getActivity() instanceof OnSettingPageListener){
// the content i want to load on the other fragment
((OnSettingPageListener)getActivity()).onSettingPageChange(R.xml.settings_classification);
}
}
});
}
// [...]
}
So I hope you understand what's my problem, it's just a matter of listener. Does someone has a solution?
I'm not sure if you want the event on your first fragment to show the second fragment (for further editing) or if you want the event to make a change visible to the second fragment.
Let's say you want the change to be visible to the second fragment:
Have your second fragment implement SharedPreference.OnSharedPreferenceChangeListener with method onSharedPreferenceChanged, implemented to do what you want to have happen when the preference in the first fragment changes.
In the second fragment's onResume method, call registerOnSharedPreferenceChangeListener on your SharedPreferences object.
In the second fragment's onPause method, call unregisterOnSharedPreferenceChangeListener on your SharedPreferences object.
Now, if you want to show the second fragment for editing the preference in the first fragment:
Give your preference a fragment attribute that declares the fragment class that edits your preference
Have your first fragment implement PreferenceFragment.OnPreferenceStartFragmentCallback with method onPreferenceStartFragment implemented to instantiate your second fragment and display it. Your PreferenceActivity subclass will invoke this callback when the preference is clicked.
I finally found a solution, my problem was the way the handle a listener on the textview of my custom preference. I followed the observer pattern and made it work on my own listener.
On my fragment I instantiated my CustomSwitchPreference (I just renamed the ItemPreference class you can see upstairs ^^). I called my own listener, you will see the code after.
CustomSwitchPreference classif = (CustomSwitchPreference)
findPreference(getString(R.string.pref_key_classification));
classif.setHandlerListener(new ItemPreferenceTextViewListener() {
#Override
public void onHandle(TextView textView) {
if (getActivity() instanceof OnSettingPageListener){
((OnSettingPageListener)getActivity())
.onSettingPageChange(R.xml.settings_classification);
}
}
});
And there we go for the listener in the CustomPreference class, first create an interface to made your own listener (you can write it in the same file as the CustomPreference class you are doing) :
interface ItemPreferenceTextViewListener {
// the function which is going to be called when we will use our listener
void onHandle(TextView textView);
}
And then in the CustomPreference class you do this :
// you can put the interface here if you want to make it visible
public class CustomSwitchPreference extends Preference {
private static TextView text;
// the key of the preference is for the SWITCH !! this activates or
// not the "game" on the mainscreen
private Switch switcher;
private boolean checked;
// ---------------------------------------------------------------------- \\
// create a listener in your own custom preference
ItemPreferenceTextViewListener itemPreferenceTextViewListener ;
// this is the function to handle the event on the textview of the
// custom preference item, creates the listener
public void setHandlerListener(ItemPreferenceTextViewListener listener) {
itemPreferenceTextViewListener = listener;
}
// to make the event happen on a textview
// (we will pass the right textview in the onBindView(....) )
protected void myEventListener(TextView textView) {
if(itemPreferenceTextViewListener!=null)
itemPreferenceTextViewListener.onHandle(textView);
}
// ---------------------------------------------------------------------- \\
/**
* #param context
* #param attrs
*/
public CustomSwitchPreference(Context context, AttributeSet attrs) {
super(context, attrs);
setLayoutResource(R.layout.row_setting);
}
#Override
protected void onBindView(View view) {
super.onBindView(view);
// the TextView at the right of the switch, this one
// handles a listener to show the other page preference
text = (TextView)view.findViewById(R.id.buttonRowSetting);
// I called it buttonRowSetting because it will handle an event like a button
// I set the text of this TextView with the
// key preference of the related switch
if (this.getKey().equals("pref_key_classification"))
text.setText(R.string.title_activity_classification);
else if (this.getKey().equals("pref_key_matching"))
text.setText(R.string.title_activity_matching);
else if (this.getKey().equals("pref_key_intruder"))
text.setText(R.string.title_activity_intruder);
else if (this.getKey().equals("pref_key_semantic"))
text.setText(R.string.title_activity_semantic);
// AND HERE it's where it happens, simply make a basic listener
// on the textview, and inside the onClick method, handle your own
// listener, on the TextView you initialized just before)
text.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// just call
itemPreferenceTextViewListener.onHandle(text);
}
});
// ---------------------------------------------------------------------- \\
// to keep the Switch saved in the preferences
switcher = (Switch)view.findViewById(R.id.switchRowSetting);
Boolean value = this.getPersistedBoolean(false);
if (value){
switcher.setChecked(true);
} else {
switcher.setChecked(false);
}
switcher.setOnCheckedChangeListener(new OnCheckedChangeListener(){
#Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
setSwitchChecked(isChecked);
}
});
// ---------------------------------------------------------------------- \\
}
public void setSwitchChecked(boolean value) {
if (checked != value) {
checked = value;
persistBoolean(value);
notifyDependencyChange(shouldDisableDependents());
notifyChanged();
}
}
#Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return a.getBoolean(index, false);
}
#Override
protected void onClick() {
super.onClick();
}
#Override
protected void onSetInitialValue(boolean restorePersistedValue,
Object defaultValue) {
setSwitchChecked(restorePersistedValue
? getPersistedBoolean(checked) : (Boolean) defaultValue);
}
#Override
protected Parcelable onSaveInstanceState() {
return super.onSaveInstanceState();
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
super.onRestoreInstanceState(state);
}
}
Hope this helps, it was my mistake I did not called the onHandle method inside the myTextView.onClickListener. Hope this will help one to understand well how the observer pattern works.
Related
I want to set the background color of the Spinner's main TextView to blue whenever the user is about to make a selection from the dropdown. So how do I know when the dropdown appears and disappears?
I think we can use an onTouch listener on the Spinner for when the dropdown appears, but I am not sure what we would do for when it disappears.
There is a method that tells us about the state of the spinner. Which is following
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
Log.d("","selected")
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
Log.d("nothing" , "unselected");
}
});
But it won't tell you the state when you touch outside of the spinner. so to resolve this issue you will have to implement your own logic for that. Fortunately, luksporg has shared a solution to that which can help you out.
public class CustomSpinner extends Spinner {
/**
* An interface which a client of this Spinner could use to receive
* open/closed events for this Spinner.
*/
public interface OnSpinnerEventsListener {
/**
* Callback triggered when the spinner was opened.
*/
void onSpinnerOpened(Spinner spinner);
/**
* Callback triggered when the spinner was closed.
*/
void onSpinnerClosed(Spinner spinner);
}
private OnSpinnerEventsListener mListener;
private boolean mOpenInitiated = false;
// implement the Spinner constructors that you need
#Override
public boolean performClick() {
// register that the Spinner was opened so we have a status
// indicator for when the container holding this Spinner may lose focus
mOpenInitiated = true;
if (mListener != null) {
mListener.onSpinnerOpened(this);
}
return super.performClick();
}
#Override
public void onWindowFocusChanged (boolean hasFocus) {
if (hasBeenOpened() && hasFocus) {
performClosedEvent();
}
}
/**
* Register the listener which will listen for events.
*/
public void setSpinnerEventsListener(
OnSpinnerEventsListener onSpinnerEventsListener) {
mListener = onSpinnerEventsListener;
}
/**
* Propagate the closed Spinner event to the listener from outside if needed.
*/
public void performClosedEvent() {
mOpenInitiated = false;
if (mListener != null) {
mListener.onSpinnerClosed(this);
}
}
/**
* A boolean flag indicating that the Spinner triggered an open event.
*
* #return true for opened Spinner
*/
public boolean hasBeenOpened() {
return mOpenInitiated;
}
}
I have a tabhost with three activities and I want to save the pressed state of the buttons of each activity
So now How can I save the pressed state of each button in all three child activities so that when I move from one activity to the other the button pressed state will be reflected on moving back. first activity -> all 4 buttons pressed -> go to 2nd activity -> come back to first activity -> all buttons in first activity should be in pressed state
When I go to second child tab and come to the first child tab the change(The buttons which I pressed are not in pressed state) is not reflecting
Help is always appreciated , Thanks
this is my code in first tabhost child activity
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
seatdirnbtn.setOnClickListener(listner1);
seatdirnbtn1.setOnClickListener(listner2);
seatdirnbtn.setPressed(true);
seatdirnbtn1.setPressed(true);
this.LoadPreferences();
}
private void SavePreferences() {
SharedPreferences sharedPreferences = getApplicationContext().getSharedPreferences("sharedPreferences",MODE_WORLD_READABLE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean("state", seatdirnbtn.isEnabled());
editor.putBoolean("state1", seatdirnbtn1.isEnabled());
editor.commit();
}
private void LoadPreferences() {
SharedPreferences sharedPreferences = getApplicationContext().getSharedPreferences("sharedPreferences",MODE_WORLD_READABLE);
Boolean state = sharedPreferences.getBoolean("state", false);
Boolean state1 = sharedPreferences.getBoolean("state1", false);
seatdirnbtn.setPressed(state);
seatdirnbtn1.setPressed(state1);
}
#Override
protected void onStart() {
super.onStart();
LoadPreferences();
}
#Override
protected void onPause() {
SavePreferences();
super.onPause();
}
public static boolean isclick = false;
private View.OnClickListener listner1 = new View.OnClickListener() {
public void onClick(View v) {
if (isclick) {
seatdirnbtn.setBackgroundResource(R.drawable.icon4hlt);
} else {
seatdirnbtn.setBackgroundResource(R.drawable.icon4);
}
isclick = !isclick;
}
};
private View.OnClickListener listner2 = new View.OnClickListener() {
public void onClick(View v) {
if (isclick) {
seatdirnbtn1.setBackgroundResource(R.drawable.icon2hlt);
} else {
seatdirnbtn1.setBackgroundResource(R.drawable.icon2);
}
isclick = !isclick;
}
};
probably you should override onResume() method in which you should set buttons states. this method is called after onCreate() and even the activity is already created. If you have activities in tabHost they are not created each time you switch between tabs so onCreate() method will be called only once but onResume() every time you switch to tab with particular activity.
your code which is loading preferences is in onStart() method. Look here on activity lifecycle. You can see that this method is called only if your activity was stopped before but will never called if it was just paused.
EDIT:
if you have just 2 states like in your code from question it could be better to use ToggleButton which also generally have 2 states. You can style it to have different backgrounds for each state. This tutorial could be helpfull.
Than you will have a little bit different Listener:
toggleButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
if(checked) {
//do sth if it's checked
} else {
//do sth if it's not checked;
}
}
});
to change states for them programatically:
toggleButton.setChecked(true); //or false
so finally you can save this state to SharedPreferences:
editor.putBoolean("toggleButton1",toggleButton.isChecked());
and when you will need this state:
boolean isChecked = sharedPreferences.getBoolean("toggleButton1",false);
toggleButton.setChecked(isChecked);
selector will take care of switching button backgrounds for each state.
I have an object "A"
"A" initiates the display of a popup window
That popup window has a button within it.
I want that button in the popup window to initiate a call to a method in "A".
I want to initiate the call from the line of code below that says:
"// RIGHT HERE I WANT TO CALL A METHOD IN "A" from which this popup was declared"
Generally speaking how can I call a method in the object that declares the popup window, from that popup window? this seems like it would be soo easy, but I am soo Newbie with this OO stuff.
If this explanation is confusing I will be happy to embellish.
public EventsOverlay A = new EventsOverlay(a, b))
class EventsOverlay extends ItemizedOverlay<OverlayItem> {
private PrePopupPanel panel=new PrePopupPanel( R.layout.preview1);
#Override
protected boolean onTap(int index) {
panel.show(true);
return true;
}
...
} end the EventsOverly class
class PrePopupPanel {
View popup;
boolean isVisible=false;
PrePopupPanel(int layout) {
ViewGroup parent=(ViewGroup)mapView.getParent();
popup=getLayoutInflater().inflate(layout, parent, false);
popup.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
hide();
}
});
ImageButton infobtn = (ImageButton)popup.findViewById(R.id.button1);
infobtn.setOnClickListener(new OnClickListener()
{
public void onClick(View v)
{
hide();
// RIGHT HERE I WANT TO CALL A METHOD IN "A" from which this popup was declared
}
});
... other methods like show(), hide() etc... copied from someone else
Your class is inside the other one, you may have access to all member method of your parent.
Use an Interface to do this.
-- class A should implement the interface
-- Pass this to PrePopupPanel.
-- call the method through the interface object.
Interface
package com.demo.interface;
public interface ICallHandler {
public void show(String show);
}
Class
class EventsOverlay extends ItemizedOverlay<OverlayItem> implements ICallHandler{
private PrePopupPanel panel=new PrePopupPanel( R.layout.preview1,this);
#Override
protected boolean onTap(int index) {
panel.show(true);
return true;
}
PrePopupPanel
Class PrePopupPanel {
View popup;
boolean isVisible=false;
ICallHandler mHandler;
PrePopupPanel(int layout, ICallHandler callHandler) {
mHandler = callHandler;
ViewGroup parent=(ViewGroup)mapView.getParent();
popup=getLayoutInflater().inflate(layout, parent, false);
popup.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
hide();
}
});
ImageButton infobtn = (ImageButton)popup.findViewById(R.id.button1);
infobtn.setOnClickListener(new OnClickListener()
{
public void onClick(View v)
{
hide();
// RIGHT HERE I WANT TO CALL A METHOD IN "A" from which this popup was declared
mHandler.show();
}
});
Use Android Dialog methods.
In onCreateDialog you can pass a reference to your custom object and trigger any methods you want when user clicks a button. Then anywhere in your activity code call showDialog()
I have a PreferenceActivity with, among other things, a category including call forward options. What I want is a preference that:
Enables/Disables if the user presses a checkbox on the right.
Opens up the EditTextPreference dialog if the user presses the text(or anything else in the preference)
It's probably not of any use but here is a snippet of this particular preferencecategory :
<PreferenceCategory
android:title="#string/category_callforward">
<EditTextPreference
android:key="call_forward_always"
android:title="#string/call_forward_always"
android:summary="#string/call_forward_forwardto" />
</PreferenceCategory>
EDIT
I'd like to implement it in this method if possible:
// Locates the correct data from saved preferences and sets input type to numerics only
private void setCallForwardType()
{
ep1 = (EditTextPreference) findPreference("call_forward_always");
EditText et = (EditText) ep1.getEditText();
et.setKeyListener(DigitsKeyListener.getInstance());
}
EDIT2
If anyone is still wondering - this is what I want as a Preference:
EDIT3
I've searched around for a couple hours now and have come up with a single word: 'PreferenceGroupAdapter'. I have not, however, been able to find examples or tutorials showing me how to use it. Suggestions ? Is this even the correct path to go?
EDIT4
If this really isn't possibly I would very much like a suggestion to an alternative(user-friendly) solution that I can implement instead of the combined Edit- and Checkbox preference.
You can do this. First, create a class for preferences which should be extended from PreferenceActivity. Use like this:
// editbox ise your EditTextPreference, so set it.
checkbox = (CheckBoxPreference) findPreference("checkbox_preference");
checkbox.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference preference, Object newValue) {
if(newValue.toString().equals("false")) {
PrefActivity.this.editbox.setEnabled(false);
} else if(newValue.toString().equals("true")) {
PrefActivity.this.editbox.setEnabled(true);
}
return true;
}
});
I hope it helps.
A bit late but I think I've managed to create something similar with a dialog that creates a layout with an edit text and a checkbox, it should be possible to do the same in a normal layout:
public class CheckEditTextPreference extends DialogPreference {
private static final String KEY_PROPERTY_DISABLED = "key_property_disabled";
private EditText editText;
private CheckBox checkBox;
private String text;
private boolean isDisabled;
private SharedPreferences mySharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext());
public CheckEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected View onCreateDialogView() {
return buildUi();
}
/**
* Build a dialog using an EditText and a CheckBox
*/
private View buildUi() {
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.setMargins(25, 0, 0, 0);
LinearLayout linearLayout = new LinearLayout(getContext());
linearLayout.setOrientation(LinearLayout.VERTICAL);
linearLayout.setLayoutParams(layoutParams);
checkBox = new CheckBox(getContext());
editText = new EditText(getContext());
editText.setLayoutParams(layoutParams);
checkBox.setLayoutParams(layoutParams);
checkBox.setText("Disabled");
FrameLayout dialogView = new FrameLayout(getContext());
linearLayout.addView(editText);
linearLayout.addView(checkBox);
dialogView.addView(linearLayout);
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
editText.setEnabled(!isChecked);
}
});
return dialogView;
}
#Override
protected void onBindDialogView(View view) {
super.onBindDialogView(view);
checkBox.setChecked(isDisabled());
editText.setText(getText());
}
#Override
protected void onDialogClosed(boolean positiveResult) {
if (positiveResult) {
String text = editText.getText().toString();
boolean isChecked = checkBox.isChecked();
if (callChangeListener(text)) {
setText(text);
}
if (callChangeListener(isChecked)) {
isDisabled(isChecked);
}
}
}
#Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return a.getString(index);
}
#Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
setText(restorePersistedValue ? getPersistedString("") : defaultValue.toString());
isDisabled(mySharedPreferences.getBoolean(KEY_PROPERTY_DISABLED, true));
}
public void setText(String value) {
this.text = value;
persistString(this.text);
}
public String getText() {
return this.text;
}
private void isDisabled(boolean value) {
this.isDisabled = value;
mySharedPreferences.edit().putBoolean(KEY_PROPERTY_DISABLED, this.isDisabled).apply();
}
public boolean isDisabled() {
return this.isDisabled;
}
}
And put this into your preferences screen:
<your.package.name.CheckEditTextPreference
android:key="chkEtPref"
android:title="Title"/>
Define a key in res/values/strings.xml for your CheckBoxPreference.
Give your CheckBoxPreference the XML attribute android:key="#string/THE_KEY_YOU_DEFINED" so that it will automatically save state in SharedPreferences.
Give your EditTextPreference the XML attribute android:dependency="#string/THE_KEY_YOU_DEFINED.
The EditTextPreference should then enable / disable depending on the state of the CheckBoxPreference.
I am creating a settings menu for a free version of my app. I have a ListPreference displaying many different options. However, only some of these options are to be made available in the free version (I would like all options to be visible - but disabled, so the user knows what they are missing!).
I'm struggling to disable certain rows of my ListPreference. Does anybody know how this can be achieved?
Solved it.
I made a custom class extending ListPreference. I then used a custom ArrayAdapter and used methods areAllItemsEnabled() and isEnabled(int position).
public class CustomListPreference extends ListPreference {
public CustomListPreference (Context context, AttributeSet attrs) {
super(context, attrs);
}
protected void onPrepareDialogBuilder(Builder builder) {
ListAdapter listAdapter = new CustomArrayAdapter(getContext(), R.layout.listitem, getEntries(), resourceIds, index);
builder.setAdapter(listAdapter, this);
super.onPrepareDialogBuilder(builder);
}
}
and
public class CustomArrayAdapter extends ArrayAdapter<CharSequence> {
public CustomArrayAdapter(Context context, int textViewResourceId,
CharSequence[] objects, int[] ids, int i) {
super(context, textViewResourceId, objects);
}
public boolean areAllItemsEnabled() {
return false;
}
public boolean isEnabled(int position) {
if(position >= 2)
return false;
else
return true;
}
public View getView(int position, View convertView, ViewGroup parent) {
...
return row;
}
I searched through and through all over the web, and couldn't find a way to achieve this. The answer above did not help me. I found the entire "ArrayAdapter" method very unintuitive , unhelpful, and hard to implement.
Finally, I actually had to look inside the source code for "ListPreference", to see what they did there, and figure out how to override the default behavior cleanly and efficiently.
I'm sharing my solution below. I made the class "SelectiveListPreference" to inherit the behavior of "ListPreference", but add a positive button, and prevent closing when an option is pressed. There is also a new xml attribute to specify which options are available in the free version.
My trick is not to call ListPreference's version of onPrepareDialogBuilder, but instead implement my own, with a custom click handler. I did not have to write my own code for persisting the selected value, since I used ListPreference's code (that's why I extended "ListPreference" and not "Preference").
The handler looks for the boolean resource "free_version" and if it's true, it only allows the options specified in "entry_values_free" xml attribute. If "free_version" is false, all options are allowed. There's also an empty method for inheritors, if something should happen when an option is chosen.
Enjoy,
Tal
public class SelectiveListPreference extends ListPreference
{
private int mSelectedIndex;
private Collection<CharSequence> mEntryValuesFree;
private Boolean mFreeVersion;
public SelectiveListPreference(Context context)
{
super(context);
}
//CTOR: load members - mEntryValuesFree & mFreeVersion
public SelectiveListPreference(Context context, AttributeSet attrs)
{
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.SelectiveListPreference);
try
{
CharSequence[] entryValuesFree = a
.getTextArray(R.styleable.SelectiveListPreference_entryValuesFree);
mEntryValuesFree = new ArrayList<CharSequence>(
Arrays.asList(entryValuesFree));
}
finally
{
a.recycle();
}
Resources resources = context.getResources();
mFreeVersion = resources.getBoolean(R.bool.free_version);
}
//override ListPreference's implementation - make our own dialog with custom click handler, keep the original selected index
#Override
protected void onPrepareDialogBuilder(android.app.AlertDialog.Builder builder)
{
CharSequence[] values = this.getEntries();
mSelectedIndex = this.findIndexOfValue(this.getValue());
builder.setSingleChoiceItems(values, mSelectedIndex, mClickListener)
.setPositiveButton(android.R.string.ok, mClickListener)
.setNegativeButton(android.R.string.cancel, mClickListener);
};
//empty method for inheritors
protected void onChoiceClick(String clickedValue)
{
}
//our click handler
OnClickListener mClickListener = new OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
if (which >= 0)//if which is zero or greater, one of the options was clicked
{
String clickedValue = (String) SelectiveListPreference.this
.getEntryValues()[which]; //get the value
onChoiceClick(clickedValue);
Boolean isEnabled;
if (mFreeVersion) //free version - disable some of the options
{
isEnabled = (mEntryValuesFree != null && mEntryValuesFree
.contains(clickedValue));
}
else //paid version - all options are open
{
isEnabled = true;
}
AlertDialog alertDialog = (AlertDialog) dialog;
Button positiveButton = alertDialog
.getButton(AlertDialog.BUTTON_POSITIVE);
positiveButton.setEnabled(isEnabled);
mSelectedIndex = which;//update current selected index
}
else //if which is a negative number, one of the buttons (positive or negative) was pressed.
{
if (which == DialogInterface.BUTTON_POSITIVE) //if the positive button was pressed, persist the value.
{
SelectiveListPreference.this.setValueIndex(mSelectedIndex);
SelectiveListPreference.this.onClick(dialog,
DialogInterface.BUTTON_POSITIVE);
}
dialog.dismiss(); //close the dialog
}
}
};
}
EDIT: we also need to override the implemented onDialogClosed from ListPreference (and do nothing), otherwise, things valued do not get persisted. Add:
protected void onDialogClosed(boolean positiveResult) {}
Maybe you can do it by overrding default getView:
Steps:
Extend ListPreference
Override onPrepareDialogBuilder and replace mBuilder in DialogPreference with ProxyBuilder
Handle getView in ProxyBuilder->AlertDialog->onShow->getListView->Adapter
Code samples are in custom row in a listPreference?
Having the same problem I found a solution (maybe "hack" is more appropriate). We can register an OnPreferenceClickListener for the ListPreference. Inside this listener we can get the dialog (since the preference was clicked we are pretty safe that it is not null). Having the dialog we can set a OnHierarchyChangeListener on the ListView of the dialog where we are notified when a new child view is added. With the child view at hand we can disable it.
Assuming that the ListView entries are created in the same order as the entry values of the ListPreference we can even get the entry value.
I hope somebody finds this helpful.
public class SettingsFragment extends PreferenceFragment {
private ListPreference devicePreference;
private boolean hasNfc;
#Override
public void onCreate(android.os.Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// load preferences
addPreferencesFromResource(R.xml.preferences);
hasNfc = getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC);
devicePreference = (ListPreference) getPreferenceScreen().findPreference(getString(R.string.pref_device));
// hack to disable selection of internal NFC device when not available
devicePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
final ListPreference listPref = (ListPreference) preference;
ListView listView = ((AlertDialog)listPref.getDialog()).getListView();
listView.setOnHierarchyChangeListener(new OnHierarchyChangeListener() {
// assuming list entries are created in the order of the entry values
int counter = 0;
public void onChildViewRemoved(View parent, View child) {}
public void onChildViewAdded(View parent, View child) {
String key = listPref.getEntryValues()[counter].toString();
if (key.equals("nfc") && !hasNfc) {
child.setEnabled(false);
}
counter++;
}
});
return false;
}
});
}
}