How to fix DatePickerDialog in spinner mode for Android 7.0?

喜欢而已 提交于 2020-02-02 06:16:15

问题


I’m currently developing a simple project consisting of a website wrapped in a web view with minor interactions to improve interactivity between the site itself and android mobile devices.

Since the website includes a date input field for the user’s birthdate, I was looking to implement a datepicker in spinner format compatible with all devices. I tried implementing the following solution:

`<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>`

As shown in: Datepicker dialog without calendar visualization in lollipop [spinner mode]?

However, as the answer points out, this solution does not work with Android 7.0 Nougat / API 24 due to the following known bug: https://issuetracker.google.com/issues/37119315

Doing some research I came across some proposed solutions such as:

https://gist.github.com/uqmessias/d9a8dc624af935f344dfa2e8928490ec

https://gist.github.com/lognaturel/232395ee1079ff9e4b1b8e7096c3afaf

DatePickerDialog Holo styling failed on Android 7 Nougat

However in my case, none of them seem to work either for devices with Android 7.0, or any other devices.

I am heavily inexperienced in android development so I was wondering what could be causing this and how could I solve it, as some of the solutions have been maintained up to recently, which pretty much discards that they could have become obsolete.

Do I need to implement something that hasn’t been pointed out in the code I posted and I am not aware of?

Is it the fact that the datepicker element is being called directly from the web view and not from the app itself that renders these solutions useless to me? And if so, is there any work around for this?


回答1:


I ran into the same issue (users don't want to scroll back in time 40 years month by month to find their birth year and most don't know you can just click on the year in the android date picker to scroll through years). Like you, I couldn't get the spinner to work universally, so I figured out (with help from SO and Google) how to make it start in year selection mode.

The code for my DatePickerDialogFragment is pasted below.

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;
    }
}

The code in the Activity (member variables and stuff in onCreate) to launch this (and preserve it on rotation) looks like this:

// 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");
    }
});

This includes both the hack to get it to start in year mode, and a fix for some random date picker failures on Samsung devices of a certain vintage. This version has been working without crashes or user complaints for API 15+ for a few months now.

EDIT: Updated openYearView to work on Android 10




回答2:


For anyone who's interested in making DatePicker from WebView working properly without adding a JS interface to manually call your own DatePicker, here is a solution that can override ALL DatePickers in your app:

https://github.com/Noisyfox/DatePickerForceSpinner

This fix also applies to any DatePicker created by LayoutInflater so you don't need to worry about changing your existing layout files.




回答3:


This will be displayed in a monospaced font. The first four spaces
will be stripped off, but all other whitespace will be preserved.
Markdown and HTML are turned off in code blocks:
<i>This is not italic</i>, and [this is not a link](https://example.com)

To create not a block, but an inline code span, use backticks:

The $ character is just a shortcut for window.jQuery. If you want to have a preformatted block within a list, indent by eight spaces:

  1. This is normal text.
  2. So is this, but now follows a code block:

    Skip a line and indent eight spaces.
    That's four spaces for the list
    and four to trigger the code block.
    


来源:https://stackoverflow.com/questions/51448575/how-to-fix-datepickerdialog-in-spinner-mode-for-android-7-0

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!