I have a item in RecyclerView which does not need to be updated when I call notifyDataSetChanged.
But I didn't find any methods to make the specified item avoid updated. Is it possible to do this ?
I need to do this because the item is a WebView, it had loaded a html page, if I called notifyDataSetChanged, this item will flash.
The reason why I post this question is avoiding the flash of the WebView when notifyDataSetChanged (see this quesion). After the failure of using notifyItemRangeXXX methods, I post this question.
But after checking your answers and comments, it seems that it's impossible to avoid updating when using notifyDataSetChanged.
Yes, its actually possible to do so. notifyDataSetChanged() notifies the RecyclerView that all the elements in the data set has changed.
As you do not want that to happen and exclude specific items.
for(int i = 0; i< mAdapter.getItemCount(); i++){
if(i != itemPositionToExclude){
mAdapter.notifyItemChanged(i);
}
}
So, we need to loop through all the elements in the adapter and call notifyItemChanged() only for those positions which you do not want to exclude.
The purpose of notifyDataSetChanged IS to update ALL items. Don't use it if you don't want this behavior!
The correct approach would be to only update the data you've changed. There are some methods which will help you to only invalidate the desired data:
notifyItemChanged(int position)
notifyItemInserted(int position)
notifyItemMoved(int fromPosition, int toPosition)
notifyItemRangeChanged(int positionStart, int itemCount)
notifyItemRangeRemoved(int positionStart, int itemCount)
notifyItemRemoved(int position)
You should think in different way - which items need to be updated. To update UI of item, after data has changed use method notifyitemchanged
Related
I'm using a RecyclerView and .notifyItemInserted only inserts one item into the RecyclerView. How would I do that say, for multiple items? I want to AVOID using .notifyDataSetChanged().
Say I have 25 items, I want to insert another 20, what method would I call?
You can use notifyItemRangeInserted to notify for multiple insertion.
void notifyItemRangeInserted (int positionStart, int itemCount)
According official documentation
Notify any registered observers that the currently reflected itemCount
items starting at positionStart have been newly inserted. The items
previously located at positionStart and beyond can now be found
starting at position positionStart + itemCount.
I'm trying to implement an autoplay feature for video items in a RecyclerView (linear vertical layout). I can't figure out how to know when a certain item is currently on/off the screen so I can autoplay/pause the video. If I put the code in onBindViewHolder method all videos start playing simultaniously. Couldn't find a solution by googling it either. Help, please!
For Recyclerview you should rely on your layout manager to give you this information.
https://developer.android.com/reference/android/support/v7/widget/GridLayoutManager
or
https://developer.android.com/reference/android/support/v7/widget/LinearLayoutManager
Assign a layout to the RecyclerView when you assign the adapter. Then use it to see what is visible.
Let me help a bit more with some Psedo Kotlin code for you to help with the player aspect.
Let's pretend you have some object that is bound into each row that can trigger the playing and has a unique ID. Let's call that an ActiveRowPlayer for this example.
NOTE*
If you are using databinding, it is simple to bind your video player playing content to a property in your model that is populating the row, but that's different story for a different post.
You can make an interface like:
interface IActivePlayerUpdater{
fun onUpdateCurrentPlayer
}
You can make a helper method like:
in your activity, you can implement an interface like :IActivePlayerUpdater and override the methods for it.
override fun onUpdateCurrentPlayer(){
var activeRowPlayer = recyclerView.layoutManager.findFirstCompletelyVisibleItemPosition()
if(activeRowplayer.someID != currentRowPlayer.someID){
currentRowPlayer.stopPlaying
currentRowPlayer = activeRowPlayer
activeRowPlayer.startPlaying
}
}
Then pass it into your adapter and just monitor your onBind method and anytime a new onBind is called that means the content has moved enough to trigger a new row item.
MyAdapterConstructor(IActivePlayerUpdater myCallback)
fun recyclerView.onBindMethod(stuffThatComesHere){
//do normal stuff
myCallback.onUpdateCurrentPlayer()
}
Keep in mind, this is just pseudo to help you on your journey. not intended to be direct copy and paste.
----NOTE* REQUESTED FROM COMMENT TO SUPPLY HOW TO TOUCH VIEWMODEL FROM OUTSIDE OF ADAPTER---
#Goran, this example I had setup a long time ago to avoid
notifyDataSetChanged on selection changed to toggle a checkbox for
each item. I eventually moved to a better option, but you asked how do
you get the viewmodel, here is a sample. rvCameraRoll was my
recyclerView, I was using it to display camera media, but that is not
relevant, just focus on getting the viewModel piece.
The only part you should care about is getting the ViewHolder, I just left the rest there in case it helps you with anything else.
int count = rvCameraRoll.getChildCount();
for (int i = 0; i < count; i++) {
MediaModelGridAdapter.ViewHolder childRow = (MediaModelGridAdapter.ViewHolder)rvCameraRoll.getChildViewHolder(rvCameraRoll.getChildAt(i));
if(isVisible) {
childRow.imgCellSelected.setVisibility(View.VISIBLE);
if(getMediaModelList().get(i).getIsSelected()){
childRow.imgCellSelected.setBackgroundResource(R.drawable.ic_ap_selected_on);
}
}else{
//check that exists, because after fresh delete list may be short while updating cells
if(getMediaModelList().size() > i) {
getMediaModelList().get(i).setIsSelected(false);
}
childRow.imgCellSelected.setBackgroundResource(R.drawable.ic_ap_selected_off);
childRow.imgCellSelected.setVisibility(View.GONE);
}
}
OLD
leaving this for anyone using a ListView
There is a OnScrollListener for ListView
You can override the
onScrollStateChanged(AbsListView view, int scrollState)
and
the
onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
this will give you the ListView item that is visible. So by using the onScroll you can detect which items are visible and determine which one to play and stop playing.
if you use with recyclerview
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.OnScrollListener
The RecyclerView is only holding the items and the LayoutManager is responsible for displaying the items, so in order to get the ones that are visible to the user, assuming that you use LinearLayoutManager, you should call :
((LinearLayoutManager)recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
or
((LinearLayoutManager)recyclerView.getLayoutManager()).findLastVisibleItemPosition();
When an item is added or removed using DiffUtil, inside android.support.v7.util.AdapterListUpdateCallback only the notifyItemRangeInserted(1, 1) or notifyItemRangeRemoved(1,1) respectively is invoked, notifyItemRangeChanged() is not invoked. I set the positions on each of the views using setTag(position) but are not getting updated for the existing items. Wouldn't prefer to update items manually.
list update:
private void updateItems(final List<Feed> newPosts) {
List<Feed> olderPosts = new ArrayList<>(currentPosts);
final CustomDiffCallback DIFF_CALLBACK = new CustomDiffCallback(olderPosts, newPosts);
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(DIFF_CALLBACK, true);
}
Turns out that we can't use setTag() and getTag() for passing in position to the views. Since the main purpose of DiffUtil is to minimize the updations for views that haven't changed, the views that already have positions set on them cannot get through an updation pass hence would return stale positions. Though In my case, I had the custom listener in a separate class, the update part was a bit unusual. I had to get getLayoutPosition() of ViewHolder. Also can use getAdapterPosition() for the same.
I have 500 above records in the list and i am using pagination to load 10 records each time, animation applied to the recyclerview what i want to do is when i am using notifyDataChanged its refresh all items of recyclerview, due to this reason my applied animation not working properly as i want.
Any idea how to refresh only new data in the recyclerview so that all items of recyclerview will not change.
You can use this method to update only newly added data in adapter,
adapter.notifyItemRangeChanged(int positionStart, int itemCount);
or use
adapter.notifyItemRangeInserted(int positionStart, int itemCount);
here,
positionStart: is a starting position from where you've inserted data (list size for most cases when inserted at end of list)
itemCount: is number of items inserted to list
More from here
notifyItemRangeInserted
Notify any registered observers that the currently reflected itemCount items starting at positionStart have been newly inserted. The items previously located at positionStart and beyond can now be found starting at position positionStart + itemCount.
This is a structural change event. Representations of other existing items in the data set are still considered up to date and will not be rebound, though their positions may be altered.
positionStart: Position of the first item that was inserted
itemCount: Number of items inserted
notifyItemRangeChanged
Notify any registered observers that the itemCount items starting at position positionStart have changed. Equivalent to calling notifyItemRangeChanged(position, itemCount, null);.
This is an item change event, not a structural change event. It indicates that any reflection of the data in the given position range is out of date and should be updated. The items in the given range retain the same identity.
positionStart: Position of the first item that has changed
itemCount: Number of items that have changed
Different is that notifyItemRangeInserted treats old data as up to date
Use notifyItemRangeChanged() method, like if you have 10 items already and when you scroll down 10 new items added to the array then you should do:
notifyItemRangeChanged(10,array.size())
Which is : notifyItemRangeChanged(int positionStart, int itemCount)
You should use notifyItemRangeChanged because it reset items only between given range. rather than notifyDataSetChanged which reset all the items of RecyclerView.
Yes you can do that by specifying on which position you inserted the new data by calling
RecyclerView.Adapter#notifyItemInserted(int)
you can use DiffUtil to refresh particular item changes.
for reference check this https://proandroiddev.com/diffutil-is-a-must-797502bc1149
For instance, you have an adapter and in onBindViewHolder method you set OnClickListener to some views (and do some actions there depending on view position). You should assign final to position param of method onBindViewHolder so it could be accessible from onClick().
After changing dataset (remove or add item in list) you call onItemInserted or onItemRemoved and this really adds/removes a view in the recyclerview, BUT it does not refresh other viewitems so when you click on a neighbor viewitem it will open a screen or show data with invalid index. To avoid this I basically call notifyDatasetChanged to call onBind to all visible views and remove/add some views.
So how to refresh other views when you call notifyItemInserted/removed or how to work with these methots appropriately?
Assigning the position to a variable in onBindViewHolder will lead to an inconsistent state if items in the dataset are inserted or deleted without calling notifyDataSetChanged.
To use onItemInserted or onItemRemoved the data in the viewholder should remain consistent since it will not be redrawn and onClick would use this invalid position assigned before an item was added or removed.
For this and other use cases the RecyclerView.ViewHolder provides methods to access its position and id:
Use getAdapterPosition() or getItemId() to get valid positions and ids.
Also have a look on the other methods available in RecyclerView.ViewHolder.
So, the way I fix the problem I had is by changing the position into viewHolder.getAdapterPosition()
Cheers!
I advise you to add notifyItemRangeChanged after insert or remove list inside adapter. This work for my project.
Example in remove item :
public void removeItem (int pos) {
simpanList.remove(pos);
notifyItemRemoved(pos);
notifyItemRangeChanged(pos, simpanList.size());//add here, this can refresh position cmiiw
}
For future readers, this is what I do when inserting/removing in recyclerview
For example, my model class is CarsModel
In my adapter
ArrayList<CarsModel> carsModel;
In onBindViewHolder
CardModel model = carsModel.get(position);
When removing data in list using button in holder:
int position = holder.getAdapterPosition();
carsModel.remove(position);
notifyItemRemoved(position);
Then when inserting
carsModel.add(0, model);
notifyItemInserted(0);
or insert in last row
carsModel.add(carsModel.size() - 1 , model);
notifyItemInserted(carsModel.size()-1);