Home > database >  Kotlin, Recyclerview reusing items but updating item colour
Kotlin, Recyclerview reusing items but updating item colour

Time:04-14

I have a recyclerview that is being used to form a calendar. I have a custom flow that is used to populate 42 DayOfMonth tiles, and an observer to submit it to the adapter.

My aim was to have tiles from the next and previous month shown in grey (these are sometimes shown in the first and last row, depending on where the first day of the month lies).

enter image description here

When the user clicks on a tile in the next/previous month the flow updates and the calendar updates to center on that month. The tiles should adjust to their new positions and be coloured accordingly. However, if a tile is grey when it is created it does not change its colour. I assume the if statement defining its colour is not rechecked.

enter image description here

I assume this is because the recyclerview is reusing the tiles and not recreating them. This makes sense and the DiffUtil callback isnt actioned because the items are the same items (just in a different adapter position).

AdapterActivitiesCalendar

class AdapterActivitiesCalendar(
    private val listener: OnItemClickListener,
    private val viewLifecycleOwner: LifecycleOwner,
    private val dateItemSelected: LiveData<Long>
) : ListAdapter<DayOfMonthItem, AdapterActivitiesCalendar.DateItemViewHolder>(DiffCallItem()) {
 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DateItemViewHolder {
        return DateItemViewHolder(RvItemCalendarDayOfMonthTileBinding.inflate(LayoutInflater.from(parent.context), parent, false))
    }

    override fun onBindViewHolder(holder: DateItemViewHolder, position: Int) {
        holder.bind(getItem(position))
    }

    inner class DateItemViewHolder(private var binding: RvItemCalendarDayOfMonthTileBinding) : RecyclerView.ViewHolder(binding.root) {

        init {
            binding.apply {
                llItemHeader.setOnClickListener {
                    val position = adapterPosition
                    if (position != RecyclerView.NO_POSITION) {
                        val dayOfMonthItem = getItem(position)
                        listener.onClickDayOfMonthTile(dayOfMonthItem)
                    }
                }
            }
        }

        //Bind database information to item view
        fun bind(item: DayOfMonthItem) {
            binding.apply {

                tvDate.text = item.dayOfMonth.toString()

                @SuppressLint("ResourceAsColor")
                if (((adapterPosition < 14) && (item.dayOfMonth > 14)) || ((adapterPosition > 28) && (item.dayOfMonth < 14))) {
                    tvDate.setTextColor(R.color.colourGrey)
                    ivIndicatorEvent.imageAlpha = 25
                    ivIndicatorActivity.imageAlpha = 25
                } else{                  
                    //tvDate.setTextColor(R.color.colourBlack)
                    //ivIndicatorEvent.imageAlpha = 100
                    //ivIndicatorActivity.imageAlpha = 100
                }

                dateItemSelected.observe(viewLifecycleOwner) {
                    val position = adapterPosition
                    if (position != RecyclerView.NO_POSITION) {
                        val dayOfMonthItem = getItem(position)
                        //converts to day, rather than long to remove HH:mm:ss
                        if (convertLongToDate(dayOfMonthItem.timestamp) == convertLongToDate(dateItemSelected.value!!)) {
                            llItemHeader.setBackgroundResource(R.drawable.vec_border)
                        } else {
                            llItemHeader.setBackgroundResource(R.color.backgroundColour)
                        }
                    }
                }
            }
        }
    }

    // Fragment Interface
    interface OnItemClickListener {
        fun onClickDayOfMonthTile(dayOfMonthItem: DayOfMonthItem)
        fun onDragDayOfMonthTile(dayOfMonthItem: DayOfMonthItem)
        fun onHoldDayOfMonthTile(dayOfMonthItem: DayOfMonthItem)
    }

    // update list when changes are made to the repository flow
    class DiffCallItem : DiffUtil.ItemCallback<DayOfMonthItem>() {
        override fun areItemsTheSame(oldItem: DayOfMonthItem, newItem: DayOfMonthItem) =
            oldItem.timestamp == newItem.timestamp

        override fun areContentsTheSame(oldItem: DayOfMonthItem, newItem: DayOfMonthItem) =
            oldItem == newItem
    }

I've been working on the recyclerview and with DiffUtil to try to get it to recheck the position when updated but have had no luck.

Any suggestions are appreciated.

Edit:

ViewModel List

  val dayOfMonthList = dayOfMonthFlow.asLiveData()

Fragment Observer

viewModel.dayOfMonthList.observe(viewLifecycleOwner) {
     calendarAdapter.submitList(it)
}

CodePudding user response:

I'm pretty sure your areContentsTheSame needs to be improved. It will return true even when being moved to the next month, which means it won't make any changes to it. I'm not sure how to do it right and efficient but simply changing it to

override fun areContentsTheSame(oldItem: DayOfMonthItem, newItem: DayOfMonthItem) = false

should work.

Though you probably also need to comment this back in

                //tvDate.setTextColor(R.color.colourBlack)
                //ivIndicatorEvent.imageAlpha = 100
                //ivIndicatorActivity.imageAlpha = 100

CodePudding user response:

According to your images, when you change the month (Next or Previous). You need to update whole list of the adapter and need to call notifyDatasetChanged(). i am sure you are calling that.

But i you look at the code in bind. enter image description here

if (((adapterPosition < 14) && (item.dayOfMonth > 14)) || ((adapterPosition > 28) && (item.dayOfMonth < 14))) {
                    tvDate.setTextColor(R.color.colourGrey)
                    ivIndicatorEvent.imageAlpha = 25
                    ivIndicatorActivity.imageAlpha = 25
                } 

Above code changing alpha, means indicating this dates are not from the current month. you need to improve this conditions.

You need to check is from same month

fun isSameMonth(dayTime : Long, monthTime : Long) : Boolean
{
   //Create 2 instances of Calendar
    var cal1 = Calendar.getInstance();
    var cal2 = Calendar.getInstance();
    
    //set the given date in one of the instance and current date in the other
    cal1.setTimeInMillis(dayTime );
    cal2.setTimeInMillis(monthTime);
    
    //now compare the dates using methods on Calendar
    if(cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR)) {
        if(cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH)) {
           return true
        }
    }
    return false;
}

If you have date object or milliseconds in your item model,

if(isSameMonth(item.dayTime, currentSelectedMonthTime))
{
         tvDate.setTextColor(R.color.colourGrey)
         ivIndicatorEvent.imageAlpha = 25
         ivIndicatorActivity.imageAlpha = 25
}
  • Related