В настоящее время я разрабатываю простой проект, состоящий из веб-сайта, заключенного в веб-представление с небольшими взаимодействиями для улучшения взаимодействия между самим сайтом и мобильными устройствами Android.
Поскольку на веб-сайте есть поле для ввода даты для даты рождения пользователя, я хотел реализовать средство выбора даты в формате счетчика, совместимое со всеми устройствами. Я попробовал реализовать следующее решение:
`<style name = "MyAppTheme" parent = "android:Theme.Material">
<item name = "android:dialogTheme">@style/MyDialogTheme</item>
<item name = "android:datePickerStyle">@style/MyDatePicker</item>
</style>
<style name = "MyDialogTheme" parent = "android:Theme.Material.Dialog">
<item name = "android:datePickerStyle">@style/MyDatePicker</item>
</style>
<style name = "MyDatePicker" parent = "android:Widget.Material.DatePicker">
<item name = "android:datePickerMode">spinner</item>
</style>`
Как показано в: Диалоговое окно Datepicker без визуализации календаря в леденце [режим счетчика]?
Однако, как указывает ответ, это решение не работает с Android 7.0 Nougat / API 24 из-за следующей известной ошибки: https://issuetracker.google.com/issues/37119315
Проведя небольшое исследование, я наткнулся на несколько предлагаемых решений, таких как:
https://gist.github.com/uqmessias/d9a8dc624af935f344dfa2e8928490ec
https://gist.github.com/lognaturel/232395ee1079ff9e4b1b8e7096c3afaf
Не удалось создать стиль DatePickerDialog Holo на Android 7 Nougat
Однако в моем случае ни один из них, похоже, не работает ни на устройствах с Android 7.0, ни на каких-либо других устройствах.
У меня очень мало опыта в разработке Android, поэтому мне было интересно, что может быть причиной этого и как я могу решить эту проблему, поскольку некоторые решения поддерживаются до недавнего времени, что в значительной степени отклоняет то, что они могли устареть.
Нужно ли мне реализовать что-то, что не было указано в опубликованном мной коде и о чем я не знаю?
Является ли тот факт, что элемент datepicker вызывается непосредственно из веб-представления, а не из самого приложения, делает эти решения бесполезными для меня? И если да, то есть ли какое-то решение для этого?
@TylerV Похоже, хорошая альтернатива. На самом деле я не подозревал, что это возможно. Есть ли у вас какие-нибудь шаги, которым я мог бы следовать, чтобы этого добиться? Также спасибо за быстрый ответ.
Конечно, я опубликую ответ по этому поводу.




Я столкнулся с той же проблемой (пользователи не хотят прокручивать назад на 40 лет месяц за месяцем, чтобы найти год своего рождения, и большинство из них не знают, что вы можете просто щелкнуть год в средстве выбора даты Android, чтобы прокрутить годы) . Как и вы, я не мог заставить счетчик работать универсально, поэтому я понял (с помощью SO и Google), как запустить его в режиме выбора года.
Код моего DatePickerDialogFragment вставлен ниже.
public class DatePickerDialogFragment extends DialogFragment {
private DatePickerDialog.OnDateSetListener listener = null;
void setListener(DatePickerDialog.OnDateSetListener listener) {
this.listener = listener;
}
private static final String START_IN_YEARS = "com.myapp.picker.START_IN_YEARS";
private static final String YEAR = "com.myapp.picker.YEAR";
private static final String MONTH = "com.myapp.picker.MONTH";
private static final String DAY_OF_MONTH = "com.myapp.picker.DAY_OF_MONTH";
public static DatePickerDialogFragment newInstance(boolean startInYears, Calendar c) {
DatePickerDialogFragment f = new DatePickerDialogFragment();
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH);
int day = c.get(Calendar.DAY_OF_MONTH);
Bundle args = new Bundle();
args.putBoolean(START_IN_YEARS, startInYears);
args.putInt(YEAR, year);
args.putInt(MONTH, month);
args.putInt(DAY_OF_MONTH, day);
f.setArguments(args);
return f;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Bundle args = getArguments();
DatePickerDialog dpd = null;
if ( listener != null && args != null) {
boolean startInYears = args.getBoolean(START_IN_YEARS);
Context context = getActivity();
boolean requireSpinnerMode = isBrokenSamsungDevice();
if (requireSpinnerMode) {
context = new ContextThemeWrapper(context, android.R.style.Theme_Holo_Light_Dialog);
}
int year = args.getInt(YEAR);
int month = args.getInt(MONTH);
int day = args.getInt(DAY_OF_MONTH);
dpd = new DatePickerDialog(context, listener, year, month, day);
if (startInYears && !requireSpinnerMode) {
boolean canOpenYearView = openYearView(dpd.getDatePicker());
if (!canOpenYearView) {
context = new ContextThemeWrapper(getActivity(), android.R.style.Theme_Holo_Light_Dialog);
dpd = new DatePickerDialog(context, listener, year, month, day);
}
}
}
else {
setShowsDialog(false);
dismissAllowingStateLoss();
}
return dpd;
}
private static boolean isBrokenSamsungDevice() {
return Build.MANUFACTURER.equalsIgnoreCase("samsung") &&
isBetweenAndroidVersions(
Build.VERSION_CODES.LOLLIPOP,
Build.VERSION_CODES.LOLLIPOP_MR1);
}
private static boolean isBetweenAndroidVersions(int min, int max) {
return Build.VERSION.SDK_INT >= min && Build.VERSION.SDK_INT <= max;
}
private static boolean openYearView(DatePicker datePicker) {
if ( isBrokenSamsungDevice() ) {
return false;
}
View v = datePicker.findViewById(Resources.getSystem().getIdentifier("date_picker_header_year", "id", "android"));
if ( v != null ) {
v.performClick();
}
else {
try {
Field mDelegateField = datePicker.getClass().getDeclaredField("mDelegate");
mDelegateField.setAccessible(true);
Object delegate = mDelegateField.get(datePicker);
Method setCurrentViewMethod = delegate.getClass().getDeclaredMethod("setCurrentView", int.class);
setCurrentViewMethod.setAccessible(true);
setCurrentViewMethod.invoke(delegate, 1);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
return true;
}
}
Код в Activity (переменные-члены и прочее в onCreate) для запуска этого (и сохранения его при вращении) выглядит следующим образом:
// Class member variables
private Calendar myCalendar = Calendar.getInstance();
private boolean birthday_is_set = false;
// this next part is in onCreate
// set the calendar date to a saved date if applicable
// and change birthday_is_set if they had saved a birthday
final DatePickerDialog.OnDateSetListener birthdayListener = new DatePickerDialog.OnDateSetListener() {
@Override
public void onDateSet(DatePicker view, int year, int monthOfYear,
int dayOfMonth) {
// I save the date in a calendar, replace this
// with whatever you want to do with the selected date
myCalendar.set(Calendar.YEAR, year);
myCalendar.set(Calendar.MONTH, monthOfYear);
myCalendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
birthday_is_set = true;
updateBirthdayLabel();
}
};
if (savedInstanceState != null) {
DatePickerDialogFragment dpf;
dpf = (DatePickerDialogFragment) getFragmentManager().findFragmentByTag("birthdayDatePicker");
if (dpf != null) {
// on rotation the listener will be referring to the old Activity,
// so we have to reset it here to act on the current Activity
dpf.setListener(birthdayListener);
}
}
birthdayDatePicker.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Your logic may vary here. I chose not to start it in year
// mode if they've already selected a date.
boolean startInYears = !birthday_is_set;
DatePickerDialogFragment dpf = DatePickerDialogFragment.newInstance(startInYears, myCalendar);
dpf.setListener(birthdayListener);
dpf.show(getFragmentManager(), "birthdayDatePicker");
}
});
Это включает в себя как взлом, чтобы заставить его запускаться в режиме года, так и исправление некоторых сбоев случайного выбора даты на устройствах Samsung определенного поколения. Эта версия уже несколько месяцев работает без сбоев и жалоб пользователей на API 15+.
Обновлено: обновлен openYearView для работы на Android 10.
Я заметил, что вы вызываете событие onClick для установки и вызова фрагмента. Учитывая, что я использую поле ввода из webView, у меня нет такого события. Могу ли я в этом случае выполнить вызов из функции в моем WebViewInterface? Или перемещение содержимого birthdayDatePicker.setOnClickListener все сломает?
Кроме того, я не знал, что вы также можете нажать на год, поэтому в худшем случае я просто предлагал пользователям это сделать. Что на лиги опережает то, что было у меня.
Вы переместите содержимое onClick туда, откуда вы запускаете DatePicker. Материал в birthdayListener (в методе onDateSet) будет обрабатывать, что делать, когда пользователь выбирает дату в средстве выбора даты.
Для всех, кто заинтересован в правильной работе DatePicker из WebView без добавления JS-интерфейса для ручного вызова собственного DatePicker, вот решение, которое может переопределить ВСЕ DatePicker в вашем приложении:
https://github.com/Noisyfox/DatePickerForceSpinner
Это исправление также применимо к любому DatePicker, созданному LayoutInflater, поэтому вам не нужно беспокоиться об изменении существующих файлов макета.
Почему вам нужен спиннер, чтобы было легче выбрать год в далеком прошлом (удобно для дней рождения)? Если это так, вы также можете принудительно запустить средство выбора даты сначала в режиме выбора года (это сработало для меня, поскольку, как вы отметили, принудительное использование счетчика в API 24, похоже, не работает)