具有自定义本机(不兼容)对话框首选项的应用程序兼容性,其中包含TimePicker [英] Appcompatactivity with custom native (not compatibility) dialogpreference containing a TimePicker
问题描述
我正在Android AppCompatActivity
中构建首选项/设置屏幕.一个要求是带有TimePicker
的自定义[DialogPreference][1]
.
DialogPreference必须为"native",而不是此处所述的兼容版本. >和此处.
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类:
...
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类:
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" />
结果类似于下面的屏幕截图.在装有Android 7的Moto G5 plus手机上.
问题: 应该有两个首选项.但是,自定义DialogPreference不会显示在设置列表中. 这是怎么了? AppCompatActivity是否可以与本机" DialogPreference一起使用?
TimePreference类实际上是从首选项xml实例化的,可以从构造函数中记录它.也没有编译时错误,没有运行时错误.
最后,我发现了另一种方法,该方法看起来干净,经过测试并且可以在Android 4之前的7的实际设备上运行.首选项"屏幕上显示了首选项".此外,"TimePicker"对话框也可以横向显示.这在某些设备上是个问题.见
步骤是:
- 在首选项xml文件中包含常规
Preference
项 - 使用onPreferenceTreeClick()在此首选项上设置点击侦听器
- 单击此首选项后,显示一个常规(不兼容的库)
TimePickerDialog
就像选择器"指南中所述 ( https://developer.android.com/reference/android/app/TimePickerDialog.html ) - 在SharedPreferences中手动保存在TimePicker上设置的时间
首选项活动:
...
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类:
...
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>
TimePickerFragment类,请参见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);
}
}
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.
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);
}
}
这篇关于具有自定义本机(不兼容)对话框首选项的应用程序兼容性,其中包含TimePicker的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!