问题
I am building a preferences / settings screen inside an Android AppCompatActivity
. One requirement is a custom [DialogPreference][1]
with a TimePicker
.
The DialogPreference must be 'native', meaning not the compatibility version like described here and here.
The code for the AppCompatActivity:
...
public class SettingsActivity extends AppCompatActivity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_preferences);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar_settings);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
}
The layout of activity_preferences.xml:
...
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<fragment
android:name="nl.waywayway.broodkruimels.SettingsFragment"
android:id="@+id/settings_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v4.widget.NestedScrollView>
The SettingsFragment class:
...
public class SettingsFragment extends PreferenceFragment
{
Context mContext;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
}
The preferences.xml file:
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SwitchPreference
android:key="pref_notify"
android:title="@string/pref_notify"
android:summary="@string/pref_notify_summ"
android:defaultValue="false" />
<nl.waywayway.broodkruimels.TimePreference
android:dependency="pref_notify"
android:key="pref_notify_time"
android:title="@string/pref_notify_time"
android:summary="@string/pref_notify_time_summ"
android:defaultValue="390" />
</PreferenceScreen>
And the custom TimePreference class:
public class TimePreference extends DialogPreference
{
private TimePicker mTimePicker = null;
private int mTime;
private int mDialogLayoutResId = R.layout.preferences_timepicker_dialog;
// 4 constructors for the API levels,
// calling each other
public TimePreference(Context context)
{
this(context, null);
}
public TimePreference(Context context, AttributeSet attrs)
{
this(context, attrs, R.attr.preferenceStyle);
}
public TimePreference(Context context, AttributeSet attrs, int defStyleAttr)
{
this(context, attrs, defStyleAttr, defStyleAttr);
}
public TimePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
{
super(context, attrs, defStyleAttr, defStyleRes);
}
public int getTime()
{
return mTime;
}
public void setTime(int time)
{
mTime = time;
// Save to Shared Preferences
persistInt(time);
}
@Override
public int getDialogLayoutResource()
{
return mDialogLayoutResId;
}
@Override
protected Object onGetDefaultValue(TypedArray a, int index)
{
// Default value from attribute. Fallback value is set to 0.
return a.getInt(index, 0);
}
@Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue)
{
// Read the value. Use the default value if it is not possible.
setTime(restorePersistedValue ?
getPersistedInt(mTime) : (int) defaultValue);
}
@Override
protected void onBindDialogView(View view)
{
super.onBindDialogView(view);
mTimePicker = (TimePicker) view.findViewById(R.id.preferences_timepicker);
if (mTimePicker == null)
{
throw new IllegalStateException("Dialog view must contain a TimePicker with id 'preferences_timepicker'");
}
// Get the time from the related Preference
Integer minutesAfterMidnight = null;
TimePreference preference = (TimePreference) findPreferenceInHierarchy("pref_notify_time");
minutesAfterMidnight = preference.getTime();
// Set the time to the TimePicker
if (minutesAfterMidnight != null)
{
int hours = minutesAfterMidnight / 60;
int minutes = minutesAfterMidnight % 60;
boolean is24hour = DateFormat.is24HourFormat(getContext());
mTimePicker.setIs24HourView(is24hour);
if (Build.VERSION.SDK_INT >= 23)
{
mTimePicker.setHour(hours);
mTimePicker.setMinute(minutes);
}
else
{
mTimePicker.setCurrentHour(hours);
mTimePicker.setCurrentMinute(minutes);
}
}
}
@Override
protected void onDialogClosed(boolean positiveResult)
{
if (positiveResult)
{
// Get the current values from the TimePicker
int hours;
int minutes;
if (Build.VERSION.SDK_INT >= 23)
{
hours = mTimePicker.getHour();
minutes = mTimePicker.getMinute();
}
else
{
hours = mTimePicker.getCurrentHour();
minutes = mTimePicker.getCurrentMinute();
}
// Generate value to save
int minutesAfterMidnight = (hours * 60) + minutes;
// Save the value
TimePreference timePreference = (TimePreference) findPreferenceInHierarchy("pref_notify_time");
// This allows the client to ignore the user value.
if (timePreference.callChangeListener(minutesAfterMidnight))
{
// Save the value
timePreference.setTime(minutesAfterMidnight);
}
}
}
}
The preferences_timepicker_dialog.xml file is as follows:
...
<TimePicker
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/preferences_timepicker"
android:layout_width="match_parent"
android:layout_height="match_parent" />
The result is like the screenshot below. On a Moto G5 plus phone with Android 7.
Question: There should be two preferences. However, the custom DialogPreference is not showing in the settings list. What is going wrong here? Does the AppCompatActivity actually work with the 'native' DialogPreference?
The TimePreference class is actually instantiated from the preferences xml, that could be logged from the constructor. Also no compile time errors, no runtime errors.
回答1:
Finally I found a different approach that looks clean, tested and works on real devices from Android 4 until 7. The Preference is showing in the Preference screen.
Also, the TimePicker dialog is properly showing in landscape orientation. This is a problem on some devices. See
- Android TimePicker not displayed well on landscape mode
- TimePickerDialog widget in landscape mode (PreferenceScreen)
Steps are:
- Include a general
Preference
item in the preferences xml file - Set a click listener on this Preference using onPreferenceTreeClick()
- When this Preference is clicked, show a regular (not compatibility library) TimePickerDialog like described in the 'Pickers' guide (https://developer.android.com/reference/android/app/TimePickerDialog.html)
- Save the time set on the TimePicker manually in the SharedPreferences
The preferences Activity:
...
public class SettingsActivity extends AppCompatActivity
{
public static final String KEY_PREF_NOTIFY = "pref_notify";
public static final String KEY_PREF_NOTIFY_TIME = "pref_notify_time";
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_preferences);
}
}
The layout file activity_preferences.xml:
...
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<fragment
android:name="nl.waywayway.broodkruimels.SettingsFragment"
android:id="@+id/settings_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v4.widget.NestedScrollView>
The SettingsFragment class:
...
public class SettingsFragment extends PreferenceFragmentCompat
{
Context mContext;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey)
{
setPreferencesFromResource(R.xml.preferences, rootKey);
}
// The Context object of this fragment is only available when this fragment is 'attached', so set the Context object inside the onAttach() method
@Override
public void onAttach(Context context)
{
super.onAttach(context);
mContext = context;
}
// This method sets the action of clicking the Preference
@Override
public boolean onPreferenceTreeClick(Preference preference)
{
switch (preference.getKey())
{
case SettingsActivity.KEY_PREF_NOTIFY_TIME:
showTimePickerDialog(preference);
break;
}
return super.onPreferenceTreeClick(preference);
}
private void showTimePickerDialog(Preference preference)
{
DialogFragment newFragment = new TimePickerFragment();
newFragment.show(getFragmentManager(), "timePicker");
}
}
The preferences.xml file:
...
<android.support.v7.preference.PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.preference.SwitchPreferenceCompat
android:key="pref_notify"
android:title="@string/pref_notify"
android:summary="@string/pref_notify_summ"
android:defaultValue="false" />
<android.support.v7.preference.Preference
android:dependency="pref_notify"
android:key="pref_notify_time"
android:title="@string/pref_notify_time"
android:summary="@string/pref_notify_time_summ"
android:defaultValue="390" />
</android.support.v7.preference.PreferenceScreen>
The TimePickerFragment class, see the Android 'Pickers' guide (https://developer.android.com/guide/topics/ui/controls/pickers.html) for an explanation:
...
public class TimePickerFragment extends DialogFragment
implements TimePickerDialog.OnTimeSetListener
{
private Context mContext;
private int mTime; // The time in minutes after midnight
// The Context object for this fragment is only available when this fragment is 'attached', so set the Context object inside the onAttach() method
@Override
public void onAttach(Context context)
{
super.onAttach(context);
mContext = context;
}
// Getter and setter for the time
public int getTime()
{
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(mContext);
int prefDefault = mContext.getResources().getInteger(R.integer.preferences_time_default);
mTime = sharedPref.getInt(SettingsActivity.KEY_PREF_NOTIFY_TIME, prefDefault);
return mTime;
}
public void setTime(int time)
{
mTime = time;
// Save to Shared Preferences
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(mContext);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(SettingsActivity.KEY_PREF_NOTIFY_TIME, time);
editor.commit();
}
public Dialog onCreateDialog(Bundle savedInstanceState)
{
int minutesAfterMidnight = getTime();
int hour = minutesAfterMidnight / 60;
int minute = minutesAfterMidnight % 60;
Log.i("HermLog", "onCreateDialog(), tijd: " + hour + ":" + minute);
// Create a new instance of TimePickerDialog and return it
return new TimePickerDialog(
mContext,
this,
hour,
minute,
DateFormat.is24HourFormat(mContext)
);
}
@Override
public void onTimeSet(TimePicker view, int hour, int minute)
{
int minutesAfterMidnight = (hour * 60) + minute;
setTime(minutesAfterMidnight);
}
}
来源:https://stackoverflow.com/questions/44943048/appcompatactivity-with-custom-native-not-compatibility-dialogpreference-contai