0

我正在开发一个使用 MaterialDatePicker 的应用程序。

来自应用程序的材料 DatePicker 片段

整个应用程序是全屏的(启用沉浸式模式 - 隐藏状态和导航栏),我也希望在 DatePicker 对话框中使用它。我尝试了多种建议,但没有任何效果。有没有办法做到这一点?

更新:

到目前为止我已经尝试过:

    val datePickerBuilder = MaterialDatePicker.Builder.dateRangePicker()

    datePickerBuilder.apply {
      setTitleText("SELECT A DATE")
      setTheme(R.style.MaterialCalendarTheme)
      setSelection(
        Pair(
          startDate.atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli(),
          endDate.atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli(),
        )
      )
    }


    val dp = datePickerBuilder.build()

    dp.dialog?.apply {
      window?.setFlags(
        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
      )
      window?.decorView?.setSystemUiVisibility(dp.requireActivity().window.decorView.getSystemUiVisibility())
      setOnShowListener {
        dp.dialog?.window?.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)

        val wm = dp.requireActivity().getSystemService(Context.WINDOW_SERVICE) as WindowManager
        wm.updateViewLayout(dp.dialog?.window?.decorView, dp.dialog?.window?.attributes)
      }
    }

第二个应用运算符内部的代码片段适用于我构建的其他自定义 DialogFragmet。

在尝试了上述建议后,我发现 MaterialDatePicker 的 onCreateDialog 方法是最终的,因此无法覆盖。

4

1 回答 1

0

您的方法的问题在于,dp.dialog它始终是在初始化null时才创建的,因此 UI 可见性更改代码永远不会被执行。如果您同步显示对话框,它可能是非空的:FragmentManagerDialogFragmentMaterialDatePicker

dp.showNow(supportFragmentManager, null)
dp.dialog?.apply {
   // anything here gets executed as the dp.dialog is not null
}

然而,这种方法的问题在于,这段代码再也不会被调用。如果对话框被重建(例如设备被旋转),该块内的任何内容都不会再执行,这将导致非全屏对话框。

现在,有一个基本问题MaterialDatePicker:它final不能覆盖在其他情况下可以工作的任何对话框创建者/处理程序方法。

幸运的是,有一个类FragmentLifecycleCallbacks可以用来监听(惊喜-惊喜)片段生命周期事件。您可以使用它来捕捉在创建视图之后构建对话框的时刻(回调:)onFragmentViewCreated。如果您在Activity's 或Fragment's中注册它onCreate(...),您的日期选择器片段(以及对话框本身)将是最新的。

因此,事不宜迟,在对不同设置进行大量实验和调整之后,以下解决方案可能会满足您的需求(这使用Activity)。

示例项目在GitHub上可用。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // ... setContentView(...), etc.

    registerDatePickerFragmentCallbacks()
}

// need to call this every time the Activity / Fragment is (re-)created
private fun registerDatePickerFragmentCallbacks() {
    val setFocusFlags = fun(dialog: Dialog, setUiFlags: Boolean) {
        dialog.window?.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)

        if (setUiFlags) {
            dialog.window?.decorView?.setOnSystemUiVisibilityChangeListener { visibility ->
                // after config change (e.g. rotate) the system UI might not be fullscreen
                // this ensures that the UI is updated in case of this
                if (visibility and View.SYSTEM_UI_FLAG_FULLSCREEN == 0) {
                    hideSystemUI(dialog.window?.decorView)
                    hideSystemUI() // this might not be needed
                }
            }

            hideSystemUI(dialog.window?.decorView)
            hideSystemUI() // this might not be needed
        }
    }

    // inline fun that clears the FLAG_NOT_FOCUSABLE flag from the dialog's window
    val clearFocusFlags = fun(dialog: Dialog) {
        dialog.window?.apply {
            clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)

            decorView.also {
                if (it.isAttachedToWindow) {
                    windowManager.updateViewLayout(it, attributes)
                }
            }
        }
    }

    supportFragmentManager.registerFragmentLifecycleCallbacks(object :
        FragmentManager.FragmentLifecycleCallbacks() {

        override fun onFragmentViewCreated(
            fm: FragmentManager,
            f: Fragment,
            v: View,
            savedInstanceState: Bundle?
        ) {
            // apply this to MaterialDatePickers only
            if (f is MaterialDatePicker<*>) {
                f.requireDialog().apply {
                    setFocusFlags(this, true)

                    setOnShowListener {
                        clearFocusFlags(this)
                    }
                }
            }
        }
    }, false)
}


override fun onResume() {
    super.onResume()
    // helps with small quirks that could happen when the Activity is returning to a resumed state
    hideSystemUI()
}

// this is probably already in your class
override fun onWindowFocusChanged(hasFocus: Boolean) {
    super.onWindowFocusChanged(hasFocus)

    if (hasFocus) hideSystemUI()
}

private fun hideSystemUI() {
    hideSystemUI(window.decorView)
}

// this is where you apply the full screen and other system UI flags
private fun hideSystemUI(view: View?) {
    view?.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
            or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
            or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
            or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
            or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
            or View.SYSTEM_UI_FLAG_FULLSCREEN)
}

MaterialDatePicker使用沉浸式模式,您需要以下任一样式。第一个显示普通对话框,而第二个使用全屏对话框,这会禁用打开对话框时通常发生的背景变暗,并确保对话框显示在全屏窗口中。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- Normal dialog -->
    <style name="MyCalendar" parent="ThemeOverlay.MaterialComponents.MaterialCalendar">
        <!-- you can use ?attr/colorSurface to remove any blinking happening during re-creation of the dialog -->
        <item name="android:navigationBarColor">?attr/colorPrimary</item>

        <!-- or use translucent navigation bars -->
        <!--<item name="android:windowTranslucentNavigation">true</item>-->

        <item name="android:immersive">true</item>
        <item name="android:windowFullscreen">true</item>
        <item name="android:windowIsTranslucent">false</item>
    </style>

    <-- Fullscreen dialog -->
    <style name="MyCalendar.Fullscreen" parent="ThemeOverlay.MaterialComponents.MaterialCalendar.Fullscreen">
        <item name="android:windowIsFloating">false</item>
        <item name="android:backgroundDimEnabled">false</item>

        <!-- you can use ?attr/colorSurface to remove any blinking happening during re-creation of the dialog -->
        <item name="android:navigationBarColor">?attr/colorPrimary</item>

        <!-- or use translucent navigation bars -->
        <!--<item name="android:windowTranslucentNavigation">true</item>-->

        <item name="android:immersive">true</item>
        <item name="android:windowFullscreen">true</item>
        <item name="android:windowIsTranslucent">false</item>
    </style>
</resources>

在构建对话框时,只需将此样式作为对话框的主题传递:

val datePickerBuilder = MaterialDatePicker.Builder.dateRangePicker()

// for a normal dialog
datePickerBuilder.setTheme(R.style.MyCalendar)

// for a fullscreen dialog
datePickerBuilder.setTheme(R.style.MyCalendar_Fullscreen)

这是全屏沉浸式对话框的样子:

在此处输入图像描述

和一个正常的沉浸式对话:

在此处输入图像描述

这些屏幕截图是在通常具有导航栏的模拟器上拍摄的。

于 2021-01-16T02:03:29.257 回答