stefanodp91
stefanodp91

Reputation: 3

Recyclerview multiple item selector

I'm trying to create a calendar view for a reservation app. I need to show to the user which days are already in use.

For this i like to create a selector between continuous days like this:

calendar days selector

For the calendar view i created a RecyclerView using java.util.Calendar as datasource, every day is a ViewHolder.

Adapter:

    class CalendarAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    var list = emptyArray<CalendarItem>()

    override fun getItemViewType(position: Int): Int {
        return list[position].viewType?.asInt ?: super.getItemViewType(position)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)

        return when (viewType) {
            ViewType.CURRENT_DAY.asInt -> {
                val binding = ViewCurrentDayBinding.inflate(inflater, parent, false)
                CurrentDayHolder(binding)
            }
            ViewType.DAY_OF_MONTH.asInt -> {
                val binding = ViewDayOfMonthBinding.inflate(inflater, parent, false)
                DayOfMonthHolder(binding)
            }
            ViewType.DAY_OF_WEEK.asInt -> {
                val binding = ViewDayOfWeekBinding.inflate(inflater, parent, false)
                DayOfWeekHolder(binding)
            }
            ViewType.SELECTED_DAY.asInt -> {
                val binding = ViewSelectedDayBinding.inflate(inflater, parent, false)
                SelectedDayHolder(binding)
            }
            ViewType.MOCK.asInt -> {
                val binding = ViewMockDayBinding.inflate(inflater, parent, false)
                MockDayHolder(binding)
            }
            else -> {
                val binding = ViewMockDayBinding.inflate(inflater, parent, false)
                MockDayHolder(binding)
            }
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (getItemViewType(position)) {
            ViewType.CURRENT_DAY.asInt -> {
                (holder as CurrentDayHolder).bind(list[position], position, callback)
            }
            ViewType.DAY_OF_MONTH.asInt -> {
                (holder as DayOfMonthHolder).bind(list[position], position, callback)
            }
            ViewType.DAY_OF_WEEK.asInt -> {
                (holder as DayOfWeekHolder).bind(list[position])
            }
            ViewType.SELECTED_DAY.asInt -> {
                (holder as SelectedDayHolder).bind(list[position], position, callback)
            }
            ViewType.MOCK.asInt -> {
                (holder as MockDayHolder).bind(list[position])
            }
        }
    }

    override fun getItemCount(): Int {
        return list.size
    }

    private val callback: (index: Int) -> Unit = {
        // find current selected index and unselect
        val currentSelectedIndex =
            list.indices.find { el -> list[el].viewType == ViewType.SELECTED_DAY }
        if (currentSelectedIndex != null) {
            list[currentSelectedIndex].viewType = list[currentSelectedIndex].defaultViewType
            notifyItemChanged(currentSelectedIndex)
        }
        // select the new index
        list[it].viewType = ViewType.SELECTED_DAY
        notifyItemChanged(it)
    }
}

ViewHolder:

class CurrentDayHolder(var binding: ViewCurrentDayBinding) :
    RecyclerView.ViewHolder(binding.root) {

    fun bind(
        calendarItem: CalendarItem,
        index: Int,
        callback: (index: Int) -> Unit
    ) {
        binding.day.text = calendarItem.label

        binding.root.setOnClickListener {
            callback.invoke( index)
        }
    }
}

The full project is avaible on GitHub

How can i archive my goal?

I also thought not to use a RecyclerView and create a custom view directly, with the obvious complexities of the case.

I'm sure there is a way to do this with the RecyclerView as well

Upvotes: 0

Views: 183

Answers (2)

stefanodp91
stefanodp91

Reputation: 3

I solved the problem using your suggestions: I managed a state for each element of the recyclerview indicating the selection of start, destination and the intermediate value when I create the datasource of the elements. then in each viewholder I change the corresponding background.

Full solution available on the example code in the question.

Here a screen of the result.

enter image description here

thanks everyone for the help

Upvotes: 0

cactustictacs
cactustictacs

Reputation: 19544

With a RecyclerView you'd need to customise the item layout so you can display the different styles (normal item, start of selection, middle of selection, end of selection, all the stripey versions of those) and then calculate the state of each item in onBindViewHolder so you can style it correctly, e.g. by having different background drawables you can switch between. But since these are all separate views, you might have trouble getting those stripes to line up correctly where one view ends and the adjacent one starts.

Also you could just use a GridLayout or something for this - no need for a RecyclerView when you're displaying all the items at once. You might want to consider a custom view with a grid/table where each item is a TextView or a borderless Button (better!), where you have another View layer on top which is a custom view that draws the selections.

But since you'd have to draw the text as well (e.g. the white date over the orange highlight) you might find it easier to just make the whole thing as a custom view, where you're positioning all the text yourself. That's one of the benefits of custom views - you get more control over how it draws itself. You could always try subclassing an existing calendar widget! Use the work that's already been done

Upvotes: 1

Related Questions