Custom Preference shows differently on Preference screen than native Preferences

℡╲_俬逩灬. 提交于 2019-12-13 03:43:05

问题


A picture is worth a thousand words, this is my problem:

The last three preferences are a custom time picker for minutes and seconds The other settings are the normal SwitchPreference, RingTonePreference, ListPreference and EditTextPreference

This is my preferences XML

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">

    <PreferenceCategory android:title="@string/pref_header_general">

        <SwitchPreference
            android:defaultValue="true"
            android:key="@string/pref_key_turbo"
            android:summaryOff="@string/pref_summaryOff_turbo"
            android:summaryOn="@string/pref_summaryOn_turbo"
            android:title="@string/pref_title_turbo" />

        <ListPreference
            android:defaultValue="1"
            android:entries="@array/pref_distance_units_list_titles"
            android:entryValues="@array/pref_distance_units_list_values"
            android:key="@string/pref_key_distance_units"
            android:negativeButtonText="@null"
            android:positiveButtonText="@null"
            android:title="@string/pref_title_distance_units" />

    </PreferenceCategory>

    <PreferenceCategory android:title="@string/pref_header_advanced">

        <EditTextPreference
            android:defaultValue="100"
            android:dialogMessage="@string/pref_distance_dialog_msg"
            android:dialogTitle="@string/pref_distance_dialog_title"
            android:inputType="number|numberDecimal"
            android:key="@string/pref_key_distance"
            android:summary="@string/pref_summary_distance"
            android:title="@string/pref_title_distance" />

        <!-- Allows the user to choose a ringtone -->
        <RingtonePreference
            android:defaultValue="content://settings/system/notification_sound"
            android:key="@string/pref_key_default_ringtone"
            android:ringtoneType="notification"
            android:showDefault="true"
            android:showSilent="true"
            android:title="@string/pref_title_default_ringtone" />

        <com.test.birenbaum.TimePickerPreference
            android:defaultValue="@string/pref_default_first"
            android:dialogMessage="@string/pref_default_first_dialog_msg"
            android:dialogTitle="@string/pref_default_first_dialog_title"
            android:key="@string/pref_key_default_first"
            android:summary="@string/pref_summary_default_first"
            android:title="@string/pref_title_default_first" />

        <com.test.birenbaum.TimePickerPreference
            android:defaultValue="@string/pref_default_value_retry"
            android:dialogMessage="@string/pref_default_retry_dialog_msg"
            android:dialogTitle="@string/pref_default_retry_dialog_title"
            android:key="@string/pref_key_default_retry"
            android:summary="@string/pref_summary_default_retry"
            android:title="@string/pref_title_default_retry" />

        <com.test.birenbaum.TimePickerPreference
            android:defaultValue="@string/pref_default_value_off"
            android:dialogMessage="@string/pref_default_off_dialog_msg"
            android:dialogTitle="@string/pref_default_off_dialog_title"
            android:key="@string/pref_key_default_off"
            android:summary="@string/pref_summary_default_off"
            android:title="@string/pref_title_default_off" />

        <ListPreference
            android:defaultValue="1"
            android:entries="@array/pref_default_algo_list_titles"
            android:entryValues="@array/pref_default_algo_list_values"
            android:key="@string/pref_key_default_algo_mode"
            android:negativeButtonText="@null"
            android:positiveButtonText="@null"
            android:summary="@string/pref_summary_default_algo"
            android:title="@string/pref_title_default_algo_mode" />
    </PreferenceCategory>
</PreferenceScreen>

I don't put here the TimePickerPreference code because it is long.
I don't set the preference layout anywhere in the code, so I expected to be presented the same as the built-in preferences, but as can be seen in the picture above, it is different.

Any ideas on why the custom preference item is presented different from the regular preferences?

MORE INFO, as requested

public class TimePickerPreference extends DialogPreference {
    private static final String TAG = "TimePickerPreference";
    public static boolean DEBUG = true;

    private static final String DEFAULT_VALUE = "0m0s";
    private static final String DEFAULT_SUMMARY = "%s";
    private static final String SPLIT_REGEX = "m|s";
    private static final String MATCH_REGEX = "\\d+m[0-5]?\\ds";
    private static final String TIME_FORMAT = "%dm%ds";

    private static final int MAX_MINUTES = 30;
    private static final int MIN_MINUTES = 0;
    private static final int MAX_SECONDS = 59;
    private static final int MIN_SECONDS = 0;

    private int mSeconds;
    private int mMinutes;

    private NumberPicker mSecondsPicker;
    private NumberPicker mMinutesPicker;

    private String mDefaultValue;
    private String mSummary;
    private String mSummaryFormat;


    public TimePickerPreference(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        MyLog.pe(DEBUG, TAG, "+ Constructor TimePickerPreference(context:%s, attrs:%s, defStyleAttr:%d)", context, attrs, defStyleAttr);

        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.TimePickerPreference, 0, 0);

        setTitle(a.getString(R.styleable.TimePickerPreference_android_title));
        if (getTitle() == null) {
            setTitle(TimePickerPreference.class.getSimpleName());
        }

        mSummary = a.getString(R.styleable.TimePickerPreference_android_summary);
        if (mSummary == null) {
            mSummary = DEFAULT_SUMMARY;
        }
        setSummary(mSummary);
        // At this stage the summary is virgin, still in skeleton format (with %s)
        setSummaryFormat(mSummary);

        mDefaultValue = a.getString(R.styleable.TimePickerPreference_android_defaultValue);
        if (mDefaultValue == null) {
            mDefaultValue = DEFAULT_VALUE;
        }

        setDefaultValue(mDefaultValue);

        a.recycle();

        setDialogLayoutResource(R.layout.preference_dialog_timepicker);
        setPositiveButtonText(R.string.save_button);
        setNegativeButtonText(android.R.string.cancel);

        MyLog.px(DEBUG, TAG, "- Constructor TimePickerPreference()");
    }

    public TimePickerPreference(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TimePickerPreference(Context context) {
        this(context, null, 0);
    }

    @Override
    protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
        MyLog.pe(DEBUG, TAG, "+ onSetInitialValue(restorePersistedValue:%s, defaultValue:%s)", restorePersistedValue, defaultValue);
        String[] time;
        if (restorePersistedValue) {
            time = getPersistedString((String) defaultValue).split(SPLIT_REGEX);
            // Assume persisted value is kosher
            mMinutes = Integer.valueOf(time[0]);
            mSeconds = Integer.valueOf(time[1]);

        } else {
            time = ((String) defaultValue).split(SPLIT_REGEX);
            if (time.length == 2) {
                // Enforce MIN-MAX boundaries
                mMinutes = Math.max(MIN_MINUTES, Math.min(MAX_MINUTES, (Integer.valueOf(time[0]))));
                mSeconds = Math.max(MIN_SECONDS, Math.min(MAX_SECONDS, (Integer.valueOf(time[1]))));
                defaultValue = getTime();
                persistString((String) defaultValue);
            } else {
                // Picker system default value, definitely kosher
                time = DEFAULT_VALUE.split(SPLIT_REGEX);
                mMinutes = Integer.valueOf(time[0]);
                mSeconds = Integer.valueOf(time[1]);
                persistString(DEFAULT_VALUE);
            }
        }
        MyLog.px(DEBUG, TAG, "- onSetInitialValue()");
    }

    @Override
    protected Object onGetDefaultValue(TypedArray a, int index) {
        MyLog.pe(DEBUG, TAG, "* onGetDefaultValue(a:%s, index:%s)", a, index);
        return a.getString(index);
    }

//    @Override
//    protected View onCreateDialogView() {
//        return super.onCreateDialogView();
//    }

    @Override
    protected void onBindDialogView(View view) {
        MyLog.pe(DEBUG, TAG, "+ onBindDialogView(view:%s)", view);
        super.onBindDialogView(view);

        TextView tvMessage = view.findViewById(R.id.tvMessage);
        String message = (String) getDialogMessage();
        if (message == null || message.isEmpty()) {
            tvMessage.setVisibility(View.GONE);
        } else {
            tvMessage.setText(message);
        }

        mMinutesPicker = view.findViewById(R.id.minutesPicker);
        mMinutesPicker.setMaxValue(MAX_MINUTES);
        mMinutesPicker.setMinValue(MIN_MINUTES);

        mSecondsPicker = view.findViewById(R.id.secondsPicker);
        mSecondsPicker.setMaxValue(MAX_SECONDS);
        mSecondsPicker.setMinValue(MIN_SECONDS);

        mMinutesPicker.setValue(mMinutes);
        mSecondsPicker.setValue(mSeconds);


        MyLog.px(DEBUG, TAG, "- onBindDialogView()");
    }

    @Override
    protected void onDialogClosed(boolean positiveResult) {
        MyLog.pe(DEBUG, TAG, "+ onDialogClosed(positiveResult:%s)", positiveResult);
        super.onDialogClosed(positiveResult);

        if (positiveResult) {
            String newValue = new StringBuilder()
                    .append(mMinutesPicker.getValue())
                    .append('m')
                    .append(mSecondsPicker.getValue())
                    .append('s')
                    .toString();
            if (callChangeListener(newValue)) {
                //noinspection ConstantConditions
                setTime(newValue);
            }
        }
        MyLog.px(DEBUG, TAG, "- onDialogClosed()");
        super.onDialogClosed(positiveResult);
    }

    @Override
    public void setDefaultValue(Object defaultValue) {
        MyLog.pe(DEBUG, TAG, "* setDefaultValue(defaultValue:%s)", defaultValue);
        super.setDefaultValue(defaultValue);
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        MyLog.pe(DEBUG, TAG, "+ onRestoreInstanceState(state:%s)",state);
        if (state == null || !state.getClass().equals(SavedState.class)) {
            // Didn't save state for us in onSaveInstanceState
            super.onRestoreInstanceState(state);
        }
        MyLog.px(DEBUG, TAG, "- onRestoreInstanceState()");
    }

    /**
     * Save the instance state so that it will survive events like
     * screen orientation change that may temporarily destroy it.
     */
    @Override
    protected Parcelable onSaveInstanceState() {
        MyLog.pe(DEBUG, TAG, "+ onSaveInstanceState()");
        final Parcelable superState = super.onSaveInstanceState();
        Parcelable result;
        if (isPersistent()) {
            // No need to save instance state since it's persistent
            result = superState;
        } else {
            final SavedState myState = new SavedState(superState);
            myState.seconds = mSeconds;
            myState.minutes = mMinutes;
            result = myState;
        }
        MyLog.px(DEBUG, TAG, "- onSaveInstanceState()");
        return result;
    }

    //----------------------------------------------------------------------------

    public int getSeconds() {
        return mSeconds;
    }

    public void setSeconds(int seconds) {
        mSeconds = seconds;
    }

    public int getMinutes() {
        return mMinutes;
    }

    public void setMinutes(int minutes) {
        mMinutes = minutes;
    }

    public String getSummaryFormat() {
        return mSummaryFormat;
    }

    public void setSummaryFormat(String summaryFormat) {
        mSummaryFormat = summaryFormat;
    }

    @SuppressLint("DefaultLocale")
    public String getTime() {
        return String.format(TIME_FORMAT, mMinutes, mSeconds);
    }

    /**
     * Saves the value to the {@link android.content.SharedPreferences SharedPreferences}.
     *
     * @param newTime A value to save. Must be in the correct format otherwise the save
     *                operation is not executed
     */
    public void setTime(String newTime) {
        MyLog.pe(DEBUG, TAG, "+ setTime(newTime:%s)", newTime);
        final boolean wasBlocking = shouldDisableDependents();

        if (newTime.matches(MATCH_REGEX)) {
            String[] time = newTime.split(SPLIT_REGEX);
            // No need to check for MAX and MIN values
            // The values come from the spinners, therefore are within boundaries
            mMinutes = Integer.valueOf(time[0]);
            mSeconds = Integer.valueOf(time[1]);
            persistString(newTime);
            notifyChanged();
        }

        final boolean isBlocking = shouldDisableDependents();
        if (isBlocking != wasBlocking) {
            notifyDependencyChange(isBlocking);
        }
        MyLog.px(DEBUG, TAG, "- setTime()");
    }

    @Override
    public String toString() {
        return getTime();
    }

//----------------------------------------------------------------------------

    /**
     * Returns the summary of this TimePickerPreference. If the summary
     * has a {@linkplain java.lang.String#format String formatting}
     * marker in it (i.e. "%s" or "%1$s"), then the current minutes and seconds
     * value will be substituted in its place.
     *
     * @return the summary with appropriate string substitution
     */
    @Override
    public CharSequence getSummary() {
        MyLog.pe(DEBUG, TAG, "+ getSummary()");
        CharSequence result;
        if (mSummary == null) {
            result = super.getSummary();
        } else {
            result = String.format(mSummary, getTime());
        }
        MyLog.px(DEBUG, TAG, "- getSummary()");
        return result;
    }

    /**
     * Sets the summary for this Preference with a CharSequence.
     * If the summary has a {@linkplain java.lang.String#format String formatting}
     * marker in it (i.e. "%s" or "%1$s"), then the current entry value will be substituted
     * in its place when it's retrieved.
     *
     * @param summary The summary for the preference.
     */
    @Override
    public void setSummary(CharSequence summary) {
        MyLog.pe(DEBUG, TAG, "+ setSummary(summary:%s)", summary);
        super.setSummary(summary);
        if (summary == null && mSummary != null) {
            mSummary = null;
        } else if (summary != null && !summary.equals(mSummary)) {
            mSummary = summary.toString();
        }
        MyLog.px(DEBUG, TAG, "- setSummary()");
    }


    //----------------------------------------------------------------------------

    private static class SavedState extends BaseSavedState {
        int seconds;
        int minutes;

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

        public SavedState(Parcel source) {
            super(source);
            MyLog.pe(DEBUG, TAG, "+ SavedState(source:%s)", source);

            seconds = source.readInt();
            minutes = source.readInt();

            MyLog.px(DEBUG, TAG, "- SavedState()");
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            MyLog.pe(DEBUG, TAG, "+ writeToParcel(dest:%s, flags:%s)", dest, flags);
            super.writeToParcel(dest, flags);

            dest.writeInt(seconds);
            dest.writeInt(minutes);

            MyLog.px(DEBUG, TAG, "- writeToParcel()");
        }

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

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

回答1:


The problem was/is in the third argument (defStyleAtttr) to the preference constructor.

It is an integer, but 0 is not the default value, com.android.internal.R.attr.dialogPreferenceStyle is.

So I deleted my third constructor; and let the base class DialogPreference deal with the defStylAttr value. And now it shows teh same style as the other built-in preferences.



来源:https://stackoverflow.com/questions/46421525/custom-preference-shows-differently-on-preference-screen-than-native-preferences

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!