prolificinteractive / material-calendarview

A Material design back port of Android's CalendarView
https://prolificinteractive.github.io/material-calendarview/
MIT License
5.92k stars 1.32k forks source link

How to set different drawables in same selector #912

Open mpierucci opened 5 years ago

mpierucci commented 5 years ago

I need a custom selector for range selection in which I could set different drawables if the date is the first selected, last selected, or in the middle. Since the decorator doesn't expose current date in decorate how can I achieve that. Cheers!

sampengilly commented 5 years ago

This is talked about in #162, copying my comment from there to here:

The only way to do this still is to specify three different decorators as mentioned above (left, middle, and right) and apply/remove those as necessary. For the purposes of date range selection I feel that decorators shouldn't be used (and instead should be reserved for stuff like event dots and other such decoration). The idea of start, middle, and end range selection drawables should probably be migrated inside the library and exposed via some attributes on the view.

My current solution for this is to wrap these three decorators up in a handler that adds and removes them all as one:

class RangeSelectionDecorator(
        private val leftDrawable: Drawable,
        private val middleDrawable: Drawable,
        private val rightDrawable: Drawable
) {

    private var previousDecorators = emptyList<DayViewDecorator>()

    fun clear(calendar: MaterialCalendarView) {
        previousDecorators.forEach { calendar.removeDecorator(it) }
    }

    fun applyForSelection(calendar: MaterialCalendarView, selectedDates: List<LocalDate>) {
        clear(calendar)

        val newDecorators = getDecoratorsForSelection(selectedDates)
        calendar.addDecorators(newDecorators)

        previousDecorators = newDecorators
    }

    private fun getDecoratorsForSelection(selectedDates: List<LocalDate>): List<DayViewDecorator> {
        val first = selectedDates.first()
        val last = selectedDates.last()
        val middle = selectedDates.subtract(listOf(first, last))

        return listOf(
                RangeSelectionLeftDecorator(first, leftDrawable),
                RangeSelectionMiddleDecorator(middle, middleDrawable),
                RangeSelectionRightDecorator(last, rightDrawable)
        )
    }

}
CreatorB commented 4 years ago

This is talked about in #162, copying my comment from there to here:

The only way to do this still is to specify three different decorators as mentioned above (left, middle, and right) and apply/remove those as necessary. For the purposes of date range selection I feel that decorators shouldn't be used (and instead should be reserved for stuff like event dots and other such decoration). The idea of start, middle, and end range selection drawables should probably be migrated inside the library and exposed via some attributes on the view.

My current solution for this is to wrap these three decorators up in a handler that adds and removes them all as one:

class RangeSelectionDecorator(
        private val leftDrawable: Drawable,
        private val middleDrawable: Drawable,
        private val rightDrawable: Drawable
) {

    private var previousDecorators = emptyList<DayViewDecorator>()

    fun clear(calendar: MaterialCalendarView) {
        previousDecorators.forEach { calendar.removeDecorator(it) }
    }

    fun applyForSelection(calendar: MaterialCalendarView, selectedDates: List<LocalDate>) {
        clear(calendar)

        val newDecorators = getDecoratorsForSelection(selectedDates)
        calendar.addDecorators(newDecorators)

        previousDecorators = newDecorators
    }

    private fun getDecoratorsForSelection(selectedDates: List<LocalDate>): List<DayViewDecorator> {
        val first = selectedDates.first()
        val last = selectedDates.last()
        val middle = selectedDates.subtract(listOf(first, last))

        return listOf(
                RangeSelectionLeftDecorator(first, leftDrawable),
                RangeSelectionMiddleDecorator(middle, middleDrawable),
                RangeSelectionRightDecorator(last, rightDrawable)
        )
    }

}

@sampengilly how to use that, can you provide the details of RangeSelectionRightDecorator ?

sampengilly commented 4 years ago

Hey, it's been a while since I did that workaround and I no longer have access to the codebase where I did it. From memory the left, right, and middle decorators were simple decorator implementations along the lines of the dot examples, but with a drawable to suit. The drawable was also the fiddliest part as well I think, getting it to work right without any margin gaps. I can reach out to someone and see if I can get a copy of those classes and the drawables that I can share.

CreatorB commented 4 years ago

Thanks, @sampengilly I will try and will wait too, maybe I can check yours, even I work with java nowadays

Pify commented 4 years ago

@sampengilly i have hard time to figure out how to use RangeSelectionMiddleDecorator, btw this is RangeSelectionRightDecorator that i have figured out,

`

class RangeSelectionRightDecorator(last: org.threeten.bp.LocalDate, rightDrawable: Drawable) :  DayViewDecorator {

        var drawable = rightDrawable
        var myDay = CalendarDay.from(last)

        override fun shouldDecorate(day: CalendarDay): Boolean {
              return day == myDay
        }

        override fun decorate(view: DayViewFacade) {
             view.setSelectionDrawable(drawable)
        }
 }

`

i havent done testing on this class if there is a mistake please let me know

sampengilly commented 4 years ago

That's essentially all it is. I managed to find that code:

private class RangeSelectionLeftDecorator(
        private val leftDate: LocalDate,
        private val leftDrawable: Drawable
) : DayViewDecorator {

    override fun shouldDecorate(day: CalendarDay): Boolean {
        return leftDate == day.date
    }

    override fun decorate(view: DayViewFacade) {
        view.setSelectionDrawable(leftDrawable)
        view.addSpan(ForegroundColorSpan(Color.WHITE))
    }

}

private class RangeSelectionMiddleDecorator(
        private val middleDates: Set<LocalDate>,
        private val middleDrawable: Drawable
) : DayViewDecorator {

    override fun shouldDecorate(day: CalendarDay): Boolean {
        return day.date in middleDates
    }

    override fun decorate(view: DayViewFacade) {
        view.setSelectionDrawable(middleDrawable)
        view.addSpan(ForegroundColorSpan(Color.BLACK))
    }

}

private class RangeSelectionRightDecorator(
        private val rightDate: LocalDate,
        private val rightDrawable: Drawable
) : DayViewDecorator {

    override fun shouldDecorate(day: CalendarDay): Boolean {
        return rightDate == day.date
    }

    override fun decorate(view: DayViewFacade) {
        view.setSelectionDrawable(rightDrawable)
        view.addSpan(ForegroundColorSpan(Color.WHITE))
    }

}

The color spans were to adjust text color from memory. It's been a while since I've looked at this.

Left Drawable:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:gravity="center_vertical|right" android:left="@dimen/cal_tile_height_half">
        <shape android:shape="rectangle">
            <solid android:color="@color/[surface color]"/>
        </shape>
    </item>
    <item android:gravity="center">
        <shape android:shape="oval">
            <solid android:color="@color/[decorator color]"/>
            <size android:height="@dimen/cal_tile_height" android:width="@dimen/cal_tile_height"/>
        </shape>
    </item>
</layer-list>

Middle drawable:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:gravity="center">
        <shape android:shape="rectangle">
            <solid android:color="@color/[surface color]"/>
        </shape>
    </item>
</layer-list>

Right drawable:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:gravity="center_vertical|left" android:right="@dimen/cal_tile_height_half">
        <shape android:shape="rectangle">
            <solid android:color="@color/[surface color]"/>
        </shape>
    </item>
    <item android:gravity="center">
        <shape android:shape="oval">
            <solid android:color="@color/[decorator color]"/>
            <size android:width="@dimen/cal_tile_height" android:height="@dimen/cal_tile_height"/>
        </shape>
    </item>
</layer-list>
Pify commented 4 years ago

@sampengilly how to use RangeSelectionDecorator class ? im trying to put it on calendarView.addDecorator() but i get Type mismatch. Required: DayViewDecorator! Found: RangeSelectionDecorator

sampengilly commented 4 years ago

See the applyForSelection() method in the earlier comment. Create the RangeSelectionDecorator and keep a reference to it, then call that method whenever the selection changes on the calendar. It's a bit of a misnomer as it's really more of a helper class