Is it possible to add a footer to the MaterialDateRange Picker?

Viewed 121

I'm pretty sure this is impossible, but I just wanted to check before I use a 3rd party library. The DatePicker doesn't really let you customize the UI, and it's basically a full screen DialogFragment so putting something above or below (or even on top) doesn't seem to be an option.

My calendar currently looks like this:

enter image description here

But I would like it to look like this design doc with a footer at the bottom. Let me know if you have any ideas, because I'm out of them. Thanks!

enter image description here

   val builder = MaterialDatePicker.Builder.dateRangePicker()
      .setTheme(R.style.ThemeOverlay_MaterialComponents_MaterialCalendar_Fullscreen)
      .setCalendarConstraints(
        CalendarConstraints.Builder()
          .setStart(Date().time)
          .setEnd(oneYearFrom().time)
          .setValidator(AvailabilityValidator(it.unavailableDays)).build()
      )
    builder.setInputMode(MaterialDatePicker.INPUT_MODE_CALENDAR)
    builder.build().show(requireActivity().supportFragmentManager, TAG_DATE_RANGE_PICKER)
1 Answers

The only way to add a footer to the MaterialDateRangePicker is to get access to the Picker root View and from there you can find the Picker FrameLayout Container (with Id app:id/mtrl_calendar_frame) and get access to its child View the Vertical LinearLayout Container which contains of three children: (a Days-GridView, a Separator-View and a RecyclerView) in a vertical orientation. What you want is to add a footer below the RecyclerView. Because the parent is a LinearLayout you can add the weight attribute to all its children to be weight=0 except the RecyclerView (which render the months) to be weight=1 to get all the remaining space.

To access the Picker root View you have to observe the MaterialDatePicker (DialogFragment) lifecycle and add the footer View in onStart() of DialogFragment. For this purpose you can use the DefaultLifecycleObserver.

Below is full working example to add a footer View:

private fun showRangeDatePicker() {

    //initialize dateRangePicker
    val builder = MaterialDatePicker.Builder.dateRangePicker()
    builder.setTheme(R.style.ThemeOverlay_MaterialComponents_MaterialCalendar_Fullscreen)

    //set CalendarConstraints
    val constraintsBuilder = CalendarConstraints.Builder()
    val c: Calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
    constraintsBuilder.setStart(c.getTimeInMillis())
    c.add(Calendar.YEAR, 1)
    constraintsBuilder.setEnd(c.getTimeInMillis())
    builder.setCalendarConstraints(constraintsBuilder.build())

    //set InputMode and Title
    builder.setInputMode(MaterialDatePicker.INPUT_MODE_CALENDAR)
    builder.setTitleText("Pick dates")

    //get MaterialDatePicker instance and set a lifecycle Observer to listen for DialogFragment lifecycle
    //DefaultLifecycleObserver needs androidx.appcompat:appcompat:1.4.0 and add the FooterView in onStart of DialogFragment
    val picker: MaterialDatePicker<*> = builder.build()
    picker.lifecycle.addObserver(object : DefaultLifecycleObserver
    {
        override fun onCreate(owner: LifecycleOwner) {
        }

        override fun onStart(owner: LifecycleOwner) {
            //add footerView in onStart of DialogFragment so you can access the picker.view
            addMyFooterView(picker)
        }

        override fun onResume(owner: LifecycleOwner) {
        }

        override fun onDestroy(owner: LifecycleOwner) {
            //remove Lifecycle Observer
            picker.lifecycle.removeObserver(this)
        }
    })
    picker.show(supportFragmentManager, picker.toString())
}

private fun addMyFooterView(picker: MaterialDatePicker<*>) {

    //get the MaterialDatePicker root View
    val rootView = picker.view as LinearLayout
    //find the Picker FrameLayout Container with ID: app:id/mtrl_calendar_frame
    val frameLayout = rootView.findViewById<FrameLayout>(resources.getIdentifier("mtrl_calendar_frame", "id", packageName))
    //and get the Picker LinearLayout Container (which contains of 3 children: Days-GridView, Separator-View and a RecyclerView)
    val linearContainer = frameLayout.getChildAt(0) as LinearLayout

    //for each child in Vertical LinearLayout Container set the weight to RecyclerView to 1 and 0 to all others.
    //(This is necessary before adding the footerView to distribute correctly each children Height)
    for (i in 0 until linearContainer.childCount) {
        val v = linearContainer.getChildAt(i)
        val params = v.layoutParams as LinearLayout.LayoutParams
        params.weight = if (v is RecyclerView) 1f else 0f
        v.layoutParams = params
    }

    //and finally add the 4th child in the above linearContainer to be the FooterView
    val myFooterView = layoutInflater.inflate(R.layout.footer_layout, null)
    val myFooterViewHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50f, resources.displayMetrics).toInt()
    myFooterView.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, myFooterViewHeight, 0f)
    myFooterView.setBackgroundColor(Color.WHITE)
    linearContainer.addView(myFooterView)

    //set any FooterView click listeners
    myFooterView.findViewById<View>(R.id.textView2).setOnClickListener {}
}

Where footer_layout is your custom footer xml layout like below:

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

    <View
        android:id="@+id/separatorView"
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_alignParentTop="true"
        android:background="@android:color/darker_gray"/>

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="15sp"
        android:textStyle="bold"
        android:layout_marginEnd="10dp"
        android:layout_centerVertical="true"
        android:layout_alignParentStart="true"
        android:text="Match exact dates" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="15sp"
        android:textStyle="normal"
        android:layout_marginEnd="10dp"
        android:layout_centerVertical="true"
        android:layout_toEndOf="@+id/textView1"
        android:text="±1 day" />

    <TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="15sp"
        android:textStyle="normal"
        android:layout_marginEnd="10dp"
        android:layout_centerVertical="true"
        android:layout_toEndOf="@+id/textView2"
        android:text="±3 days" />

</RelativeLayout>

Result:

date_picker_dialog

Related