ListPreference 的自定义布局

Custom layout for ListPreference

我正在使用 PreferenceActivity 来设置我的应用程序。 我想添加一个新的首选项,允许用户 select 一个图标。 对于此任务,我想使用 ListPreference,但我还想在列表中显示图标。

我尝试自定义 ListPreference 以使用自定义布局,但问题是一旦我这样做,列表项就不可点击(它确实显示了我的自定义布局和使用当前 selection 的默认值)。

我在不同的模拟器版本和 Galaxy S2 上测试了它。按下该项目时,我可以看到 pressed/unpressed 的一些效果,但未调用 onClick 方法。

我按照 Android: Checkable Linear Layout for adding custom layout (I also tried the option describe in How to customize list preference radio button 上的说明进行操作,但结果相同。

IconTypePreference.java(从ListPreference复制并修改):

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>

添加到 settings.xml

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

编辑

CheckableLinearLayout.java

中存在错误

getCheckable 方法替换为:

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

找到问题的解决方案。

问题出在适配器的 getView 方法中: 我变了

convertView.setClickable(true);

convertView.setClickable(false);