web-dev-qa-db-ja.com

DatePickerにMinDateを設定すると、CalendarViewが1964に移動します

デフォルト以外の最小日付が設定されている場合、CalendarViewDatePickerが1964年10月に移動する問題をデバッグしています。これは少なくともAPI17Nexus7エミュレーターで再現されますが、プラットフォームスタイルにスピナーと日付ピッカーのカレンダービューの両方が含まれている他のデバイスでこの問題に関する報告があります。

この問題を示すコードスニペットは次のとおりです。

class DatePickerDialog1964 extends DatePickerDialog {
    DatePickerDialog1964(Context c) {
        super(c, null, 2013, 4, 21);

        @SuppressWarnings("deprecation")
        Date min = new Date(2013-1900, 4, 21);

        DatePicker p = getDatePicker();
        p.setMinDate(min.getTime());
    }
}

.。

new DatePickerDialog1964(context).show();

スクリーンショット:

October 1964 calendar view in date picker

もちろん、スピナーとカレンダービューに同じ日付が表示されることが期待されます。

これをデバッグし続けますが、SOユーザーが経験やその他の洞察を持っている場合は、共有してください。

関連:

16
laalto

TL; DR

1964年ではなく、2100のフォーマットが正しくありません。 CalendarViewにはバグがあります。 formatDateRange()は2038年以降は機能しません。

回避策#1

_class DatePickerDialog1964 extends DatePickerDialog {
    DatePickerDialog1964(Context c) {
        super(c, null, 2013, 4, 21);

        @SuppressWarnings("deprecation")
        Date min = new Date(2013-1900, 4, 21);

        DatePicker p = getDatePicker();
        CalendarView cv = p.getCalendarView(); // should check for null
        long cur = cv.getDate();
        int d = cv.getFirstDayOfWeek();
        p.setMinDate(min.getTime());
        cv.setDate(cur + 1000L*60*60*24*40);
        cv.setFirstDayOfWeek((d + 1) % 7);
        cv.setDate(cur);
        cv.setFirstDayOfWeek(d);
    }
}
_

実際に使用した回避策#2

_// Calendar view is a cascade of bugs.
// Work around that by explicitly disabling it.
datePicker.setCalendarViewShown(false);
_

分析

CalendarViewは、ヘッダーに月/年の名前を生成するためにAndroid.text.format.DateUtils.formatDateRange()を使用します。カレンダービューは、月番号が変更された場合にのみヘッダーを更新します。たとえば、月番号が同じで年が変更された場合、ヘッダーは更新されません。

レイアウトフェーズのどこかで、基になるListViewは、リストが最後までスクロールされたかのように、カレンダービューでOnScrollListenerを呼び出します。月番号が変更されると、ヘッダーは上記のテストコードで更新され、formatDateRange()に渡されるミリ秒値は_4131043200000_で、これは2100後半のどこかにあり、2100はDatePickerのデフォルトの最大値です。 。

formatDateRange()は、内部カレンダー表現として_Android.text.format.Time_を使用します。具体的には、set()メソッドを使用してミリ秒を設定します。基礎となるネイティブコードをチェックしませんでしたが、私の知識に基づく推測では、渡されたミリ秒の値は、固有の 2038年問題 で32ビットの_time_t_秒の値に切り捨てられます。 62年強の価値。 Time自体は1900年以降の年をintとして表す_struct tm_を使用しているため、1960年代にはTimeになり、ヘッダーでフォーマットされます。

これは、最小日付を設定せずにDatePickerを使用したプレーンなCalendarViewで再現できます。スピナーを使用して年を2038以降に移動し、月を変更して(ヘッダーの更新をトリガーするため)、ヘッダーの折り返しに年が1902に表示されます。

さて、最小の日付を設定するとカレンダービューが最大の日付までスクロールするのはなぜかという疑問が残ります。答えは、カレンダービューの週ListViewアダプターのようです。最小日からの週にインデックスを付けますが、最小日が変更されると、アダプターのnotifyDataSetChanged()は呼び出されません。したがって、上記の回避策#1は、次の理由で機能します。

  • 日付を1か月以上変更すると、月のヘッダーが変更されます。
  • 曜日を変更すると、週のリストビューでアダプターのデータが変更されたことがわかります。
20
laalto

上記の答えは、このバグの原因について正しいものです。私のアプリケーションでは、次の回避策を使用しています。

DatePickerの日付またはminDateが変更されるたびに、ピッカー/カレンダーで選択する必要のある日付を使用して次のルーチンを呼び出します。

private void fixUpDatePickerCalendarView(Calendar date) {
    // Workaround for CalendarView bug relating to setMinDate():
    // https://code.google.com/p/Android/issues/detail?id=42750
    // Set then reset the date on the calendar so that it properly
    // shows today's date. The choice of 24 months is arbitrary.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        final CalendarView cal = datePicker.getDatePicker().getCalendarView();
        if (cal != null) {
            date.add(Calendar.MONTH, 24);
            cal.setDate(date.getTimeInMillis(), false, true);
            date.add(Calendar.MONTH, -24);
            cal.setDate(date.getTimeInMillis(), false, true);
        }
    }
}
12
bpenrod

私が使用することになったソリューション:

private void fixBuggyCalendarview(CalendarView cv) {
    long current = cv.getDate();
    cv.setDate(cv.getMaxDate(), false, true);
    cv.setDate(current, false, true);
}

これは、CalendarViewの最小/最大日付を設定した場合にも機能します

4
1615903

@laaltoに同意します。元のCalendarViewは完全に混乱しています。また、ロケール、Android 4.1.2(そうそう、シフトダウン)のフォントサイズ)、アクセシビリティ機能の欠如などの問題もありました。そこで、コピーすることにしました- ソースコード そして自分のカレンダーを実装します。たとえば、ヘッダーテキストが正しく表示されるようになりました。

/**
 * The source code has the Year 2038 problem and doesn't honor CalendarView's mCurrentLocale.
 */
private void setMonthDisplayed(Calendar calendar) {
    mMonthName.setText(DateFormat.format("MMMM, yyyy", calendar));
    mMonthName.invalidate();

    mCurrentMonthDisplayed = calendar.get(Calendar.MONTH);
    mAdapter.setFocusMonth(mCurrentMonthDisplayed);
}

/**
 * This imlementation formats the date correctly and uses the stand-alone form of a month (for the languages where it's important).
 */
private void setMonthDisplayed(Calendar calendar) {
    mMonthName.setText(new SimpleDateFormat("LLLL yyyy", mCurrentLocale).format(calendar.getTime()));
    mMonthName.invalidate();

    mCurrentMonthDisplayed = calendar.get(Calendar.MONTH);
    mAdapter.setFocusMonth(mCurrentMonthDisplayed);
}
0
Nikolai

上記の提案を実装する前に、シミュレータが今日の日時に実行されていることを確認してください。しばらく開いたままにしておくと、数日遅れることがあります。シミュレータを再起動するだけで問題を修正しました。

0
Felker