Reputation: 370
I am using diff util to improve performance in my recyclerview as opposed to calling notifyDataSetChanged(). The recyclerview has a header with some chips which can reorder the list by aplhabetical order, highester score etc
When I click the chip in the header and the list reorders its effecting my position. For example if I have the ordered by hightest score and score 100% is position 1 (after the header) and I click the chip to reorder in reverse. Score 100% will now be at the bottom of the list and 0% will be at the top. But now I have to scroll all the way back to the top to see the header chips again. I want the list to reorder but I dont want my on screen position to change.
Here is my adapter code:
class DigitalTestsResultsAdapter(
private val interaction: Interaction? = null,
private val dateUtil: DateUtil,
private val theme: ThemeModel?,
private val username: String?
) : ListAdapter<ResultResponseModel, RecyclerView.ViewHolder>(ResultsDiffCallBack()) {
private val itemViewTypeHeader: Int = 0
private val itemViewTypeItem: Int = 1
private var filteredList = emptyList<ResultResponseModel>()
private val adapterScope = CoroutineScope(Dispatchers.Default)
class ResultsDiffCallBack : DiffUtil.ItemCallback<ResultResponseModel>() {
override fun areItemsTheSame(
oldItem: ResultResponseModel,
newItem: ResultResponseModel
): Boolean {
return oldItem.certificateUrl == newItem.certificateUrl
}
@SuppressLint("DiffUtilEquals")
override fun areContentsTheSame(
oldItem: ResultResponseModel,
newItem: ResultResponseModel
): Boolean {
return oldItem == newItem
}
}
fun filterList(list: List<ResultResponseModel>, type: String) {
adapterScope.launch {
when (type) {
"courseName" -> {
filteredList = list.sortedBy { it.courseName }
}
"isCpd" -> {
filteredList = list.sortedBy { it.courseName }.sortedByDescending { it.isCPD }
}
"organisationName" -> {
filteredList = list.sortedBy { it.organisationName }
}
"roleName" -> {
filteredList = list.sortedBy { it.roleName }
}
"score" -> {
filteredList = list.sortedByDescending { it.score }
}
"submitTime" -> {
filteredList = list.sortedByDescending { it.submitTime }
}
}
withContext(Dispatchers.Main) {
submitList(filteredList)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
itemViewTypeHeader -> {
DigitalTestsResultsHeaderViewHolder(
RvDigitalTestResultsHeaderBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
itemViewTypeItem -> {
DigitalTestsResultsViewHolder(
RvDigitalTestsResultsBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
),
interaction = interaction
)
}
else -> throw ClassCastException("Unknown viewType $viewType")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is DigitalTestsResultsHeaderViewHolder -> {
holder.bind()
}
is DigitalTestsResultsViewHolder -> {
holder.bind(currentList[position])
}
}
}
override fun getItemViewType(position: Int): Int {
return if (position == 0) {
itemViewTypeHeader
} else {
itemViewTypeItem
}
}
override fun getItemCount(): Int {
return if (!currentList.isNullOrEmpty()) {
currentList.size
} else 0
}
inner class DigitalTestsResultsHeaderViewHolder
constructor(
private val binding: RvDigitalTestResultsHeaderBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind() {
with(binding) {
with(theme) {
userName.text = itemView.context.getString(R.string.hi_username, username)
userName.setTextColourHex(this?.textModel?.primaryColor)
chipCv.setCardBackgroundColourHex(this?.interfaceModel?.secondaryColor)
testsChipGroup.setOnCheckedChangeListener { _, checkedId ->
when (checkedId) {
R.id.chipCertified -> {
chipCertified.isChecked = true
filterList(currentList, "isCpd")
}
R.id.chipCourse -> {
chipCourse.isChecked = true
filterList(currentList, "courseName")
}
R.id.chipHighestScore -> {
chipHighestScore.isChecked = true
filterList(currentList, "score")
}
R.id.chipRecent -> {
chipRecent.isChecked = true
filterList(currentList, "submitTime")
}
R.id.chipRole -> {
chipRole.isChecked = true
filterList(currentList, "roleName")
}
R.id.chipSchoolName -> {
chipSchoolName.isChecked = true
filterList(currentList, "organisationName")
}
else -> {
}
}
}
}
}
}
}
inner class DigitalTestsResultsViewHolder
constructor(
private val binding: RvDigitalTestsResultsBinding,
private val interaction: Interaction?
) : RecyclerView.ViewHolder(binding.root) {
@SuppressLint("SetTextI18n")
fun bind(item: ResultResponseModel?) {
with(binding) {
with(theme) {
viewCertificateBtn.setOnClickListener {
interaction?.onItemSelected("certificateBtn", absoluteAdapterPosition, item)
}
retakeTestBtn.setOnClickListener {
interaction?.onItemSelected("retakeTestBtn", absoluteAdapterPosition, item)
}
resultsProgressBar.progress = item?.score?.toFloat() ?: 0f
if (isValidHex(item?.roleColour)) {
resultsProgressBar.circleProgressColor = Color.parseColor(item?.roleColour)
resultsProgressBar.pointerColor = Color.parseColor(item?.roleColour)
}
score.text = item?.score.toString() + "%"
title.text = item?.courseName
date.text = dateUtil.formatStringDateToDDMMYYYY(item?.submitTime)
role.text = item?.roleName
schoolName.text = item?.organisationName
title.setTextColourHex(this?.textModel?.primaryColor)
retakeTestBtn.setTextColourHex(this?.textModel?.primaryColor)
mainCv.setCardBackgroundColourHex(this?.interfaceModel?.secondaryColor)
roleCv.setCardBackgroundColourHex(item?.roleColour)
// Check if course is CPD and display CPD icon
if (item?.isCPD == true) cpdLogo.visibility =
View.VISIBLE else cpdLogo.visibility = View.INVISIBLE
}
}
}
}
interface Interaction {
fun onItemSelected(
tag: String,
position: Int,
result: ResultResponseModel?
)
}
}
Upvotes: 4
Views: 1740
Reputation: 21053
Its the notifyItemMove
which is causing this scrolling. Somehow LinearLayoutManager
causes List to scroll if 0th position is changed .
For your case 0th position is header which will be at same position regardless of sorting Order .
What you can do is keep the 0th item and Sort rest of the part inside your filterList
method.
I have tried it on sample and it worked for me. Here is a post which explains the similar thing.
"courseName" -> {
val updatedList = arrayListOf<ResultResponseModel>()
updatedList.add(list[0])
updatedList.addAll(list.subList(1,list.size).sortedBy { it.courseName })
filteredList = updatedList
}
This is just pseudo code to explain the idea. Give it a try .
Upvotes: 0
Reputation: 1295
register the adapter to observe to notify you when the data changes using registerAdapterDataObserver adapter.registerAdapterDataObserver()
and when the data changed scroll the recyclerView to the first item position
using recyclerview.scrollToPosition(position)
see also: Recycler view scroll to specific position and Diffutil in recycleview, making it autoscroll if a new item is added
Upvotes: 0