具有包含 TimePicker 的自定义本机(非兼容性)对话框首选项的 Appcompatactivity
Appcompatactivity with custom native (not compatibility) dialogpreference containing a TimePicker
我正在 Android AppCompatActivity
中构建首选项/设置屏幕。一个要求是带有 TimePicker
.
的自定义 [DialogPreference][1]
DialogPreference 必须是 'native',这意味着不是 here and here 描述的兼容版本。
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);
}
}
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>
SettingsFragment class:
...
public class SettingsFragment extends PreferenceFragment
{
Context mContext;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
}
preferences.xml 文件:
<?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>
和自定义 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);
}
}
}
}
preferences_timepicker_dialog.xml文件如下:
...
<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" />
结果如下图所示。在 Moto G5 plus phone 上 Android 7.
问题:
应该有两个偏好。但是,自定义 DialogPreference 未显示在设置列表中。
这里出了什么问题?
AppCompatActivity 是否真的与 'native' DialogPreference 一起工作?
TimePreference class 实际上是从首选项 xml 中实例化的,可以从构造函数中记录。也没有编译时错误,没有运行时错误。
最后我找到了一种不同的方法,它看起来很干净,经过测试并且可以在 Android 4 到 7 的真实设备上运行。首选项显示在首选项屏幕中。
此外,TimePicker 对话框可以正确地横向显示。这是某些设备上的问题。参见
- Android TimePicker not displayed well on landscape mode
- TimePickerDialog widget in landscape mode (PreferenceScreen)
步骤是:
- 在首选项 xml 文件
中包含常规 Preference
项
- 使用 onPreferenceTreeClick() 在此首选项上设置点击监听器
- 点击此首选项时,显示常规(非兼容库)
TimePickerDialog
就像 'Pickers' 指南中描述的那样
(https://developer.android.com/reference/android/app/TimePickerDialog.html)
- 在 SharedPreferences 中手动保存 TimePicker 上设置的时间
首选项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);
}
}
布局文件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>
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");
}
}
preferences.xml 文件:
...
<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>
TimePickerFragmentclass,参见Android'Pickers'指南(https://developer.android.com/guide/topics/ui/controls/pickers.html)的解释:
...
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);
}
}
我正在 Android AppCompatActivity
中构建首选项/设置屏幕。一个要求是带有 TimePicker
.
[DialogPreference][1]
DialogPreference 必须是 'native',这意味着不是 here and here 描述的兼容版本。
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);
}
}
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>
SettingsFragment class:
...
public class SettingsFragment extends PreferenceFragment
{
Context mContext;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
}
preferences.xml 文件:
<?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>
和自定义 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);
}
}
}
}
preferences_timepicker_dialog.xml文件如下:
...
<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" />
结果如下图所示。在 Moto G5 plus phone 上 Android 7.
问题: 应该有两个偏好。但是,自定义 DialogPreference 未显示在设置列表中。 这里出了什么问题? AppCompatActivity 是否真的与 'native' DialogPreference 一起工作?
TimePreference class 实际上是从首选项 xml 中实例化的,可以从构造函数中记录。也没有编译时错误,没有运行时错误。
最后我找到了一种不同的方法,它看起来很干净,经过测试并且可以在 Android 4 到 7 的真实设备上运行。首选项显示在首选项屏幕中。
此外,TimePicker 对话框可以正确地横向显示。这是某些设备上的问题。参见
- Android TimePicker not displayed well on landscape mode
- TimePickerDialog widget in landscape mode (PreferenceScreen)
步骤是:
- 在首选项 xml 文件 中包含常规
- 使用 onPreferenceTreeClick() 在此首选项上设置点击监听器
- 点击此首选项时,显示常规(非兼容库)
TimePickerDialog
就像 'Pickers' 指南中描述的那样 (https://developer.android.com/reference/android/app/TimePickerDialog.html) - 在 SharedPreferences 中手动保存 TimePicker 上设置的时间
Preference
项
首选项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);
}
}
布局文件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>
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");
}
}
preferences.xml 文件:
...
<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>
TimePickerFragmentclass,参见Android'Pickers'指南(https://developer.android.com/guide/topics/ui/controls/pickers.html)的解释:
...
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);
}
}