Kotlin,Recyclerview 重用项目但更新项目颜色

Kotlin, Recyclerview reusing items but updating item colour

我有一个 recyclerview 用于形成日历。我有一个自定义流程,用于填充 42 DayOfMonth 个图块,以及一个将其提交给适配器的观察者。

我的目标是让下个月和上个月的图块显示为灰色(这些有时显示在第一行和最后一行,具体取决于该月第一天所在的位置)。

当用户单击 next/previous 月份的磁贴时,流程会更新,日历也会更新以该月为中心。瓷砖应调整到新位置并相应地着色。但是,如果一个图块在创建时是灰色的,它不会改变它的颜色。我假设没有重新检查定义其颜色的 if 语句。

我认为这是因为 recyclerview 正在重复使用图块而不是重新创建它们。这是有道理的,并且 DiffUtil 回调没有被执行,因为这些项目是相同的项目(只是在不同的适配器位置)。

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
    }

我一直在研究 recyclerview 并使用 DiffUtil 尝试让它在更新时重新检查位置,但没有成功。

如有任何建议,我们将不胜感激。

编辑:

视图模型列表

  val dayOfMonthList = dayOfMonthFlow.asLiveData()

片段观察者

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

我很确定您的 areContentsTheSame 需要改进。即使移动到下个月,它也会 return true,这意味着它不会对其进行任何更改。我不确定如何正确有效地完成它,只是将其更改为

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

应该可以。

尽管您可能还需要在

中对此进行评论
                //tvDate.setTextColor(R.color.colourBlack)
                //ivIndicatorEvent.imageAlpha = 100
                //ivIndicatorActivity.imageAlpha = 100

根据您的图像,当您更改月份(下一个或上一个)时。 您需要更新适配器的整个列表并需要调用 notifyDatasetChanged()。我确定你是这么叫的。

但是你看看bind中的代码。

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

以上代码更改 alpha,表示此日期不是当前月份。你需要改善这个条件。

您需要检查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(isSameMonth(item.dayTime, currentSelectedMonthTime))
{
         tvDate.setTextColor(R.color.colourGrey)
         ivIndicatorEvent.imageAlpha = 25
         ivIndicatorActivity.imageAlpha = 25
}

多做一点测试。当在观察者中调用 submit() 时,现有列表和新列表(图像中的 1-7)中的 dayOfMonth tiles 没有变化(因此每个项目的 if 语句不是 运行更新文字颜色)。

根据我的阅读和尝试,一个不完美的解决方案是在提交新列表之前从 RecyclerView 适配器中清除现有列表。这意味着不会比较列表,并且在提交新列表时将创建所有项目。我认识到这并不理想,如果列表非常大,可能会产生问题,但是对于 42 个项目,它似乎工作得很好。

所需的代码更改很简单:

观察者

viewModel.dayOfMonthList.observe(viewLifecycleOwner) {
     calendarAdapter.submitList(null) //added this line
     calendarAdapter.submitList(it)
}