web-dev-qa-db-ja.com

DatePickerDialogHoloスタイリングがAndroid 7Nougatで失敗しました

お客様の要望から、すべてのAndroid OSバージョン、次のように、DatePickerDialogでHOLOスタイルを維持したいと考えています: DatePicker on Android 7-

しかし、Android 7:では正しく機能しないようです。

DatePicker on Android 7

私の実装から:

new DatePickerDialog(getContext(), AlertDialog.THEME_HOLO_LIGHT,
                        mCalendarPickerListener,
                        calendar.get(Calendar.YEAR),
                        calendar.get(Calendar.MONTH),
                        calendar.get(Calendar.DAY_OF_MONTH));

以前のAndroid 7.)では問題なく動作しています。

編集:ソリューションはAPI 25で修正されていることが判明 https://code.google.com/u/106133255289400340786/

11
Trung Le

DatePickerDialogを使用して、ユーザーに誕生日のプロンプトを表示します。残念ながら、試してみると、マテリアルをテーマにしたダイアログについてユーザーから多くの苦情が寄せられたため、それに切り替えることはできません。ホロをテーマにしたダイアログに固執する必要があります。

Android 7.0にはバグが付属しています:このプラットフォームでHoloテーマを使用しようとすると、代わりに壊れたマテリアルテーマの使用にフォールバックします。 DatePickerDialogの場合。次の2つのバグレポートを参照してください。

Jeff Lockhartによるこの回避策 の修正された形式を使用しました。これらのバグレポートで参照されています。

_private static final class FixedHoloDatePickerDialog extends DatePickerDialog {
    private FixedHoloDatePickerDialog(Context context, OnDateSetListener callBack,
                                      int year, int monthOfYear, int dayOfMonth) {
        super(context, callBack, year, monthOfYear, dayOfMonth);

        // Force spinners on Android 7.0 only (SDK 24).
        // Note: I'm using a naked SDK value of 24 here, because I'm
        // targeting SDK 23, and Build.VERSION_CODES.N is not available yet.
        // But if you target SDK >= 24, you should have it.
        if (Build.VERSION.SDK_INT == 24) {
            try {
                final Field field = this.findField(
                        DatePickerDialog.class,
                        DatePicker.class,
                        "mDatePicker"
                );

                final DatePicker datePicker = (DatePicker) field.get(this);
                final Class<?> delegateClass = Class.forName(
                        "Android.widget.DatePicker$DatePickerDelegate"
                );
                final Field delegateField = this.findField(
                        DatePicker.class,
                        delegateClass,
                        "mDelegate"
                );

                final Object delegate = delegateField.get(datePicker);
                final Class<?> spinnerDelegateClass = Class.forName(
                        "Android.widget.DatePickerSpinnerDelegate"
                );

                if (delegate.getClass() != spinnerDelegateClass) {
                    delegateField.set(datePicker, null);
                    datePicker.removeAllViews();

                    final Constructor spinnerDelegateConstructor =
                            spinnerDelegateClass.getDeclaredConstructor(
                                    DatePicker.class,
                                    Context.class,
                                    AttributeSet.class,
                                    int.class,
                                    int.class
                            );
                    spinnerDelegateConstructor.setAccessible(true);

                    final Object spinnerDelegate = spinnerDelegateConstructor.newInstance(
                            datePicker,
                            context,
                            null,
                            Android.R.attr.datePickerStyle,
                            0
                    );
                    delegateField.set(datePicker, spinnerDelegate);

                    datePicker.init(year, monthOfYear, dayOfMonth, this);
                    datePicker.setCalendarViewShown(false);
                    datePicker.setSpinnersShown(true);
                }
            } catch (Exception e) { /* Do nothing */ }
        }
    }

    /**
     * Find Field with expectedName in objectClass. If not found, find first occurrence of
     * target fieldClass in objectClass.
     */
    private Field findField(Class objectClass, Class fieldClass, String expectedName) {
        try {
            final Field field = objectClass.getDeclaredField(expectedName);
            field.setAccessible(true);
            return field;
        } catch (NoSuchFieldException e) { /* Ignore */ }

        // Search for it if it wasn't found under the expectedName.
        for (final Field field : objectClass.getDeclaredFields()) {
            if (field.getType() == fieldClass) {
                field.setAccessible(true);
                return field;
            }
        }

        return null;
    }
}
_

これは何をしますか:

  • このダイアログに属するプライベート_DatePicker mDatePicker_フィールドを取得します
  • このダイアログに属するプライベート_DatePickerDelegate mDelegate_フィールドを取得します
  • デリゲートがまだDatePickerSpinnerDelegate(必要なデリゲートのタイプ)のインスタンスではないことを確認します
  • DatePickerからすべてのビューを削除します。これらはマテリアルカレンダーウィジェットであるため、
  • DatePickerSpinnerDelegateの新しいインスタンスを作成し、それをこのダイアログのmDelegatemDatePickerフィールドに割り当てます。
  • カレンダー情報といくつかのパラメータを使用してmDatePickerを再初期化し、スピナーを膨らませます

この回避策を使用するには、ContextThemeWrapperの周りにContextを作成します。これにより、テーマ(この場合はHolo)を設定できます。

_final Context themedContext = new ContextThemeWrapper(
        this.getContext(),
        Android.R.style.Theme_Holo_Light_Dialog
);

final DatePickerDialog dialog = new FixedHoloDatePickerDialog(
        themedContext,
        datePickerListener,
        calender.get(Calendar.YEAR),
        calendar.get(Calendar.MONTH),
        calendar.get(Calendar.DAY_OF_MONTH)
);
_

メモ

  • これは、リフレクションを使用してプライベートフィールドにアクセスします。一般的に、これは堅牢なアプローチではなく、信頼することはできません。ここでは、1)これを単一のSDKバージョンv24に制限することでリスクを軽減しています。 2)リフレクションコードのビット全体をtry {...} catch (Exception e) {/* NOP */}ブロックでラップするため、リフレクションのいずれかが失敗しても何も起こらず、(悲しいことに壊れた)デフォルトのマテリアルフォールバックが使用されます。
  • 上記のバグレポートは、この問題がAndroid 7.1(SDK 25)で修正されたと主張しています。私はこれをテストしていません。
  • 元の回避策コード は、同様の問題が発生したTimePickerDialog用でした。代わりにDatePickerDialogで動作するように変更し、ソリューションを単純化して一般的ではなく、正確なユースケースに固有のものにしました。ただし、より完全な元のバージョンを使用して、Dateの代わりにTime用に微調整することもできます。
30
savanto

日付ピッカーには独自のスタイルがあります https://developer.Android.com/reference/Android/R.style.html#Widget_DatePicker

R.style.Widget.Holo.DatePickerの代わりにAlertDialog.THEME_HOLO_LIGHTが必要だと思います。親として@Android:style/Widget.Holo.DatePickerを持つ独自の空のスタイルを作成し、それを使用する必要がある可能性があります。

0
Ben

savantoによるソリューション に加えて:API 28以降、Holo LightThemeは非推奨になりました。 API 21/Lollipopデバイス以降を対象とする場合は、マテリアルデザインスタイルを使用する必要があります。 このソリューション を確認してください。これらのスタイルを定義してから、ContextThemeWrapperR.style.dlg_datePickerにリンクしました。

<style name="dlg_datePicker" parent="Theme.AppCompat.Light.Dialog">
    <item name="Android:datePickerStyle">@style/datePicker_style</item>
    <item name="Android:textSize">18sp</item>
</style>

<style name="datePicker_style" parent="Android:Widget.Material.Light.DatePicker">
    <item name="Android:datePickerMode">spinner</item>
</style>
0
guglhupf

それが機能していることをフォローしてみてください。私はテストしました。そして、それは空白のギャップを持っていません。

また、mDatePickerListenerは、作成できる私のDatePickerListnerです。

//Setting theme to Android.R.style.Theme_Holo_Light_Dialog  
Calendar cal = Calendar.getInstance(TimeZone.getDefault());
DatePickerDialog datePicker = new DatePickerDialog(getContext(), Android.R.style.Theme_Holo_Light_Dialog,
                    mDatePickerListener,
                    cal.get(Calendar.YEAR),
                    cal.get(Calendar.MONTH),
                    cal.get(Calendar.DAY_OF_MONTH));

datePicker.setCancelable(false);
datePicker.setTitle(getString(R.string.select_your_dob));
//This line is important to remove blank gaps
datePicker.getWindow().setBackgroundDrawable(new ColorDrawable(Android.graphics.Color.TRANSPARENT));
datePicker.show();
0
Akshay Taru