单击时 DatePicker 在我的设备上崩溃(使用个人应用程序)

DatePicker crashes on my device when clicked (with personal app)

编辑:我检查过只有在 phone 设置为 "French" 语言时才会出现此问题。

我目前正在构建自己的应用程序,但在 phone(调试模式 phone:Samsung S5)上使用 DatePicker 小部件时遇到问题。

错误集合是:java.util.IllegalFormatConversionException

我添加了有关 logcat 中收集的问题的更多详细信息:

02-20 10:37:18.255  13029-13029/avappmobile.mytasks E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: avappmobile.mytasks, PID: 13029
    java.util.IllegalFormatConversionException: %d can't format java.lang.String arguments
            at java.util.Formatter.badArgumentType(Formatter.java:1489)
            at java.util.Formatter.transformFromInteger(Formatter.java:1689)
            at java.util.Formatter.transform(Formatter.java:1461)
            at java.util.Formatter.doFormat(Formatter.java:1081)
            at java.util.Formatter.format(Formatter.java:1042)
            at java.util.Formatter.format(Formatter.java:1011)
            at java.lang.String.format(String.java:1803)
            at android.content.res.Resources.getString(Resources.java:1453)
            at android.content.Context.getString(Context.java:397)
            at android.widget.SimpleMonthView$MonthViewTouchHelper.getItemDescription(SimpleMonthView.java:684)
            at android.widget.SimpleMonthView$MonthViewTouchHelper.onPopulateNodeForVirtualView(SimpleMonthView.java:628)
            at com.android.internal.widget.ExploreByTouchHelper.createNodeForChild(ExploreByTouchHelper.java:377)
            at com.android.internal.widget.ExploreByTouchHelper.createNode(ExploreByTouchHelper.java:316)
            at com.android.internal.widget.ExploreByTouchHelper.access0(ExploreByTouchHelper.java:50)
            at com.android.internal.widget.ExploreByTouchHelper$ExploreByTouchNodeProvider.createAccessibilityNodeInfo(ExploreByTouchHelper.java:711)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfVirtualNode(AccessibilityInteractionController.java:1179)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1091)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchAccessibilityNodeInfos(AccessibilityInteractionController.java:888)
            at android.view.AccessibilityInteractionController.findAccessibilityNodeInfoByAccessibilityIdUiThread(AccessibilityInteractionController.java:155)
            at android.view.AccessibilityInteractionController.access0(AccessibilityInteractionController.java:53)
            at android.view.AccessibilityInteractionController$PrivateHandler.handleMessage(AccessibilityInteractionController.java:1236)
            at android.os.Handler.dispatchMessage(Handler.java:102)
            at android.os.Looper.loop(Looper.java:145)
            at android.app.ActivityThread.main(ActivityThread.java:5834)
            at java.lang.reflect.Method.invoke(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:372)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1388)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1183)

这是我的 DialogFragment 代码:

public class DatePFragment extends DialogFragment implements DatePickerDialog.OnDateSetListener {

    DatePicker dp;
    OnDatePickedListener mCallback;
    Integer mLayoutId = null;

    // Interface definition
    public interface OnDatePickedListener {
        public void onDatePicked(int textId, int year, int month, int day);
    }

    // make sure the Activity implement the OnDatePickedListener
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        try {
            mCallback = (OnDatePickedListener)activity;
        }
        catch (final ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnDatePickedListener");
        }
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState){
        mCallback = (OnDatePickedListener)getActivity();

        Calendar cal = Calendar.getInstance();
        Bundle bundle = this.getArguments();
        mLayoutId = bundle.getInt("layoutId");
        int year = cal.get(Calendar.YEAR);
        int month = cal.get(Calendar.MONTH);
        int day = cal.get(Calendar.DAY_OF_MONTH);

        View view = getActivity().getLayoutInflater().inflate(R.layout.datepfragment, null);

        dp = (DatePicker) view.findViewById(R.id.datePicker);

        // Create a new instance of DatePickerDialog and return it
        return new DatePickerDialog(getActivity(),this,year, month, day);
    }

    @Override
    public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
        if (mCallback != null) {
            // Use the date chosen by the user.

            mCallback.onDatePicked(mLayoutId, year, monthOfYear+1, dayOfMonth);
        }
    }
}

这是关联的 xml 布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/txtDPTitle"
        android:id="@+id/txtDPTitle"
        android:layout_gravity="center_horizontal"
        android:gravity="center"
        android:layout_alignParentTop="true"
        android:layout_alignParentStart="true"
        android:layout_marginTop="35dp" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/btnDPValidate"
        android:id="@+id/btnDPValidate"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="35dp" />

    <DatePicker
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/datePicker"
        android:layout_gravity="center_horizontal"
        android:layout_marginLeft="0dp"
        android:datePickerMode="spinner"
        android:calendarViewShown="false"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />

</RelativeLayout>

事实是我遇到了两种问题(无论如何都会导致应用程序崩溃):

  1. 当我进入我的应用程序并直接转到 DatePicker 小部件时,日历会正确显示,并且我 select 一天(我说一天,因为例如更改年份没有问题),应用程序崩溃。
  2. 如果我继续其他功能点击显示DatePicker,它直接崩溃。

我看到了其他帖子,例如:DatePicker crash in samsung with android 5.0

在这个线程中,我没有得到足够的信息,因为给定的解决方案只允许显示日历的微调器视图,而且显示的微调器没有居中显示为我的(直接放在顶部屏幕)。

如果您需要更多详细信息,请询问我。

谢谢。 亚历克斯

我找到了解决方法,使用配置和区域设置变量。

如果我检测到它是法语 ("fr") 语言,我只是修改语言并将其替换为英语 ("en") 语言,然后在 DatePicker DialogFragment 中恢复正常.

public void showDatePickerDialog(int layoutId) {
        Integer year = cal.get(Calendar.YEAR);
        Integer month = cal.get(Calendar.MONTH);
        Integer day = cal.get(Calendar.DAY_OF_MONTH);

        // Due to an issue with DatePicker and fr language (locale), if locale language is french, this is changed to us to make the DatePicker working
        if(Locale.getDefault().getLanguage().toString().equals(FR_LANG_CONTEXT)){
            Configuration config = new Configuration();
            config.locale = new Locale(US_LANG_CONTEXT);
            getApplicationContext().getResources().updateConfiguration(config, null);
        }

        Bundle bundle = createDatePickerBundle(layoutId, year, month, day);
        DialogFragment newFragment = new DatePFragment();
        newFragment.setArguments(bundle);
        newFragment.show(fm, Integer.toString(layoutId));
    }

一旦我出去

@Override
    public void onDatePicked(int LayoutId, int year, int month, int day){
        // Once out from the DatePicker, if we were working on a fr language, we update back the configuration with fr language.
        if(Locale.getDefault().getLanguage().toString().equals(FR_LANG_CONTEXT)){
            Configuration config = new Configuration();
            config.locale = new Locale(FR_LANG_CONTEXT);
            getApplicationContext().getResources().updateConfiguration(config, null);
        }

        String date = day + "/" + month + "/" + year;
        txtTaskDate.setText(date);
    }

忘掉内置的日期选择器吧。恕我直言,切换语言环境不是解决方案,因为我想在当前语言环境中使用选择器。只有一种方法可以避免崩溃:使用提供独立实现的库。

对于日期选择器片段:https://github.com/flavienlaurent/datetimepicker

对于日期选择器小部件:https://github.com/SingleCycleKing/CustomTimePicker(这是一个起点,而不是现成的解决方案)

这是三星 Lollipop UI 实现中的一个错误。它会影响 Galaxy S4、S5、Note 3 以及可能更多的设备。对我们来说,它发生在 de_DE 和 de_AT 种语言上,但它似乎是一个影响多种语言的问题。

我通过强制三星设备为日期选择器使用全息主题来修复它。在我们的 DatePickerFragment 中:

@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    /* calendar code here */

    Context context = getActivity();
    if (isBrokenSamsungDevice()) {
        context = new ContextThemeWrapper(getActivity(), android.R.style.Theme_Holo_Light_Dialog);
    }
    return new DatePickerDialog(context, this, year, month, day);
}

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

多亏了三星,他们的用户将不会得到漂亮的 material 设计的日期选择器。

我有一个可能的 FIX PATCH,实际上可以让用户继续使用 Lollipop 库。

我只能通过在 Android 5.0.1 法语的 S4 上启用 TalkBack 来重现崩溃,三星似乎将整个日期传递给用于可访问性的项目选择字符串而不是天数.

基于包装上下文的@mreichelt 解决方案,我们可以覆盖 getString(id,args) 函数并实际解决问题。

context = new ContextWrapper(getActivity()) {

    private Resources wrappedResources;

    @Override
    public Resources getResources() {
        Resources r = super.getResources();
        if(wrappedResources == null) {
            wrappedResources = new Resources(r.getAssets(), r.getDisplayMetrics(), r.getConfiguration()) {
                @NonNull
                @Override
                public String getString(int id, Object... formatArgs) throws NotFoundException {
                    try {
                        return super.getString(id, formatArgs);
                    } catch (IllegalFormatConversionException ifce) {
                        Log.e("DatePickerDialogFix", "IllegalFormatConversionException Fixed!", ifce);
                        String template = super.getString(id);
                        template = template.replaceAll("%" + ifce.getConversion(), "%s");
                        return String.format(getConfiguration().locale, template, formatArgs);
                    }
                }
            };
        }
        return wrappedResources;
    }
};

因此,如果我们将此上下文提供给 DatePickerDialog 构造函数,它将解决问题。

更新: 当页面包含日期输入字段时,此问题也会出现在 webview 中,我发现解决这两个问题(对话框和 webview)的最佳方法是重写 Activity getResources 函数,如下所示:

@Override
public Resources getResources() {
    if (wrappedResources == null) {
        wrappedResources = wrapResourcesFixDateDialogCrash(super.getResources());
    }
    return wrappedResources;
}

public Resources wrapResourcesFixDateDialogCrash(Resources r) {
    return new Resources(r.getAssets(), r.getDisplayMetrics(), r.getConfiguration()) {
        @NonNull
        @Override
        public String getString(int id, Object... formatArgs) throws NotFoundException {
            try {
                return super.getString(id, formatArgs);
            } catch (IllegalFormatConversionException ifce) {
                Log.i("DatePickerDialogFix", "IllegalFormatConversionException Fixed!", ifce);
                String template = super.getString(id);
                template = template.replaceAll("%" + ifce.getConversion(), "%s");
                return String.format(getConfiguration().locale, template, formatArgs);
            }
        }
    };
}

注意:该解决方案包括缓存资源 - 这意味着如果应用程序中有事件更改资源(如语言更改),您可能需要更新缓存资源。

我发现@mreichelt 的解决方案存在一个小问题。呈现的对话框没有显示当前选定日期(包括星期几)的标题。明确设置标题,然后立即再次设置日期为我解决了这个问题。

Context context = getActivity();
if (isBrokenSamsungDevice()) {
    context = new ContextThemeWrapper(getActivity(), android.R.style.Theme_Holo_Light_Dialog);
}
DatePickerDialog datePickerDialog = new DatePickerDialog(context, this, year, month, day);
if (isBrokenSamsungDevice()) {
    datePickerDialog.setTitle("");
    datePickerDialog.updateDate(year, month, day);
}
return datePickerDialog;

基于 I created a SupportDatePickerDialog.java 环绕 DatePickerDialog 并自动 修复 android 5.0 和 5.1 的上下文。

gist

当您以编程方式定义比 max date

更远的 min date 时,也会发生这种情况
datePicker.getDatePicker().setMaxDate(15000000);

datePicker.getDatePicker().setMinDate(16000000);