Android-Prevent dismissal of dropdown in AutoCompleteTextView after item selection - android

Even though I'm setting the setOnItemClickListener on the AutoCompleteTextView and performing some custom operations in it, once that method is done, the list dismisses and prints out the object.toString in the editbox.
I want to prevent dismissal of the dropdown on item select and would also like it to not replace the edit box. How can I achieve this ?

I also want to implement the same i used below code to implement it.
Create a custom class and extend AutoCompleteTextView.
Override dismissDropDown() method and remove the super call from it.
Will work for you.
public class CustomAutoComplete extends AutoCompleteTextView {
public NoSelectionAutoComplete(Context context) {
super(context);
}
public NoSelectionAutoComplete(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NoSelectionAutoComplete(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void replaceText(CharSequence text) {
}
#Override
public void dismissDropDown() {
}
}

I added an onClickListener to the entire custom row layout that I was using for the dropdown adapter. This way whenever the row is clicked, my row onClickListener is invoked and the default one for the dropdown is not.

First Question - Prevent dropdown dismissal:
Solved below.
Second Question - Prevent text replacement: (For others interested)
You can extend AutoCompleteTextView and override
protected void replaceText(CharSequence text) {}
to do nothing.
As others mentioned, overriding performCompletion() won't help here.

well at least it seems like they are planning to add this in near future.
/**
* Sets whether the drop-down should remain visible as long as there is there is
* {#link #enoughToFilter()}. This is useful if an unknown number of results are expected
* to show up in the adapter sometime in the future.
*
* The drop-down will occupy the entire screen below {#link #getDropDownAnchor} regardless
* of the size or content of the list. {#link #getDropDownBackground()} will fill any space
* that is not used by the list.
*
* #param dropDownAlwaysVisible Whether to keep the drop-down visible.
*
* #hide Pending API council approval
*/
public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) {
mPopup.setDropDownAlwaysVisible(dropDownAlwaysVisible);
}

edit,new answer:
this worked for me but it closes for a sec,and opens again.
class task extends TimerTask {
#Override
public void run() {
runOnUiThread(new Runnable() {
#Override
public void run() {
autoComplete.showDropDown();
}
});
}
};
autoComplete.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {
new Timer().schedule(new task(),0, 10);
}
});

Overriding replaceText without calling super works fine (prevents entering suggested text into AutoCompleteTextView), but overriding dismissDropDown causes not-dismissing dropdown not only when item clicked, but also when onBackPressed, touched outside dialog etc...
I've ended with NOT using setOnItemClickListener method from AutoCompleteTextView at all. I'm creating custom onClick in my custom ArrayAdapter and set it for all Views returned by getView method
View.OnClickListener onClick=null;
public void setOnItemClickListener(View.OnClickListener onClick) {
this.onClick=onClick;
/*this.onClick=new View.OnClickListener(){
#Override
public void onClick(View v) {
if(v.getTag()==null)
return;
Integer position = (Integer) v.getTag();
Toast.makeText(v.getContext(), "position: "+postion, Toast.LENGTH_SHORT).show();
}
});*/
}
#NonNull
#Override
public View getView(int position, View convertView, #NonNull ViewGroup parent) {
... call super/inflate convertView and do your stuff here
setCustomOnClick(convertView, position);
return convertView;
}
private void setCustomOnClick(final View view, final int position){
view.setTag(position);
view.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v) {
if(onClick==null)
return;
// inside called onClick method v.getTag() will return pressed position
onClick.onClick(v);
}
});
}
in fact setting onClick for whole view will cover "original" always-dismissing and always-replacing-text onClick (not called at all then). Remember about adding custom graphic representation, when pressed (ripple/selector are shown when "original" onClick called only)
I've needed also always-visible functionality, because my autocomplete must always show first position (functional), even when there is no suggestions (if present then shown below on positions 1+)
public class AlwaysVisibleAutoCompleteTextView extends AppCompatAutoCompleteTextView {
private boolean showAlways=true;
public AlwaysVisibleAutoCompleteTextView(Context context) {
super(context);
}
public AlwaysVisibleAutoCompleteTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AlwaysVisibleAutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setShowAlways(boolean showAlways) {
this.showAlways = showAlways;
}
#Override
public boolean enoughToFilter() {
return showAlways || super.enoughToFilter();
}
#Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
showDropDownIfFocused();
}
private void showDropDownIfFocused() {
if (enoughToFilter() && isFocused() && getWindowVisibility() == View.VISIBLE)
showDropDown();
}
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
showDropDownIfFocused();
}
}
when AlwaysVisibleAutoCompleteTextView is focused, but dropdown dismissed and user press view again, then dropdown is not showing, because focus state not changing (onFocusChanged not called), so
autoComplete.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP)
autoComplete.showDropDown();
return false;
}
});
If you aren't planning using setOnTouchListener for your AutoCompleteTextView for any other purpose, then OnTouchListener may be set inside AlwaysVisibleAutoCompleteTextView class (in every constructor)

Related

How to hide the previous and next numbers in a number picker?

Summary
I'm attempting to hide the previous and next numbers in an Android number picker. Additionally, I'm extending the NumberPicker class so that I can change the font size and color. I've noticed that I could potentially change the value:
private static final int MODIFIED_SELECTOR_WHEEL_ITEM_COUNT = 3;
I'm just not sure how I could override a static final value.
Default number picker:
Desired Modified Number Picker
I would like to make the number picker look like so, with the animation in tact:
Extended Number Picker class:
public class CustomStatNumberPicker extends android.widget.NumberPicker{
private static final int MODIFIED_SELECTOR_WHEEL_ITEM_COUNT = 1;
public CustomStatNumberPicker(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public void addView(View child) {
super.addView(child);
updateView(child);
}
#Override
public void addView(View child, int index, android.view.ViewGroup.LayoutParams params) {
super.addView(child, index, params);
updateView(child);
}
#Override
public void addView(View child, android.view.ViewGroup.LayoutParams params) {
super.addView(child, params);
updateView(child);
}
private void updateView(View view) {
if(view instanceof EditText){
((EditText) view).setTextSize(40);
((EditText) view).setTextColor(Color.parseColor("#FFFFFF"));
}
}
}
TL;DR; How do I hide the previous and next number picker values? Either through overriding a method, or modifying the extended class like I've already done.
Thank you in advance.
you could modify the drawing routine to skip over any index that isn't in the center row. Try changing the conditions at line 1478.

OnClick for Floating Action Button

I'm new to working with floating action button and trying to get a few of the basic things working today. Currently I am stuck on getting the onClick functionality to work. I pulled most of the code from googles FAB basic example, and in there it has an onChecked method which sends a string to a logger to show you have clicked it.
#Override
public void onCheckedChanged(FloatingActionButton fabView, boolean isChecked) {
// When a FAB is toggled, log the action.
switch (fabView.getId()){
case R.id.fab_1:
break;
default:
break;
}
}
I was thinking I'd be able to replace the functionality in there but that had no affect. So I tried to create the onClickListener like you would with any other button but that also had no affect. I am not sure how to continue since neither option worked. my goal is just to produce a dialog when the floating action button is clicked, but for now I am just trying to use a placeholder alert dialog.
This is the FloatingActionButtonFragment class:
public class FloatingActionButtonFragment extends Fragment implements FloatingActionButton.OnCheckedChangeListener {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.fab_layout, container, false);
// Make this {#link Fragment} listen for changes in both FABs.
FloatingActionButton fab1 = (FloatingActionButton) rootView.findViewById(R.id.fab_1);
fab1.setOnCheckedChangeListener(this);
fab1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage("Are you sure?")
.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// User cancelled the dialog
}
});
// Create the AlertDialog object and return it
AlertDialog dialog = builder.create();
dialog.show();
}
});
return rootView;
}
#Override
public void onCheckedChanged(FloatingActionButton fabView, boolean isChecked) {
// When a FAB is toggled, log the action.
switch (fabView.getId()){
case R.id.fab_1:
break;
default:
break;
}
}
}
And here is the FloatingActionButton class:
public class FloatingActionButton extends FrameLayout implements Checkable {
/**
* Interface definition for a callback to be invoked when the checked state
* of a compound button changes.
*/
public static interface OnCheckedChangeListener {
/**
* Called when the checked state of a FAB has changed.
*
* #param fabView The FAB view whose state has changed.
* #param isChecked The new checked state of buttonView.
*/
void onCheckedChanged(FloatingActionButton fabView, boolean isChecked);
}
/**
* An array of states.
*/
private static final int[] CHECKED_STATE_SET = {
android.R.attr.state_checked
};
private static final String TAG = "FloatingActionButton";
// A boolean that tells if the FAB is checked or not.
private boolean mChecked;
// A listener to communicate that the FAB has changed it's state
private OnCheckedChangeListener mOnCheckedChangeListener;
public FloatingActionButton(Context context) {
this(context, null, 0, 0);
}
public FloatingActionButton(Context context, AttributeSet attrs) {
this(context, attrs, 0, 0);
}
public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr);
setClickable(true);
// Set the outline provider for this view. The provider is given the outline which it can
// then modify as needed. In this case we set the outline to be an oval fitting the height
// and width.
setOutlineProvider(new ViewOutlineProvider() {
#Override
public void getOutline(View view, Outline outline) {
outline.setOval(0, 0, getWidth(), getHeight());
}
});
// Finally, enable clipping to the outline, using the provider we set above
setClipToOutline(true);
}
/**
* Sets the checked/unchecked state of the FAB.
* #param checked
*/
public void setChecked(boolean checked) {
// If trying to set the current state, ignore.
if (checked == mChecked) {
return;
}
mChecked = checked;
// Now refresh the drawable state (so the icon changes)
refreshDrawableState();
if (mOnCheckedChangeListener != null) {
mOnCheckedChangeListener.onCheckedChanged(this, checked);
}
}
/**
* Register a callback to be invoked when the checked state of this button
* changes.
*
* #param listener the callback to call on checked state change
*/
public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
mOnCheckedChangeListener = listener;
}
#Override
public boolean isChecked() {
return mChecked;
}
#Override
public void toggle() {
setChecked(!mChecked);
}
/**
* Override performClick() so that we can toggle the checked state when the view is clicked
*/
#Override
public boolean performClick() {
toggle();
return super.performClick();
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// As we have changed size, we should invalidate the outline so that is the the
// correct size
invalidateOutline();
}
#Override
protected int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (isChecked()) {
mergeDrawableStates(drawableState, CHECKED_STATE_SET);
}
return drawableState;
}
}
There isn't much to either class at this point, they are mostly husks, but I just want to get this basic functionality down before I continue, and being the noob I am, I don't know why this wouldn't work.
If you are not already heading for deadline, you must change the floating action button to the one provided by google in design library
just follow http://android-developers.blogspot.in/2015/05/android-design-support-library.html
Add to the XML Layout:
<android.support.design.widget.FloatingActionButton
android:id="#+id/myFAB"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/your_icon"
app:elevation="4dp"
... />
Add to the code behind:
FloatingActionButton myFab = (FloatingActionButton) myView.findViewById(R.id.myFAB);
myFab.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
doMyThing();
}
});
For more details follow : FloatingActionButton example with Support Library
Actually now with android support library it was very easy to add FAB and to customize it with click listeners
FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// FAB Action goes here
}
});
Reference : http://androidgifts.com/android-material-design-floating-action-button-tutorial/
To use dialog/Alertdialog with the floating action button you're using, try changing your onClick(View v) from this
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
to
AlertDialog.Builder builder = new AlertDialog.Builder(v.getContext());

Dispatch Touch event from DialogFragment view to parent activity view

Here is how my layout looks like:
I have a parent activity which has a custom view (view1 that handle onTouch events by itself) and 2 buttons (view2 and view3). The DialogFragment has a visible layout shown and the rest is transparent.
My dialog fragments look like this:
public class FragmentText extends DialogFragment{
public static FragmentText newInstance() {
FragmentText frag = new FragmentText();
frag.setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Translucent_NoTitleBar);
return frag;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE);
// instantiate the custom layout
final View layout = inflater.inflate(R.layout.fragment_layout, null);
.....
}
}
and the layout file looks like this:
<com.TransparentView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/layMain"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_marginBottom="70dp"
android:background="#color/transparent"
android:gravity="bottom"
android:orientation="vertical"
android:paddingBottom="70dp" >
<LinearLayout
android:id="#+id/layContent"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_margin="5dp"
android:background="#drawable/bk_text_edit"
android:orientation="vertical"
android:padding="#dimen/margin" >
all my layouts and buttons
</LinearLayout>
</com.TransparentView>
and
public class TransparentView extends LinearLayout {
#SuppressLint("NewApi")
public TransparentView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public TransparentView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TransparentView(Context context) {
super(context);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false; // event get propagated
}
}
When the user presses outside the visible layout of the DialogFragment I want to:
1. dismiss the dialog fragment
2. pass the onTouch to the parent activity and allow the user to interact with the views.
So basically if I drag my finger over View1, I want to dismiss the dialog and continue my dragging interaction against view1.
Is this possible to achieve ?
LE: also this does not work:
layMain.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
getActivity().dispatchTouchEvent(event);
return false;
}
});
I had the same problem with BottomSheetDialog. I solved my problem:
final BottomSheetDialog dialog = getDialog();
final View touchOutside = dialog.findViewById(R.id.touch_outside);
touchOutside.setOnTouchListener((v, event) -> {
getActivity().dispatchTouchEvent(event);
return false;
});
So looks like solution of Finn should work correctly.
In the end, as Luksprog suggested, I ended up with ditching DialogFragment. I did a test using simple Fragment which is hosted in an activity's FrameLayout so it looks just like an overlay on where I need it. In this way I still get all the interaction I need with the rest of the views.
Thank you all for your support.
even though you ditched DialogFragment this might help others.
you need to get the actual decor view and intercept the touch event there:
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
getDialog().getWindow().setBackgroundDrawableResource(android.R.color.transparent);
getDialog().getWindow().getDecorView().setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
getActivity().dispatchTouchEvent(event);
return false;
}
});
return super.onCreateView(inflater, container, savedInstanceState);
}
I would use an Alert dialog instead for you to get the desired effect:
AlertDialog.Builder Dialog = new AlertDialog.Builder(this);
Dialog.setMessage("Text you wish to enter in the dialog"));
//Use this if you would like to include a button at the bottom of the alert dialog. Otherwise just leave it blank.
Dialog.setNeutralButton("Back to App", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
}
});
Dialog.show();
Hope this helps :)
You may override boolean onInterceptTouchEvent(MotionEvent ev) in your dialog's transparent view, catch event, send it where you need, dismiss dialog and return false (which means that touch event wasn't consumed).
Or, if you don't want to override View class, you may override boolean dispatchTouchEvent (MotionEvent ev) in your dialog and try to find out where the click took place by analysing MotionEvent object.
If you are already successfully capturing the touch event in your LinearLayout, which I understand to be the transparent view surrounding the Dialog then you should be able to accomplish this with an interface back to the parent Activity and a call to a public method within your dialog class. I would make the following modifications:
First - define an interface within your TransparentView and add a public method to establish a callback to your parent activity.
public class TransparentView extends LinearLayout {
public interface TouchCallback{
public void onMotionEvent(MotionEvent e);
}
private TouchCallback mCallback;
#SuppressLint("NewApi")
public TransparentView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public TransparentView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TransparentView(Context context) {
super(context);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
callback.onMotionEvent(ev);
return false; // event get propagated
}
public setCallback(TouchCallback callback){
mCallback = callback;
}
}
The idea is to catch the motion event and manually pass it back to your hosting activity.
Once you pass the motion event back to the activity all you need to do is dismiss the dialog. DialogFragment already has a dismiss method built in so as long as you keep a reference to the dialog with an instance variable you should be able to do the following
#Override
public void onMotionEvent(MotionEvent e){
mDialog.dismiss();
}
If that does not work simply add a method in your dialog class and call that. From what I can tell from your example we don't really even need to do anything with the motion event captured by the transparent view because all we want to do is close the dialog when a touch occurs. As the callback should only be activated when a touch happens there's nothing to check for (though you could expand this later to implement a Handler and only close the dialog if the touch lasted for a specified amount of time).

Android ListPopupWindow's method isShowing() does not work

I've decided to create my own custom spinner by extending a TextView and composing a ListPopupWindow. I want to emulate the following functionality of the original Spinner: when the spinner is clicked the drop down list is shown, the second time the spinner is clicked the drop down list is dismissed. But I'm having some trouble, the ListPopupWindow.isShowing() seems to always return false (I've debugged it):
public class CustomSpinner extends TextView {
...
private ListPopupWindow dropDownPopup;
...
public CustomSpinner(Context context, AttributeSet attrs) {
super(context, attrs);
...
dropDownPopup = new ListPopupWindow(context, attrs);
dropDownPopup.setAnchorView(this);
dropDownPopup.setWidth(WindowManager.LayoutParams.WRAP_CONTENT);
dropDownPopup.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
dropDownPopup.dismiss();
...
}
});
this.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (dropDownPopup.isShowing()) {
dropDownPopup.dismiss();
} else {
dropDownPopup.show();
}
}
});
}
So, each time I click on the spinner the drop down list is shown. It is dismissed when I click on one of the items in the list. The problem seems to be that dropDownPopup.isShowing() always returns false.
By setting dropDownPopup.setModal(true), everything works.
By adding dropDownPopup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NEEDED);beforedropDownPopup.show();, it works for me.

Disabling rows in ListPreference

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;
}
});
}
}

Categories

Resources