Как исправить DatePickerDialog в режиме счетчика для Android 7.0?

В настоящее время я разрабатываю простой проект, состоящий из веб-сайта, заключенного в веб-представление с небольшими взаимодействиями для улучшения взаимодействия между самим сайтом и мобильными устройствами 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 вызывается непосредственно из веб-представления, а не из самого приложения, делает эти решения бесполезными для меня? И если да, то есть ли какое-то решение для этого?

Почему вам нужен спиннер, чтобы было легче выбрать год в далеком прошлом (удобно для дней рождения)? Если это так, вы также можете принудительно запустить средство выбора даты сначала в режиме выбора года (это сработало для меня, поскольку, как вы отметили, принудительное использование счетчика в API 24, похоже, не работает)

Tyler V 20.07.2018 21:01

@TylerV Похоже, хорошая альтернатива. На самом деле я не подозревал, что это возможно. Есть ли у вас какие-нибудь шаги, которым я мог бы следовать, чтобы этого добиться? Также спасибо за быстрый ответ.

Oranakia 20.07.2018 21:13

Конечно, я опубликую ответ по этому поводу.

Tyler V 20.07.2018 21:17
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
4
3
4 174
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Я столкнулся с той же проблемой (пользователи не хотят прокручивать назад на 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 все сломает?

Oranakia 20.07.2018 22:41

Кроме того, я не знал, что вы также можете нажать на год, поэтому в худшем случае я просто предлагал пользователям это сделать. Что на лиги опережает то, что было у меня.

Oranakia 20.07.2018 22:41

Вы переместите содержимое onClick туда, откуда вы запускаете DatePicker. Материал в birthdayListener (в методе onDateSet) будет обрабатывать, что делать, когда пользователь выбирает дату в средстве выбора даты.

Tyler V 20.07.2018 22:42

Для всех, кто заинтересован в правильной работе DatePicker из WebView без добавления JS-интерфейса для ручного вызова собственного DatePicker, вот решение, которое может переопределить ВСЕ DatePicker в вашем приложении:

https://github.com/Noisyfox/DatePickerForceSpinner

Это исправление также применимо к любому DatePicker, созданному LayoutInflater, поэтому вам не нужно беспокоиться об изменении существующих файлов макета.

Другие вопросы по теме