Need the correct clicked position within a RecyclerView - android

When I click on an image within a RecyclerView, I try to enlarge it. So far everything works wonderfully. The only problem I have now is that the image of its output position is always the same. In other words, if I click on the 3rd picture, the 2nd picture enlarges with the correct content but in the wrong position. The same applies to the click on the 1st picture.
This is my AdapterDetail.kt class:
class AdapterDetail(val context: Context, private val listImg:ArrayList<String>,private var listen: CustomerAdapter.OnItemClickListener): RecyclerView.Adapter<AdapterDetail.MyCustomViewHolder>() {
...
inner class MyCustomViewHolder(view: View):RecyclerView.ViewHolder(view){
val preview_img:ImageView = view.findViewById(R.id.preview_img)
fun bind(item: String){
imgView = itemView.findViewById(R.id.preview_img)
imgView.setOnClickListener {
listen.onItemClick(imgView, adapterPosition)
}
}
}
override fun onBindViewHolder(holder: MyCustomViewHolder, position: Int) {
val item = listImg[position]
holder.bind(item)
}
In the Activity i need the correct ImageView position to inflate them:
adapterDetail = AdapterDetail(this, house!!.objektBilder as ArrayList<String>,object: CustomerAdapter.OnItemClickListener{
override fun onItemClick(view: View, position: Int) {
//Here I need the ImageView that was clicked on in the RecyclerView
var viewPosition = view
zoomImageFromThumb(viewPosition, house!!.objektBilder!![position])
}
})

Sounds like an off by one issue, since you are using the deprecated adapterPosition. Use bindingAdapterPosition instead, which gives the position as RecyclerView sees it:
listen.onItemClick(imgView, bindingAdapterPosition)

Related

I want to update my room DB field and also disable swipe in recyclerview item at the same time in Android how to achieve this?

I have a recyclerview where when I swipe left I open a bottom sheet and select one value and then I need to update my room DB table and also disable swipe.
But currently im not able to do both things at the same time, only the disable swipe is working not able to update recyclerview item
swipeController = SwipeController(object:SwipeControllerActions() {
override fun onRightClicked(position:Int) {
val bottomSheetFragment = BottomModalFragment()
bottomSheetFragment.show(parentFragmentManager, bottomSheetFragment.tag)
var homeTaskModel: HomeTaskModel = homeAdapter.getWordAtPosition(position)!!
TASK_STATUS.observe(viewLifecycleOwner, Observer {
homeTaskModel.task_status = it //passing value
homeViewModel.updateSwipeType(1,0) //disable swipe
homeAdapter.notifyItemChanged(position)
})
}
Below is my onbindviewholder code
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val tasks = taskslist[position]
var task__status = tasks.task_status
holder.Taskbinding.taskdata = taskslist[position]
holder.Taskbinding.txtTaskname.text = tasks.taskname
holder.Taskbinding.txtTaskStatus.text = task__status
//hide unhide navigation imageview
if (task__status.equals("Travel To Site"))
holder.Taskbinding.imgNavigation.visibility = View.VISIBLE
else
holder.Taskbinding.imgNavigation.visibility = View.GONE
}
getItemViewType() is called before onBindViewHolder where I am updating my views
override fun getItemViewType(position: Int): Int {
return taskslist.get(position).swipe_type
}
How to acheieve both the scenarios???
create refreshList in adapter class the refresh the list
fun refreshList(list:arrayOf()){
tasklist = list
}
call this function from mainActivity where you want to notify the adapter
homeAdapter.refreshList(pass your updated list here)
thats it!!

How can I implement a comment section using Recycler View

I'm trying to implement a comment section that allows the user to post and reply to another comment.
I have a list of comments, each comment has a list of replies this list may be null if there are no replies.
At first, I thought about using two recycler views, then I saw this post that says that using 2 RecyclerViews is not the best approach.
The comment and reply share the same layout, but the reply has a margin-left of 24dp.
Problem
My problem begins on onBindViewHolder the position will go from 0 to comments + replies
For example:
A list containing 2 objects with 5 replies each, in onBindViewHolder
position = 3 would be the reply[2] of comments[0] = comments[0].reply[2]
position = 6 would be comments[1]
How can determine the comment index and reply index from the position? I feel lost here
var comments = listOf<CommentModel>()
set(value) {
field = value
notifyDataSetChanged()
}
class ViewHolder(var binding: ItemForumCommentBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: CommentModel){
binding.commentModel = item
}
fun bind(item: ReplyModel){
binding.commentModel = item
(binding.commentGuideline.layoutParams as ConstraintLayout.LayoutParams).guideBegin = 24.px
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int){
//Bind comment or reply according to the position
//holder.bind(comments[x].replys[y]) or holder.bind(comments[x])
}
You can create some list that contains both comment and replies, and use it as single data provider for the list, e.g.:
var commentsWithReplies = mutableListOf<Any>()
var comments = listOf<CommentModel>()
set(value) {
field = value
commentsWithReplies.clear()
value.forEach {
commentsWithReplies.add(it)
commentsWithReplies.addAll(it.replies)
}
notifyDataSetChanged()
}
Then in getItemCount:
override fun getItemCount(): Int = commentsWithReplies.size
and in onBindViewHolder:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = commentsWithReplies[position]
if (item is CommentModel) {
holder.bind(commentsWithReplies[position] as CommentModel)
} else if (item is ReplyModel) {
holder.bind(commentsWithReplies[position] as ReplyModel)
}
}
P.S.
Of course, it's the very simple solution. You can refactor it (at least, use custom interface, not Any for the generic list)

Listener of a RecyclerView being apply to others RecyclersView

Well, I'm working with Kotling right now, and I've a listener in onBindViewHolder inside the ViewHolderAdapter, but, the behavior of this listener is being applied at each 10 positions. The behavior is just to change a image color using a color filter when I touch that image. Pretty simple.
I've tried to use holder.setIsRecyclable(false) but when the Recycler get out of the screen, the image gets to it's normal design, and not more red.
Here's my ViewHolderAdapter
class ViewHolderAdapter(private var products:List<EachProduct>):RecyclerView.Adapter<ViewHolderAdapter.ViewHolder>(){
override fun onCreateViewHolder(parent:ViewGroup,viewType:Int):ViewHolder{
val view=LayoutInflater.from(parent.context).inflate(R.layout.recyclerview_layout,parent,false)
return ViewHolder(view)
}
override fun getItemCount():Int{
return products.size
}
override fun onBindViewHolder(holder:ViewHolder,position:Int){
//holder.setIsRecyclable(false)
holder.likeImage.setOnClickListener{
holder.likeImage.setColorFilter(Color.RED)
}
}
class ViewHolder(itemView:View):RecyclerView.ViewHolder(itemView){
var likeImage:ImageView=itemView.LikeImage
}
}
I expected that only the image of the touched RecyclerView change, not, for example, at every 10 rows. Can someone provide me an example on how to set a listener inside a image in a RecyclerView?
ImageView.setColorFilter() assigns a ColorFilter object to a field of the ImageView. This means that when your ViewHolder is recycled and re-bound to a new image, the ColorFilter is still in place and will apply to whatever image is loaded in.
Additionally, there's no guarantee that the same ViewHolder will be used for the same position if you scroll up and down.
What you need to do is somehow store inside your data set whether or not the image should have a filter applied to it. For example, maybe you store the last clicked position:
var lastClickedPosition = -1
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if (position == lastClickedPosition) {
holder.likeImage.setColorFilter(Color.RED)
} else {
holder.likeImage.setColorFilter(null)
}
holder.likeImage.setOnClickListener {
val previousPosition = lastClickedPosition
lastClickedPosition = position
notifyItemChanged(position)
if (previousPosition != -1) {
notifyItemChanged(previousPosition)
}
}
}
Or maybe you want to be able to apply the filter to multiple rows at once. Then perhaps you could add a field to whatever list backs your adapter, or create a mutable set of highlighted positions:
val clickedPositions = mutableSetOf<Int>()
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if (clickedPositions.contains(position)) {
holder.likeImage.setColorFilter(Color.RED)
} else {
holder.likeImage.setColorFilter(null)
}
holder.likeImage.setOnClickListener {
clickedPositions.add(position)
notifyItemChanged(position)
}
}

Getting EditTexts Values From RecyclerView

I am building an app where user is required to fill some data in order to post something, so a fragment consists of EditText, radio buttons and Spinner along with RecyclerView which dynamically renders a number of child layout containing TextView and EditText.
So when user select category from Spinner, some properties which are related to that category are displayed in RecyclerView and user can optionally fill some of them.
I have tried to implement this functionality using callback and TextWatcher but I don't get the values I want.
CallBack
interface PropertiesCallback {
fun addProp(position: Int, title: String, value: String)
}
Adapter
class PropertiesAdapter(private val propertiesCallback: PropertiesCallback)
: RecyclerView.Adapter<PropertiesAdapter.ViewHolder>() {
private var list = listOf<CategoriesAndSubcategoriesQuery.Property>()
fun setData(listOfProps: List<CategoriesAndSubcategoriesQuery.Property>) {
this.list = listOfProps
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.z_property_input, parent, false)
return ViewHolder(view)
}
override fun getItemCount(): Int = list.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(list[position], position)
}
inner class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
private val label: TextView = view.findViewById(R.id.label)
private val input: EditText = view.findViewById(R.id.input)
fun bind(prop: CategoriesAndSubcategoriesQuery.Property, position: Int) {
label.text = prop.title()
prop.hint()?.let { input.hint = prop.hint() }
input.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
propertiesCallback.addProp(position, prop.title(), input.text.toString())
}
})
}
}
}
In Fragment
private var propertiesList = mutableListOf<CategoriesAndSubcategoriesQuery.Property>()
private var propertiesInputList = mutableListOf<ProductPropertiesInput>()
private fun setUpSubcategorySpinner() {
subcategoriesAdapter = ArrayAdapter(
this#AddProductFragment.context!!,
android.R.layout.simple_spinner_item,
subcategoriesList
)
//Subcategories
subcategoriesAdapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line)
subcategory_spinner.adapter = subcategoriesAdapter
subcategory_spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
subcategoryId = subcategoriesList[position].id()
//Adding properties
subcategoriesList[position].properties()?.let {
//Clear previous properties data of another subcategory.
propertiesInputList.clear()
propertiesList.clear()
propertiesList.addAll(it)
propertiesAdapter.setData(propertiesList)
propertiesAdapter.notifyDataSetChanged()
}
}
override fun onNothingSelected(parent: AdapterView<*>) {}
}
}
overide
override fun addProp(position: Int, title: String, value: String) {
val prop = ProductPropertiesInput
.builder()
.title(title)
.value(value)
.build()
propertiesInputList.add(prop)
//Log.d(TAG, "prop: ${prop.title()} : ${prop.value()}")
}
submit fun
private fun submitProduct() {
//Initializing properties.
val properties: Any
//The keys needed in final list.
val propertyKeys = propertiesList.map { it.title() }
//Removing objects which keys are not needed.
propertiesInputList.removeAll { it.title() !in propertyKeys }
Log.d(TAG, "propertiesInputList: $propertiesInputList")
//Removing duplicate and assign result in properties var.
properties = propertiesInputList
.distinctBy { it.title() }
Log.d(TAG, "properties: $properties")
for (prop in properties) {
Log.d(TAG, "properties , title: ${prop.title()}, value: ${prop.value()} ")
}
}
Above codes is intended to work as. When user types a value in one of the EditText in RecyclerView the value will be taken to fragment and added to an object which takes title and value and then added to propertiesInputList.
Problem 1: propertiesInputList will have so many duplicates objects with the same title and I thought the best solution was using distinctBy.
Problem 2: When user fills a number of EditText which are related to let's say category1 and changes his mind and select another category from Spinner. The previous values which are not part of new chosen category remain in propertiesInputList list. So I thought the best solution was to clear propertiesInputList and using removeAll with the titles related to category to filter unwanted objects.
But now I get only the first letter user types. If user types shoes I get s. So it seems distinctBy returns the first object but I want to get exactly last word user typed and if the user typed and erased everything I want blank.
Is there a better solution to handle this? Like looping recyclerView only when user press submit instead of TextWatcher? Or which part should I fix to make this work?
I don't completely understand what you are trying to achieve here. EditTexts inside a RecyclerView is generally not a good idea for following reasons.
When the recyclerView is scrolled, you would want to preserve the
text added by the user for that particular field/item and show it
correctly when the user scrolls back.
When you add a TextWatcher to an EditText, you also need to remove it when the view is recycled or the view holder is bound again. Otherwise, you will end up with multiple listeners and things will go wrong.
For the other question that you have,
But now I get only the first letter user types. If user types shoes I get s
That's by design. TextWatcher would emit event every time a character is entered. So you would get s, sh, sho, shoe, shoes. So you can not take an action on this data because the user is still adding something to that field.
So,
You don't know when the user has stopped adding the text to the EditText (or whether user is done). You could use something like debounce but that is complicated. You should give a button to the user. Take the value when the user taps the button.
I am assuming you have multiple edittexts in the RecyclerView. So you would need to store the values for each edittext because the recyclerview will re-use the views and you'll lose the data. You could do that in your adapter's onViewRecycled callback. Keep a map of id -> string where you store this data and retrieve when the view holder is bound.
You could also use a TextWatcher but you would have detach it before attaching a new one or in onViewRecycled.
Update:
If I had something like this, I would use a ScrollView with a vertical LinearLayout (for simplicity) and add EditText based on the requirements. If you want to add TextWatcher, you'd need some kind of stable id.
class EditTextContainer : LinearLayout {
private val views = mutableListOf<EditText>()
private val textWatchers = hashMapOf<Int, TextWatcher>()
... constructor and bunch of stuff
fun updateViews(items: List<Item>, textCallback: (id, text) -> Unit) {
// Remove text watchers
views.forEach { view ->
view.removeTextWatcher(textWatchers[view.id])
}
// More views than required
while (views.size > items.size) {
val view = views.removeAt(views.size-1)
removeView(view)
}
// Less views than required
while (view.size < items.size) {
val view = createView()
view.id = View.generateViewId()
addView(view, createParams()) // Add this view to the container
views.add(view)
}
// Update the views
items.forEachIndexed { index, item ->
val editText = views[item]
// Update your edittext.
addTextWatcher(editText, item.id, textCallback)
}
}
private fun createView(): EditText {
// Create new view using inflater or just constructor and return
}
private fun createParams(): LayoutParams {
// Create layout params for the new view
}
private fun addTextWatcher(view, itemId, textCallback) {
val watcher = create text watcher where it invokes textCallback with itemId
view.addTextWatcher(watcher)
textWatchers[view.id] = watcher
}
}
Your inputs are less to identify the issue. I guess you are making some data collection application with the list of edit text.
There is a an issue when you were using the edit text in recycler list.
When you scroll down the bottom edit text in the recycler view will be filled with already filled edit text value, even though you user is not filled.
As a work around You can create some sparse array any data structure which will best suitable for you, that can map you position and value
like
mPropertyValue[] = new String [LIST_SIZE]. , assuming that position of ur list item matches with index of array.
Try updating the index with the value of text watcher
mPropertyValue[POSITION] = YOUR_EDIT_TEXT_VALUE
When you want to initialize your edit text use the value by mPropertyValue[POSITION]
You can always make sure that your edit text will be having the right value by this .
i face like this problem in my java code and that was the solution
for (int i = 0; i < list.size(); i++) {
(put her the getter and setter class) mylist = list.get(i);
//use the getter class to get values and save them or do what ever you want
}

How to visually stay on same scroll position, upon unknown number of RecyclerView data changes (insertion, deletion, updates)?

Background
In case your RecyclerView gets new items, it is best to use notifyItemRangeInserted, together with unique, stable id for each item, so that it will animate nicely, and without changing what you see too much:
As you can see, the item "0", which is the first on the list, stays on the same spot when I add more items before of it, as if nothing has changed.
The problem
This is a great solution, which will fit for other cases too, when you insert items anywhere else.
However, it doesn't fit all cases. Sometimes, all I get from outside, is : "here's a new list of items, some are new, some are the same, some have updated/removed" .
Because of this, I can't use notifyItemRangeInserted anymore, because I don't have the knowledge of how many were added.
Problem is, if I use notifyDataSetChanged, the scrolling changes, because the amount of items before the current one have changed.
This means that the items that you look at currently will be visually shifted aside:
As you can see now, when I add more items before the first one, they push it down.
I want that the currently viewable items will stay as much as they can, with priority of the one at the top ("0" in this case).
To the user, he won't notice anything above the current items, except for some possible end cases (removed current items and those after, or updated current ones in some way). It would look as if I used notifyItemRangeInserted.
What I've tried
I tried to save the current scroll state or position, and restore it afterward, as shown here, but none of the solutions there had fixed this.
Here's the POC project I've made to try it all:
class MainActivity : AppCompatActivity() {
val listItems = ArrayList<ListItemData>()
var idGenerator = 0L
var dataGenerator = 0
class ListItemData(val data: Int, val id: Long)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
val inflater = LayoutInflater.from(this#MainActivity)
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
return object : RecyclerView.ViewHolder(inflater.inflate(android.R.layout.simple_list_item_1, parent, false)) {}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder?, position: Int) {
val textView = holder!!.itemView as TextView
val item = listItems[position]
textView.text = "item: ${item.data}"
}
override fun getItemId(position: Int): Long = listItems[position].id
override fun getItemCount(): Int = listItems.size
}
adapter.setHasStableIds(true)
recyclerView.adapter = adapter
for (i in 1..30)
listItems.add(ListItemData(dataGenerator++, idGenerator++))
addItemsFromTopButton.setOnClickListener {
for (i in 1..5) {
listItems.add(0, ListItemData(dataGenerator++, idGenerator++))
}
//this is a good insertion, when we know how many items were added
adapter.notifyItemRangeInserted(0, 5)
//this is a bad insertion, when we don't know how many items were added
// adapter.notifyDataSetChanged()
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.user.recyclerviewadditionwithoutscrollingtest.MainActivity">
<Button
android:id="#+id/addItemsFromTopButton" android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" android:layout_marginRight="8dp"
android:text="add items to top" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="#+id/recyclerView"/>
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView" android:layout_width="0dp" android:layout_height="0dp"
android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" android:layout_marginStart="8dp"
android:layout_marginTop="8dp" android:orientation="vertical"
app:layoutManager="android.support.v7.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"/>
</android.support.constraint.ConstraintLayout>
The question
Is it possible to notify the adapter of various changes, yet let it stay on the exact same place?
Items that are viewed currently would stay if they can, or removed/updated as needed.
Of course, the items' ids will stay unique and stable, but sadly the cells size might be different from one another.
EDIT: I've found a partial solution. It works by getting which view is at the top, get its item (saved it inside the viewHolder) and tries to scroll to it. There are multiple issues with this though:
If the item was removed, I will have to somehow scroll to the next one, and so on. I think in the real app, I can manage to do it. Wonder if there is a better way though.
Currently it goes over the list to get the item, but maybe in the real app I can optimize it.
Since it just scrolls to the item, if puts it at the top edge of the RecyclerView, so if you've scrolled a bit to show it partially, it will move a bit:
Here's the new code :
class MainActivity : AppCompatActivity() {
val listItems = ArrayList<ListItemData>()
var idGenerator = 0L
var dataGenerator = 0
class ListItemData(val data: Int, val id: Long)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val adapter = object : RecyclerView.Adapter<ViewHolder>() {
val inflater = LayoutInflater.from(this#MainActivity)
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder {
return ViewHolder(inflater.inflate(android.R.layout.simple_list_item_1, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val textView = holder.itemView as TextView
val item = listItems[position]
textView.text = "item: ${item.data}"
holder.listItem = item
}
override fun getItemId(position: Int): Long = listItems[position].id
override fun getItemCount(): Int = listItems.size
}
adapter.setHasStableIds(true)
recyclerView.adapter = adapter
for (i in 1..30)
listItems.add(ListItemData(dataGenerator++, idGenerator++))
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
addItemsFromTopButton.setOnClickListener {
for (i in 1..5) {
listItems.add(0, ListItemData(dataGenerator++, idGenerator++))
}
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
val holder = recyclerView.findViewHolderForAdapterPosition(firstVisibleItemPosition) as ViewHolder
adapter.notifyDataSetChanged()
val listItemToGoTo = holder.listItem
for (i in 0..listItems.size) {
val cur = listItems[i]
if (listItemToGoTo === cur) {
layoutManager.scrollToPositionWithOffset(i, 0)
break
}
}
//TODO think what to do if the item wasn't found
}
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var listItem: ListItemData? = null
}
}
I would solve this problem using the DiffUtil api. DiffUtil is meant to take in a "before" and "after" list (that can be as similar or as different as you want) and will compute for you the various insertions, removals, etc that you would need to notify the adapter of.
The biggest, and nearly only, challenge in using DiffUtil is in defining your DiffUtil.Callback to use. For your proof-of-concept app, I think things will be quite easy. Please excuse the Java code; I know you posted originally in Kotlin but I'm not nearly as comfortable with Kotlin as I am with Java.
Here's a callback that I think works with your app:
private static class MyCallback extends DiffUtil.Callback {
private List<ListItemData> oldItems;
private List<ListItemData> newItems;
#Override
public int getOldListSize() {
return oldItems.size();
}
#Override
public int getNewListSize() {
return newItems.size();
}
#Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return oldItems.get(oldItemPosition).id == newItems.get(newItemPosition).id;
}
#Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return oldItems.get(oldItemPosition).data == newItems.get(newItemPosition).data;
}
}
And here's how you'd use it in your app (in java/kotlin pseudocode):
addItemsFromTopButton.setOnClickListener {
MyCallback callback = new MyCallback();
callback.oldItems = new ArrayList<>(listItems);
// modify listItems however you want... add, delete, shuffle, etc
callback.newItems = new ArrayList<>(listItems);
DiffUtil.calculateDiff(callback).dispatchUpdatesTo(adapter);
}
I made my own little app to test this out: each button press would add 20 items, shuffle the list, and then delete 10 items. Here's what I observed:
When the first visible item in the "before" list also existed in the "after" list...
When there were enough items after it to fill the screen, it stayed in place.
When there were not, the RecyclerView scrolled to the bottom
When the first visible item in the "before" list did not also exist int he "after" list, the RecyclerView would try to keep whichever item that did exist in both "before" + "after" and was closest to the first visible position in the "before" list in the same position, following the same rules as above.

Categories

Resources