1

I am using PreferenceActivity for the setting of my app. I want to add a new preference that allow the user to select an icon. For this task I want to use a ListPreference, but I want also to show the icon in the list.

I tried to customize the ListPreference to use a custom layout, but the problem is that once I do that the list items are not clickable (it does show my custom layout and use the default value for the current selection).

I tested it on different emulator version and on Galaxy S2. When pressing the item I could see some effect of the pressed/unpressed, but the onClick method is not called.

I followed the instruction on Android: Checkable Linear Layout for adding custom layout (I also tried the option describe in How to customize list preference radio button, but the same result).

IconTypePreference.java (copied from ListPreference and modified):

public class IconTypePreference extends DialogPreference {
    private IconType value;
    private int      clickedDialogIndex;
    private boolean  valueSet;

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public IconTypePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public IconTypePreference(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public IconTypePreference(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public IconTypePreference(Context context) {
        super(context);
    }

    public void setValue(String value) {
        // Always persist/notify the first time.
        final boolean changed = !TextUtils.equals(getValueText(), value);
        if (changed || !valueSet) {
            if (value == null) {
                this.value = null;
            } else {
                this.value = IconType.valueOf(value);
            }
            valueSet = true;
            persistString(value);
            if (changed) {
                notifyChanged();
            }
        }
    }

    public void setValueIndex(int index) {
        setValue(IconType.values()[index].toString());
    }

    public IconType getValue() {
        return value;
    }

    public String getValueText() {
        return (value == null ? null : value.toString());
    }

    public int findIndexOfValue(String value) {
        IconType[] values = IconType.values();
        for (int i = values.length - 1; i >= 0; i--) {
            if (values[i].toString().equals(value)) {
                return i;
            }
        }
        return -1;
    }

    private int getValueIndex() {
        return findIndexOfValue(getValueText());
    }

    @Override
    protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
        super.onPrepareDialogBuilder(builder);

        clickedDialogIndex = getValueIndex();
        builder.setSingleChoiceItems(new IconTypeAdapter(getContext()), clickedDialogIndex,
                                     new DialogInterface.OnClickListener() {
                                         public void onClick(DialogInterface dialog, int which) {
                                             clickedDialogIndex = which;
                                             IconTypePreference.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
                                             dialog.dismiss();
                                         }
                                     });
        /*
         * The typical interaction for list-based dialogs is to have
         * click-on-an-item dismiss the dialog instead of the user having to
         * press 'Ok'.
         */
        builder.setPositiveButton(null, null);
    }

    @Override
    protected void onDialogClosed(boolean positiveResult) {
        super.onDialogClosed(positiveResult);

        if (positiveResult && clickedDialogIndex >= 0) {
            String value = IconType.values()[clickedDialogIndex].toString();
            if (callChangeListener(value)) {
                setValue(value);
            }
        }
    }

    @Override
    protected Object onGetDefaultValue(TypedArray a, int index) {
        return a.getString(index);
    }

    @Override
    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
        setValue(restoreValue ? getPersistedString(getValueText()) : (String)defaultValue);
    }

    @Override
    protected Parcelable onSaveInstanceState() {
        final Parcelable superState = super.onSaveInstanceState();
        if (isPersistent()) {
            // No need to save instance state since it's persistent
            return superState;
        }

        final SavedState myState = new SavedState(superState);
        myState.value = getValueText();
        return myState;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if (state == null || !state.getClass().equals(SavedState.class)) {
            // Didn't save state for us in onSaveInstanceState
            super.onRestoreInstanceState(state);
            return;
        }

        SavedState myState = (SavedState)state;
        super.onRestoreInstanceState(myState.getSuperState());
        setValue(myState.value);
    }

    private static class SavedState extends BaseSavedState {
        String value;

        public SavedState(Parcel source) {
            super(source);
            value = source.readString();
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            super.writeToParcel(dest, flags);
            dest.writeString(value);
        }

        public SavedState(Parcelable superState) {
            super(superState);
        }

        public static final Parcelable.Creator<SavedState> CREATOR =
                new Parcelable.Creator<SavedState>() {
                    public SavedState createFromParcel(Parcel in) {
                        return new SavedState(in);
                    }

                    public SavedState[] newArray(int size) {
                        return new SavedState[size];
                    }
                };
    }

    private static class IconTypeAdapter extends ArrayAdapter<IconType> {
        private final String[]       iconTypeText;
        private       LayoutInflater inflater;

        public IconTypeAdapter(Context context) {
            super(context, R.layout.icon_type_item, IconType.values());
            this.inflater = LayoutInflater.from(context);
            iconTypeText = context.getResources().getStringArray(R.array.icon_type);
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if (convertView == null) {
                convertView = inflater.inflate(R.layout.icon_type_item, parent, false);
            }
            ((TextView)convertView.findViewById(R.id.text)).setText(iconTypeText[position]);
            convertView.setClickable(true);
            // todo: set view text
            return convertView;
        }

        @Override
        public boolean hasStableIds() {
            return true;
        }

        @Override
        public long getItemId(int position) {
            return position;
        }
    }
}

CheckableLinearLayout.java

public class CheckableLinearLayout extends LinearLayout implements Checkable {
    private Checkable checkable;

    public CheckableLinearLayout(Context context) {
        super(context);
    }

    public CheckableLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public CheckableLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public CheckableLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
//        setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
        checkable = getCheckable(this);
        if (checkable == null) {
            throw new RuntimeException("Missing Checkable component");
        }
    }

    private Checkable getCheckable(ViewGroup viewGroup) {
        View v;
        int childCount = viewGroup.getChildCount();
        for (int i = 0; i < childCount; ++i) {
            v = getChildAt(i);
            if (v instanceof Checkable) {
                return (Checkable)v;
            } else if (v instanceof ViewGroup) {
                Checkable result = getCheckable((ViewGroup)v);
                if (result != null) {
                    return result;
                }
            }
        }
        return null;
    }

    @Override
    public void setChecked(boolean checked) {
        checkable.setChecked(checked);
    }

    @Override
    public boolean isChecked() {
        return checkable.isChecked();
    }

    @Override
    public void toggle() {
        checkable.toggle();
    }
}

icon_type_item.xml

<?xml version="1.0" encoding="utf-8"?>
<com.utils.ui.widget.CheckableLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                                                          android:layout_width="match_parent"
                                                          android:layout_height="wrap_content"
                                                          android:orientation="horizontal">
    <TextView android:id="@+id/text"
              android:layout_width="0dp"
              android:layout_height="wrap_content"
              android:layout_weight="1"
              android:focusable="false"
              android:focusableInTouchMode="false"/>
    <RadioButton android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:clickable="false"
                 android:focusable="false"
                 android:focusableInTouchMode="false"/>
</com.utils.ui.widget.CheckableLinearLayout>

Added to settings.xml

<com.utils.ui.preference.IconTypePreference
        android:key="icon_type"
        android:defaultValue="type_b"
        android:title="@string/icon_type_preference_title"/>

EDIT

There is a bug in CheckableLinearLayout.java

Replace the getCheckable method with this:

private Checkable getCheckable(ViewGroup viewGroup) {
    View v;
    int childCount = viewGroup.getChildCount();
    for (int i = 0; i < childCount; ++i) {
        v = viewGroup.getChildAt(i);
        if (v instanceof Checkable) {
            return (Checkable)v;
        } else if (v instanceof ViewGroup) {
            Checkable result = getCheckable((ViewGroup)v);
            if (result != null) {
                return result;
            }
        }
    }
    return null;
}
Community
  • 1
  • 1
Hanan
  • 459
  • 1
  • 5
  • 16

1 Answers1

0

Found the solution to the problem.

The problem was in the getView method of the adapter: I changed

convertView.setClickable(true);

to

convertView.setClickable(false);
Hanan
  • 459
  • 1
  • 5
  • 16