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)
}
我有一个 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)
}