I have fixed number of items in recycler view, but I update data every 10 seconds and calling notifyDataSetChanged();
When I scrolled to Nth item (EX:20th item) and on notifyDataSetChanged() called recycler view getting refreshed and auto-scroll back to 1st item. How can I stop scrolling and update the recycler view? Thanks in advance.
You can create a view type and holder for your first item then set setIsRecyclable
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
if (holder instanceof HeaderViewHolder ) {
((HeaderViewHolder ) holder).setIsRecyclable(false);
((HeaderViewHolder ) holder).bind(mData.get(position));
}
}
You should find which items changed and use adapter.notifyItemChanged(itemChangedPosition) instead of notifyDatasetChanged()
Using onSaveInstanceState you will save the state or current position of your recyclerview.Just like this.
Create variable in your activity
Parcelable recylerViewList; //this will store the position
then create this method to fetch recyclerview state
void storeInstance() {
AttachmentsActivity.recylerViewList=mRecyclerView?.layoutManager?.onSaveInstanceState()
}// call this method before notifyDataSetChanged()
Create method to restore the state
void restore() //call this method after notifyDataSetChanged()
{
mRecyclerView?.layoutManager?.onRestoreInstanceState(recylerViewList)
}
Make sure you call this methods in activity or through activity object.
Related
I need to refresh / rebind ListView or RecyclerView contents without refreshing the header item itself.
Any tips on how to achieve this?
Thanks.
Yes, you can do this. Generally, your header has 0 position in the list, so the header places on the top of your list. So, for your lists, e.g. RecyclerView you must initialize the adapter (in case of RecyclerView you must create accessor of RecyclerView.Adapter class) and this adapter has a lot of methods for updating data in adapter (notifyDataSetChanged(), notifyItemInserted() etc.) and you can use one of this methods, depends on your purpose. So, in your case you can use notifyItemRangeChanged(int positionStart, int itemCount).
You can find more information about these methods in the official documentation
Assuming the header is at position 0:
Lets say you want to bind the header once and then stop binding it after refresh:
When you call notifyDataSetChanged() to reload, the onBindViewHolder() method in the adapter gets called again to refresh the data, keeping that in mind. You can set a boolean so that you bind your header once. So that eventhough the onBindViewHolder() is called multiple times the header would bind once.
class Adapter extends ...........{
//use a boolean as a flag
private boolean bindHeader = true;
........
.......
.......
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
..........
if(position == 0 && bindHeader){
//bind the header only for the first time
......
......
//then stop binding after refresh
bindHeader = false;
}else if(position > 0){
//bind the reset of the items here
}
}
..............
..............
}
I got a RecyclerView and want to change the appearance of any clicked row. For that I have a callbackFunction in my Activity which I pass to the Adapter, which then is called inside the Adapter, as soon as I click on any row in the RecyclerView.
The clicked row is then changed, but it happens, that not only the clicked rows are changed but also other rows, that weren't clicked and were never clicked before. I checked the ArrayList that contains the data, but everything is fine there. Only the clicked elements contain the trigger to change the appearance of the row.
What is causing the other rows to change, although they have not been clicked?
Interface inside activity for callback
public interface onHeaderClickListener{
void onHeaderClicked(int index);
}
Inside RecyclerView Adapter
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, final int position) {
if (holder instanceof ViewHolderHeader){
((ViewHolderHeader)holder).dateHeaderTextView.setText( Integer.toString(((objClass_offerDateHeader) arrayList.get(position)).getDate()));
if(((objClass_offerDateHeader) arrayList.get(position)).isSelected()){
((ViewHolderHeader)holder).dateHeaderTextView.setBackgroundColor(Color.parseColor("#b642f4"));
}
((ViewHolderHeader)holder).dateHeaderTextView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onHeaderClickListener.onHeaderClicked(position);
}
});
}
}
Adapter initialisation inside activity
customAdapterRecyclerViewAddOffersTo = new customAdapterRecyclerViewAddOffers(offerArrayList,"dragTo", new onHeaderClickListener() {
#Override
public void onHeaderClicked(int index) {
if (offerArrayList.get(index) instanceof objClass_offerDateHeader){
if(((objClass_offerDateHeader) offerArrayList.get(index)).isSelected()){
((objClass_offerDateHeader) offerArrayList.get(index)).setSelected(false);
}
else {
((objClass_offerDateHeader) offerArrayList.get(index)).setSelected(true);
}
customAdapterRecyclerViewAddOffersTo.notifyDataSetChanged();
}
}
});
In your onBindViewHolder method you have to set the background of the unselected cell, keep in mind the the cells are reused and you only set the background of selected cells so when it is reused the background is not returned to the normal color
So in code you will have to add an else condition
if(((objClass_offerDateHeader) arrayList.get(position)).isSelected()){
((ViewHolderHeader)holder).dateHeaderTextView.setBackgroundColor(Color.parseColor("#b642f4"));
} else {
((ViewHolderHeader)holder).dateHeaderTextView.setBackgroundColor(Color.parseColor("#FFFFFF")); // I assume you need it to be white you can change it to any other color
}
You need to add an else condition here:
if(((objClass_offerDateHeader) arrayList.get(position)).isSelected()){
((ViewHolderHeader)holder).dateHeaderTextView.setBackgroundColor(Color.parseColor("#b642f4"));
}
Viewholders get recycled, so you cannot be sure of the current state when onBindViewHolder is called.
I have a RecyclerView with displays a List of items. One Item of this list should be my currentItem (int), that Item should be expanded. For that, I have this method expand() in my ViewHolder. I want to call this method when currentItem == position is.
I thought I could do it like this:
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
final WorkoutExcersiseHolder excersiseHolder = (WorkoutExcersiseHolder) holder;
excersiseHolder.expand(currentExcersisePointer, position);
if (currentItem == position) {
excersiseHolder.expand();
}
}
However every Item gets expanded. So is it possible to get a reference to the ViewHolder at only one position in my RecyclerView.Adapter?
You should hide the item when the condition is false.
if (currentItem == position) {
excersiseHolder.expand();
} else {
excersiseHolder.collapse();
}
When you change the currentItem's value you should call notifyItemChanged() with the old value and the new one too.
notifyItemChanged(currentItem);
currentItem = newPosition;
notifyItemChanged(currentItem);
You have to bind your current item number to the model which you use to fill your RecyclerView. In your case I guess it is WorkoutExcersiseHolder class, which should have something like isCurrent() method, which will help to onBindViewHolder decide to expand it or not. Update your model list with your logic and then use RecyclerView.Adapter method notifyItemChanged. For your note, also don't use position parameter to reference current ViewHolder, but instead try to use holder.getAdapterPosition()
pass view holder object to the expand method and then expand throw this object in the expand method
I have a RecyclerView with rows that have views that when clicked will be disabled for that row position.
The problem is after I update the adapter like this:
adapterData.clear();
adapterData.addAll(refreshedAdapterData);
notifyDataSetChanged();
After refreshing the data, the disabled views at the previous recycler position still remain disabled even though the data is refreshed.
How can I reset the views to the original state after refreshing adapter data.
Use below code.
adapterData.clear();
adapterData.addAll(refreshedAdapterData);
adapter.notifyDataSetChanged();
OR
recyclerView.invalidate();
When you call notifyDataSetChanged(), the onBindViewHolder() method of every view is called. So you could add something like this in the onBindViewHolder() of your Adapter method:
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, final int position) {
if (refreshedAdapterData.get(position).isInDefaultState()) {
//set Default View Values
} else {
//the code you already have
}
}
I have resolved this by putting a conditional statement inside onBindViewHolder method instructing all positions to reset the disabled views if data meets the required conditions for a refreshed data.
#Christoph Mayr, thanks for your comments. It helped point me in the right direction.
I cleared the data then notify change but the selected checkbox doesn't reset but just moves up one position. Let say I selected item #1 , move out of the RecyclerView, came back and it will auto select item #0.
So, I created new adapter again at onResume(), i worked for me but i don't know if it's the right way to handle this situation.
#Override
public void onResume() {
super.onResume();
if(selectedItems != null && selectedItems.size() > 0){
selectedItems.clear(); // if no selected items before then no need to reset anything
if(adapter != null && recyclerView != null){
// to remove the checked box
adapter = null;
adapter = new MyAdapter(items, new MyAdapter.MyAdapterListener() {
#Override
public void onSelected(int pos) {
selectedItems.add(items.get(pos));
}
#Override
public void onUnSelected(int pos) {
selectedItems.remove(items.get(pos));
}
});
recyclerView.setAdapter(adapter);
}
}
}
I am working where I have checkboxe inside RecyclerView items. I am aware of some of its issues. But, with only 10 items and no scrolling wrong listener is called.
I have something like this:
private class MyAdapter extends RecyclerView.Adapter<MyViewHodler> {
ArrayList<String> items;
ArrayList<String> checkedList = new ArrayList<>();
...
public void onBindViewHolder(final MyViewHolder holder, int position) {
String item = items.get(position);
String check = checkedList.get(item);
if(check != null)
holder.vChecked.setChecked(true);
else
holder.vChecked.setChecked(false);
....
holder.vChecked.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
if (isChecked) {
checkedList.add(item);
} else {
checkedList.remove(item);
}
}
}
}
Now, strangely this checkedChange calls are made on different views. I have a list of only 10 items and are fit in the recyclerview, no scrolling. I externally change the data (items and checked list), set the new data and calling myAdapter.notifyDatasetChanged()
This call removes one more item from the checked state.
For example, I have 10 items all checked. I uncheck 1 item, and doing some other calculations based on that and calling myAdapter.notifyDatasetChanged(). It also unchecks one more element from the other 9 items.
So even without scrolling why onCheckedChange listener is called for the wrong view?
I have found out the problem. I thought onBindViewHolder is called for every view to change the data. But I was wrong.
notifyDatasetChanged() recycles all the view holders on screen and from the recycled view holder pool randomly viewholders are picked to bind the new data. So when I called the notifyDatasetChanged, the viewholder was used at the different position, and thus changed one more item.
So, I removed the listener to null in onViewRecycled method of the recycler view. Like this:
#Override
public void onViewRecycled(MyViewHolder holder) {
super.onViewRecycled(holder);
holder.vChecked.setOnCheckedChangeListener(null);
}
This, did solved the issue. I was aware of this, but I thought viewholders are reused for the same position if there is no scrolling. But onDatasetChanged recycles all currently used view holders and uses it again from the random pool.
This line: String item = items.get(item);
It bothers me. Shouldn't it be String item = items.get(position);
Edit: If you change one position of the dataset, call notifyItemChanged. Don't notify the whole dataset unless it has changed entirely. Could be this.
Edit 2: Well then the problem is not that onCheckedChanged is being called on the wrong views. It is called in all of the views because you always change the checkbox state at onBindViewHolder