material-calendarview icon indicating copy to clipboard operation
material-calendarview copied to clipboard

How to set different drawables in same selector

Open mpierucci opened this issue 6 years ago • 8 comments

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!

mpierucci avatar Dec 13 '18 01:12 mpierucci

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 avatar Jun 13 '19 05:06 sampengilly

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 ?

CreatorB avatar Jul 26 '20 16:07 CreatorB

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.

sampengilly avatar Jul 27 '20 08:07 sampengilly

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

CreatorB avatar Jul 27 '20 09:07 CreatorB

@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

Pify avatar Oct 23 '20 07:10 Pify

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>

sampengilly avatar Oct 24 '20 07:10 sampengilly

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

Pify avatar Oct 26 '20 06:10 Pify

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

sampengilly avatar Oct 26 '20 07:10 sampengilly