问题
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