Android RecyclerView DefaultItemAnimator - Items moving weirdly - android

I have a problem with a simple multicolumn layout RecyclerView which is best reproducible on tablets. I have created a basic example with fully functional source code below, maybe it is easier to quickly run it, instead of trying to understand what I mean ;)
The main problem on tablets (where I have a grid layout) is, that items get strangely rearranged when hiding specific items and displaying them again (via notifyItemRemoved() and notifyItemInserted()). I think this can be even reproduced with removing and inserting only the first item. The layout manager inserts an extra row at the top and moves items from the first line below to fill it. (The example below removes and inserts every third item, starting with the first one)
I have different types of items (in the example red, green and blue items) and I want to toggle a specific type of items (in the example you can toggle the red items with a click on the FloatingActionButton).
The weird behavior that I want to fix can be reproduced by just clicking the FAB twice without scrolling before. This would first filter the red items and then display them again. When toggling the filter the second time you will notice that there is a row of items inserted above and items from the first row are moved to fill it.
If you didn't notice during the animation, just scroll up after toggling the filter.
I would expect that the first row stays where it is and that just the red items get displayed again.
Does anybody know how to fix that?
Here is the activity code:
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private TestAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
((FloatingActionButton) findViewById(R.id.fab)).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View v) {
toggleFiltered();
}
});
final int columnCount = getColumnCount(300);
GridLayoutManager layoutManager = new GridLayoutManager(this, columnCount);
recyclerView.setLayoutManager(layoutManager);
adapter = new TestAdapter();
recyclerView.setAdapter(adapter);
recyclerView.setItemAnimator(new DefaultItemAnimator());
}
private void toggleFiltered() {
final boolean filtered = adapter.isFiltered();
adapter.setFiltered(!filtered);
if(filtered) {
adapter.notifyItemInserted(0);
adapter.notifyItemInserted(3);
adapter.notifyItemInserted(6);
adapter.notifyItemInserted(9);
adapter.notifyItemInserted(12);
} else {
adapter.notifyItemRemoved(0);
adapter.notifyItemRemoved(3);
adapter.notifyItemRemoved(6);
adapter.notifyItemRemoved(9);
adapter.notifyItemRemoved(12);
}
}
int getColumnCount(final int dpThreshold){
final Display display = getWindowManager().getDefaultDisplay();
final DisplayMetrics outMetrics = new DisplayMetrics();
display.getMetrics(outMetrics);
final float density = getResources().getDisplayMetrics().density;
final float dpWidth = outMetrics.widthPixels / density;
return ((int) dpWidth) / dpThreshold;
}
class TestAdapter extends RecyclerView.Adapter {
public static final int TYPE_RED = 0;
public static final int TYPE_GREEN = 1;
public static final int TYPE_BLUE = 2;
private boolean filtered = false;
public boolean isFiltered() {
return filtered;
}
public void setFiltered(final boolean filtered) {
this.filtered = filtered;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
final View view = new View(parent.getContext());
view.setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 500));
int color = Color.RED;
if(viewType == TYPE_GREEN){
color = Color.GREEN;
} else if (viewType == TYPE_BLUE){
color = Color.BLUE;
}
return new TestViewHolder(view, color);
}
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
}
#Override
public int getItemViewType(final int position) {
if(!filtered) {
if ((position + 3) % 3 == 0) {
return TYPE_RED;
}
if ((position + 2) % 3 == 0) {
return TYPE_GREEN;
}
} else {
if ((position) % 2 == 0) {
return TYPE_GREEN;
}
}
return TYPE_BLUE;
}
#Override
public int getItemCount() {
if(filtered) {
return 10;
}
return 15;
}
class TestViewHolder extends RecyclerView.ViewHolder {
public TestViewHolder(final View itemView, final int color) {
super(itemView);
itemView.setBackgroundColor(color);
}
}
}
}
And here is the layout code:
<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:id="#+id/root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="#+id/root_layout"
app:layout_constraintLeft_toLeftOf="#+id/root_layout"
app:layout_constraintRight_toRightOf="#+id/root_layout"
app:layout_constraintTop_toTopOf="#+id/root_layout"/>
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="#+id/root_layout"
app:layout_constraintRight_toRightOf="#+id/root_layout"
android:layout_margin="16dp"
/>
</android.support.constraint.ConstraintLayout>

Firstly, I should mention that the problem is not in DefaultItemAnimator. If you set your animator to null, you would have the same behavior. And actually there is no problem at all. The behavior is expected, as you're just inserting the item into the first position of your TestAdapter, and then other items are shifting, but your RecyclerView is staying at the same position.
So all you need is just add recyclerView.scrollToPosition(0); line in the end of your toggleFiltered() method, so it will scroll up, once you've applied the filtering. And it will work as you wish.

its not weird. because you notifyItemInserted(0). it is insert Item. not notifyDataSetChanged(). adapter is showing item keep going. its Green Item. you notifyItemInserted(0) insert Red, still adapter showing first item is Green(it poisition is 1). because adapter notifyItemInserted(0) has before showing first item is Green.

Related

How to show exactly ten rows in RecyclerView

I would like to always display exactly ten rows in a RecyclerView. RecyclerView does not have a configurable setting for this. One approach is to calculate the size of each row. I would like the table to take up half the screen and always show 0..10 items within that area. If more than 10 items the user would scrooll to see the items. Right now the default behavior of RecyclerView seems to be to keep pushing everything down as it grows.
#Override
public int getItemCount() {
if(list.size()>10){
return 10;
}else{
return list.size();
}
}
In your recycler view adapter class return get item count to 10 if size of array or list is greater than 10
Your guess is right it's working with a dynamic computation of row height, here is an example:
Simple class standing for data you want to display:
public class CustomItem {
private int counter;
public CustomItem(int counter) {
this.counter = counter;
}
public int getCounter() {
return counter;
}
public void setCounter(int counter) {
this.counter = counter;
}
}
And simple layout to display CustomItem in a list:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/darker_gray">
<TextView
android:id="#+id/txvTest"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:text="Test"/>
</RelativeLayout>
RecyclerAdapter for this class, you will see that we set row height programmatically in onBindViewHolder method:
public class CustomItemRecyclerAdapter extends RecyclerView.Adapter<CustomItemRecyclerAdapter.SimpleItemViewHolder> {
private static int NB_OF_ITEM_TO_DISPLAY = 10;
private List<CustomItem> customItems;
private Context context;
private int rowHeight;
public class SimpleItemViewHolder extends RecyclerView.ViewHolder {
TextView txvTest;
RelativeLayout container;
public SimpleItemViewHolder(View itemView) {
super(itemView);
txvTest = (TextView) itemView.findViewById(R.id.txvTest);
container = (RelativeLayout) itemView;
}
}
public CustomItemRecyclerAdapter(Context context, List<CustomItem> customItems) {
this.context = context;
this.customItems = customItems;
}
public void setItemHeight(int parentHeightInPx){
// Height of a row is just parent height divided by number of row to display
this.rowHeight = parentHeightInPx / CustomItemRecyclerAdapter.NB_OF_ITEM_TO_DISPLAY;
// Notify adapter because items will need to be redraw to use newly set height
this.notifyDataSetChanged();
}
#Override
public SimpleItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(context).inflate(R.layout.custom_item, parent, false);
return new SimpleItemViewHolder(itemView);
}
#Override
public void onBindViewHolder(SimpleItemViewHolder holder, int position) {
CustomItem item = customItems.get(position);
holder.txvTest.setText("Item " + item.getCounter());
holder.txvTest.setBackgroundColor(item.getCounter());
// Row size magic is here!
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, rowHeight);
holder.container.setLayoutParams(params);
}
#Override
public int getItemCount() {
return customItems.size();
}
}
Activity layout, with both a TextView used as placeholder for half the screen and the RecyclerView:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#color/colorAccent"
android:text="TEST"/>
<android.support.v7.widget.RecyclerView
android:id="#+id/a_main_rcv_items"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/colorPrimary"
android:layout_weight="1"
android:scrollbars="vertical"/>
</LinearLayout>
And Activity code with comments to understand how it works ;):
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private CustomItemRecyclerAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Link to layout recycler view
recyclerView = (RecyclerView) findViewById(R.id.a_main_rcv_items);
// Create a simple linear layout manager and set it to recyclerview
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
// Create an instance of our custom Adapter
adapter = new CustomItemRecyclerAdapter(MainActivity.this, generateItems(20));
// Set this adapter
recyclerView.setAdapter(adapter);
// Listen for recycler view layout change event to get its height and then compute and set new rows height
recyclerView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View view, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
// Set item height using height of recyclerView
adapter.setItemHeight(view.getHeight());
// Need to simulate a scroll to force a redraw of recycler rows with new height... I have not find better for now...
recyclerView.scrollBy(0,1);
}
});
}
private List<CustomItem> generateItems(int nb) {
ArrayList<CustomItem> items = new ArrayList<>(nb);
for (int i = 1; i < nb + 1; i++) {
items.add(new CustomItem(i));
}
return items;
}
}

android recyclerview setVisibility View.GONE still occupies space

I want to list only the disliked items in my recyclerview. I have a full list of items in rv in MainActivity (did not set visibility here). I can set for each items like or dislike by clicking on imagebutton. The MainActivity shows full list of items (cardviews) that shows imagebutton likes or not. If item is liked, this is stored in firebase db as separate entry under Likes with item key (firebase key .push) and not under Items. (in firebase db I have Users, Items, Likes).
Here is my subactivity code, DislikedItemsActivity, where I want to show only items that are disliked by using setVisibility(View.GONE) for items that are liked. This still holds the space between items for the View.GONE items as well (though these cardviews are empty).
mRecyclerView = (RecyclerView) findViewById(R.id.rvItemList);
mRecyclerView .setHasFixedSize(true);
final LinearLayoutManager linearLayoutManager = new
LinearLayoutManager(this);
linearLayoutManager.setReverseLayout(true);
linearLayoutManager.setStackFromEnd(true);
mRecyclerView.setLayoutManager(linearLayoutManager);
final FirebaseRecyclerAdapter<Item, MainActivity.ItemViewHolder>
firebaseRecyclerAdapter = new FirebaseRecyclerAdapter<Item,
MainActivity.ItemViewHolder>(
Item.class,
R.layout.list_item,
MainActivity.ItemViewHolder.class,
mDatabase
) {
#Override
protected void populateViewHolder(final MainActivity.ItemViewHolder viewHolder, final Item model, final int position) {
final String itemKey = getRef(position).getKey();
mDatabaseItemsLiked.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// if item is not liked, thus no user set in db ( I want to see only items that are liked in my recyclerview)
if (!dataSnapshot.child(itemKey).hasChild(mAuth.getCurrentUser().getUid())) {
viewHolder.mView.setVisibility(View.VISIBLE);
viewHolder.itemNameSetup(model.getItemName());
viewHolder.mView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent itemSheetIntent = new Intent(DislikedItemsActivity.this, ItemSheetActivity.class);
adatlapIntent.putExtra("item_key", itemKey);
startActivity(itemSheetIntent);
}
});
} else {
viewHolder.mView.setVisibility(View.GONE);
mRecyclerView.getAdapter().notifyItemRemoved(position); //this puts together the visible items, but when scrolling, it gets messed up
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.e(TAG, databaseError.toString());
}
});
}
#Override
public void onBindViewHolder(MainActivity.TermekViewHolder viewHolder, int position) {
super.onBindViewHolder(viewHolder, position);
}
};
mRecyclerView.setAdapter(firebaseRecyclerAdapter);
}
I looked for many solutions like onBindViewHolder, notifyDataChanged, set margin to 0, set layout size in xml to wrap_content. The best I could get is to have the not-liked items without space with mRecyclerView.getAdapter().notifyItemRemoved(position);, but scrolling the list backwards the whole rv gets messed up (duplicate entry, empty spaces, disordered list).
I don't know how to list only the disliked items from the complete item list from MainActivity rv in a new activity? My code above shows only disliked items, but only until I scroll to end of list, if I scroll backwards the rv gets messed up. I logged the positions of views (18items) in onBindViewHolder and first it counts all items in sequence (17,16,15,14...0), but scrolling from end of list to backwards the position jumps from 0 to 4 like 7times (changes always how many times) then it is the same for item 5,6, until item 17 (all of their positions showed in onBindViewHolder 7 or 8 times during scrolling ie. 5,5,5,5,6,6,6,6) and only for backward scrolling and during backward move rv shows only disliked items or empty views or duplicate entry of disliked item.
my xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="horizontal"
android:background="#drawable/hatter"
tools:context="com.example.user.itemlist.ItemsLikedActivity">
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/rvItemList"
>
</android.support.v7.widget.RecyclerView>
(don't know how to add pics) When disliked list appears, it shows first item (cardview takes up the full screen), and when I start to scroll the list (from 1. visible item to next visible item) if there is space(item1 vis and next vis item is 4), rearrange and I can see that next visible item(item4) moves to 1. visible item, then the rest of the list is arranged well, until I start to scroll back, then it rearranges the rv with spaces and with double entry. The list goes back and forth until both end (which is the length of full items list and not just the disliked items), but visible items gets all messed up.
use this code to remove occupied space :
ViewGroup.LayoutParams params = holder.itemView.getLayoutParams();
params.height = 0;
holder.itemView.setLayoutParams(params);
I found solution to filter the complete database. In my question I wanted to get only the liked/disliked items in a separate activity, though my previous code showed filtered items, but with gaps.
In below code, I changed the DatabaseReferences (mDatabase -node with complete item list and mDatabaseItemsLiked -node with item uid and user uid).
This gave only empty cards with only number as the likedItems, but to get name from the mDatabase (complete list), I used dataSnapshot.getValue(Item.class).getItemName().
firebaseRecyclerAdapter = new FirebaseRecyclerAdapter<Item,
MainActivity.ItemViewHolder>(
Item.class,
R.layout.list_item,
MainActivity.ItemViewHolder.class,
mDatabaseItemsLiked
) {
#Override
protected void populateViewHolder(final MainActivity.ItemViewHolder
viewHolder, final Item model, final int position) {
final String itemKey = getRef(position).getKey();
mDatabase.child(itemKey).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
viewHolder.itemNameSetup(dataSnapshot.getValue(Item.class).getItemName());
viewHolder.mView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent itemSheetIntent = new Intent(LikedItemsActivity.this, ItemSheetActivity.class);
adatlapIntent.putExtra("item_key", itemKey);
startActivity(itemSheetIntent);
}
});
This works for me without any problem. I hope it is network efficient.
You can try to store the liked ítem in boolean array and later in populateViewHolder check if ítem has like o no and set visibility.
I would do like that:
In your class declare :
private boolean [] itemLiked;
In your constructor :
this.itemLiked = new boolean [arrayOfAllItems.size]
On click event:
itemLiked[position] = true; //Where position is row position
onBindViewholder or in your case populateViewHolder:
if (!itemLiked[position]) {
viewHolder.mView.setVisibility(View.GONE); }
Hope it helps, good luck!
EDITED
I do not understand exactly what you want to do, that's why I leave you the code for two cases.
Case 1. Mark and dis-mark the rows.
Case 2. Save to database or delete.
In continuation the complete code
Activity XML add RecyclerView:
<android.support.v7.widget.RecyclerView
android:id="#+id/my_rv"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
Make a custom layout for row:
<TextView
android:id="#+id/question_tv"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="5"
android:text="QUESTION"/>
<ImageButton
android:id="#+id/like"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:src="#android:drawable/ic_input_add"
android:background="#android:color/transparent"
android:layout_marginRight="4dp"/>
<ImageButton
android:id="#+id/dislike"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:src="#android:drawable/ic_delete"
android:background="#android:color/transparent"
android:layout_marginRight="4dp"/>
Make a Model class:
public class SomeModel {
private String question;
public SomeModel(String question) {
this.question = question;
}
public String getQuestion() {
return question;
}
}
Make Adapter Class:
public class SomeAdapter extends RecyclerView.Adapter {
private ArrayList<SomeModel> arrayList;
private boolean [] item_has_like, item_hase_vote;
public SomeAdapter(ArrayList<SomeModel> arrayList) {
this.arrayList = arrayList;
this.item_has_like = new boolean[arrayList.size()];
this.item_hase_vote = new boolean[arrayList.size()];
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
MyViewHolder myViewHolder = null;
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View view = layoutInflater.inflate(R.layout.draw_row, parent, false);
myViewHolder = new MyViewHolder(view);
return myViewHolder;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
final MyViewHolder myViewHolder = (SomeAdapter.MyViewHolder)holder;
final SomeModel item = arrayList.get(position);
int backGround;
/**In background you can save whateveryou need, example:
* backGround= R.drawable.some_background;
* backGround= View.GONE;
*.....
**/
if (item_hase_vote[position]){
if (item_has_like[position])
{
backGround= Color.GREEN;//
} else {
backGround= Color.RED;
}
} else {
backGround= Color.TRANSPARENT;
}
myViewHolder.questionTV.setText(item.getQuestion());
myViewHolder.questionTV.setBackgroundColor(backGround);
}
#Override
public int getItemCount() {
return arrayList.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder {
private TextView questionTV;
private ImageView like, dislike;
public MyViewHolder(final View itemView) {
super(itemView);
questionTV = (TextView)itemView.findViewById(R.id.question_tv);
like = (ImageView)itemView.findViewById(R.id.like);
dislike = (ImageView)itemView.findViewById(R.id.dislike);
like.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//Item been voted
item_hase_vote[getAdapterPosition()] = true;
//Item got Like save in boolean array by row position
item_has_like[getAdapterPosition()] = true;
//notify your adapter
notifyDataSetChanged();
/*OR Here comes the code where You save Item in Your Data Base.*/
}
});
dislike.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//Item been voted
item_hase_vote[getAdapterPosition()] = true;
// Item got DisLike save in boolean array by row position
item_has_like[getAdapterPosition()] = false;
//notify your adapter
notifyDataSetChanged();
/*OR Here You Remove item on Dislike
arrayList.remove(getAdapterPosition());
notifyItemRemoved(getAdapterPosition());
notifyItemRangeChanged(getAdapterPosition(),arrayList.size());
*/
}
});
}
}
}
And Your Activity:
public class SomeActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_some);
ArrayList<SomeModel> arrayList = new ArrayList<>();
for (int i = 0; i <77 ; i++) {
arrayList.add(new SomeModel("Question " + i));
}
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.my_rv);
SomeAdapter adapter = new SomeAdapter(arrayList);
recyclerView.setAdapter(adapter);
LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
recyclerView.setLayoutManager(layoutManager);
}
}
Saving the selected item by the adapterPosition in boolean, String, int....[], the Adapter always gona know whats going on with every item and like that your list always gona be arranged.
Good Luck!
I also got the same problem. what i thought was if RelativeLayout load one after one, height=0, the specs will remove.So it works for me.
This is my ViewHolder. I Introduces my reletivelayout here.
public static class BlogViewHolder extends RecyclerView.ViewHolder {
View mView;
TextView txtdate;
RelativeLayout con_rel;
String name_day = "no name";
public BlogViewHolder(View itemView) {
super(itemView);
mView=itemView;
con_rel=(RelativeLayout)itemView.findViewById(R.id.con_rel);
txtdate = (TextView)itemView.findViewById(R.id.day);
}
}
The I set height and width
con_ref=FirebaseDatabase.getInstance().getReference().child("/consultation");
FirebaseRecyclerAdapter<Consultation,SelectConsaltation.BlogViewHolder>recyclerAdapter=new FirebaseRecyclerAdapter< Consultation,SelectConsaltation.BlogViewHolder>(
Consultation.class,
R.layout.consultation_card,
SelectConsaltation.BlogViewHolder.class,
con_ref
) {
#Override
protected void populateViewHolder(final SelectConsaltation.BlogViewHolder viewHolder, final Consultation model, final int Consultation) {
Shedule_ref.child(model.getScheduleID()).child("Day").addValueEventListener(new ValueEventListener() {
ViewGroup.LayoutParams params = viewHolder.con_rel.getLayoutParams();
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
name_day = dataSnapshot.getValue(String.class);
if (doctor_id_from_doctor.equals( model.getDoctorID() )){
SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy");
Date strDate = null;
try {
strDate = sdf.parse(model.getDate());
} catch (ParseException e) {
e.printStackTrace();
}
if(System.currentTimeMillis()<=strDate.getTime() ) {
params.height = 300;
params.width =800;
viewHolder.con_rel.setLayoutParams(params);
viewHolder.setDate(model.getDate(),name_day);
}
else {
**params.height = 0;
params.width = 0;
viewHolder.con_rel.setLayoutParams(params);**
}
}
else {
params.height = 0;
params.width = 0;
viewHolder.con_rel.setLayoutParams(params);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
};
recyclerView.setAdapter(recyclerAdapter);
}
My card view code
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:id="#+id/con_rel"
android:layout_marginRight="10dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="2dp"
android:layout_marginBottom="3dp"
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="wrap_content" android:background="#a2ffffff">
<LinearLayout
android:layout_width="match_parent"
android:layout_marginTop="3dp"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/day"
android:layout_marginTop="10dp"
android:textSize="18sp"
android:layout_marginLeft="5dp"
android:textColor="#color/colorBlack"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/_07th_of_sunday_january_2018_at_9_00am"/>
<TextView
android:id="#+id/nextnumber"
android:layout_marginLeft="5dp"
android:textSize="18sp"
android:textColor="#color/colornextnumber"
android:textStyle="bold"
android:layout_marginTop="20dp"
android:text="#string/next_avealable_number_is_04"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/booknow"
android:textStyle="bold"
android:textSize="18sp"
android:layout_marginTop="20dp"
android:layout_marginLeft="240dp"
android:layout_marginBottom="10dp"
android:textColor="#color/colorbookNow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/book_now"/>
</LinearLayout>
</RelativeLayout>
Here is my inreface

Loading large number of items in recycler view

I have a recycler view within a fragment and basically I m trying to load song list in the recycler view .Each row of recycler view contains an imageview (for album art) and textview ( for song name). I am having trouble when the size of the dataset is huge, that is when there are too many songs, the recycler view lags and the app ends up giving an ANR.I am using Glide to load album arts in each row's imageview.
How is google music player able to show such large number of songs without any lag?
Edit:
This is my SongsFragment
public class SongsFragment extends Fragment {
static {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
ProgressBar progressBar; // progress bar to show after every 30 items
NestedScrollView nestedScrollView; //for smooth scrolling of recyclerview as well as to detect the end of recyclerview
RecyclerView recyclerView;
ArrayList<Song> songMainList = new ArrayList<>(); //partial list in which items are added
ArrayList<Song> songAllList = new ArrayList<>(); //Complete List of songs
SongAdapter songsAdapter;
private LinearLayoutManager layoutManager;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_songs, container, false);
nestedScrollView = (NestedScrollView) rootView.findViewById(R.id.nestedScrollView);
progressBar = (ProgressBar) rootView.findViewById(R.id.progressBar);
String songJson = getActivity().getIntent().getStringExtra("songList");
songAllList = new Gson().fromJson(songJson, new TypeToken<ArrayList<Song>>() {
}.getType());
//Getting list of all songs in songAllList
if (songAllList.size() > 30) {
songMainList = new ArrayList<>(songAllList.subList(0,30));
} else {
songMainList = songAllList;
}
//if size of fetched songAllList>30 then add only 30 rows to songMainList
recyclerView = (RecyclerView) rootView.findViewById(R.id.songs);
int spanCount = 1; // 2 columns
int spacing = 4; // 50px
recyclerView.addItemDecoration(new GridItemDecoration(spanCount, spacing, true));
recyclerView.setHasFixedSize(true);
recyclerView.setNestedScrollingEnabled(false);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
songsAdapter = new SongAdapter(getActivity(), songMainList, recyclerView);
nestedScrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
View view = (View) nestedScrollView.getChildAt(nestedScrollView.getChildCount() - 1);
int diff = (view.getBottom() - (nestedScrollView.getHeight() + nestedScrollView
.getScrollY()));
if (diff == 0) { //NestedScrollView scrolled to bottom
progressBar.setVisibility(View.VISIBLE); //show progressbar
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
if (songMainList.size() < songAllList.size()) {
int x = 0, y = 0;
if ((songAllList.size() - songMainList.size()) >= 30) {
x = songMainList.size();
y = x + 30;
} else {
x = songMainList.size();
y = x + songAllList.size() - songMainList.size();
}
for (int i = x; i < y; i++) {
songMainList.add(songAllList.get(i)); //Adding new items from songAllList to songMainList one by one
songsAdapter.notifyDataSetChanged();
}
}
progressBar.setVisibility(View.GONE);
}
}, 1500);
}
}
});
recyclerView.setAdapter(songsAdapter);
return rootView;
}
}
And this is my RecyclerViewAdapter along with viewholder
public class SongAdapter extends RecyclerView.Adapter {
private List<Song> songsList;
private Context c;
private RecyclerView.ViewHolder holder;
public SongAdapter(Context context) {
mainActivityContext = context;
}
public SongAdapter(Context context, List<Song> songs, RecyclerView recyclerView) {
songsList = songs;
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
c = context;
}
public SongAdapter getInstance() {
return SongAdapter.this;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.song_list_row, parent, false);
return new SongViewHolder(view,c);
}
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
if (holder instanceof SongViewHolder) {
Song song = songsList.get(position);
this.holder = holder;
String name = song.getName();
String artist = song.getArtist();
String imagepath = song.getImagepath();
((SongViewHolder) holder).name.setText(name);
((SongViewHolder) holder).artist.setText(artist);
if (!imagepath.equalsIgnoreCase("no_image")) //if the album art has valid imagepath for this song
Glide.with(c).load(imagepath)
.centerCrop()
.into(((SongViewHolder) holder).iv);
else
((SongViewHolder) holder).iv.setImageResource(R.drawable.empty);
((SongViewHolder) holder).song = song;
}
}
#Override
public int getItemCount() {
return songsList.size();
}
static class SongViewHolder extends RecyclerView.ViewHolder{
ImageView iv;
TextView name, artist;
CardView songListCard;
private Context ctx;
private OnLongPressListener mListener;
SongViewHolder(View v, Context context) {
super(v);
this.ctx = context;
iv= (ImageView) v.findViewById(R.id.album_art);
name= (TextView) v.findViewById(R.id.name);
artist= (TextView) v.findViewById(R.id.artist_mini);
songListCard = (CardView) v.findViewById(R.id.song_list_card);
}
}
The recyclerview works fine when there are only 150-200 items but when reaching to 600-700 items , the whole app slows down. Could this be because of the way I have used glide in onBindViewHolder?
Sort answer:
LinearLayoutManager(context).apply { isAutoMeasureEnabled = false }
// or in Java
layoutManager.setAutoMeasureEnabled(false)
UPDATE 2020.08.14
Deprecated RecyclerView.LayoutManager#setAutoMeasureEnabled
This method was deprecated in API level 27.1.0.
Implementors of LayoutManager should define whether or not it uses AutoMeasure by overriding isAutoMeasureEnabled()
From the doc of RecyclerView.LayoutManager#setAutoMeasureEnabled() we know :
This method is usually called by the LayoutManager with value {#code true} if it wants to support WRAP_CONTENT
It works by calling {#link LayoutManager#onLayoutChildren(Recycler, State)} during an {#link RecyclerView#onMeasure(int, int)} call, then calculating desired dimensions based on children's positions.
If we set mAutoMeasure = true, it will call LayoutManager#onLayoutChildren(Recycler, State) during an RecyclerView#onMeasure(int, int) call. Every child view's onMeasure() method will be called, this cost too much time.
Let's look at LinearLayoutManager's constructor
public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
setOrientation(orientation);
setReverseLayout(reverseLayout);
setAutoMeasureEnabled(true);
}
So, after we set mAutoMeasure = false, everything will be ok.
Solved the problem by removing the NestedScrollView over the recyclerview.
The nestedscrollview was not allowing the recyclerview.addOnScrollListener() to be called because of which I was getting a lag on loading more items.
Here is how i implemented the loadOnScroll for RecyclerView-
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (!recyclerView.canScrollVertically(1))
onScrolledToBottom();
}
});
private void onScrolledToBottom() {
if (songMainList.size() < songAllList.size()) {
int x, y;
if ((songAllList.size() - songMainList.size()) >= 50) {
x = songMainList.size();
y = x + 50;
} else {
x = songMainList.size();
y = x + songAllList.size() - songMainList.size();
}
for (int i = x; i < y; i++) {
songMainList.add(songAllList.get(i));
}
songsAdapter.notifyDataSetChanged();
}
}
Do you load the data all at once? RecycleView should not have problem, I think if you have too much data the processing itself can take too much time. You should load the data in chunks and check the scroll state of the user and load the next batch etc. kinda how Instagram or Facebook does it.
you may achieve this by using AndroidX Room & Paging
cache your data from network to local Room Database
using Paging to load your data from Room Database

Change in RecyclerView visibility from Gone to Visible, incorrectly shows previously removed item momentarily, before displaying newly added item

Background/Context:
In my main activity, I have a tab layout have two tabs. Each tab contains two fragments, SearchVehicleFragment and VehicleListFragment.
Following is the layout file for VehicleListFragment.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".ui.fragments.VehicleListFragment"
android:orientation="vertical">
<com.boulevardclan.vvp.ui.recyclerviews.VehicleRecyclerView
android:id="#+id/rvVehicleList"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:id="#+id/tvNoSearchedVehicles"
style="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:text="You haven't searched for any vehicles yet." />
</LinearLayout>
My VehicleRecyclerView is created exactly like AttractionsRecyclerView available here. This is because I want to implement empty state mechanism for my VehicleRecyclerView. Also, during initialization, my VehicleRecyclerView has setHasFixedSize(true)
public class VehicleRecyclerView extends RecyclerView {
private View mEmptyView;
public VehicleRecyclerView(Context context) {
super(context);
}
private AdapterDataObserver mDataObserver = new AdapterDataObserver() {
#Override
public void onChanged() {
super.onChanged();
updateEmptyView();
}
#Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
super.onItemRangeRemoved(positionStart, itemCount);
try{
updateEmptyView();
}catch(Exception e){
// TODO
}
}
#Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
updateEmptyView();
}
#Override
public void onItemRangeChanged(int positionStart, int itemCount) {
super.onItemRangeChanged(positionStart, itemCount);
updateEmptyView();
}
#Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
super.onItemRangeMoved(fromPosition, toPosition, itemCount);
updateEmptyView();
}
};
public VehicleRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public VehicleRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* Designate a view as the empty view. When the backing adapter has no
* data this view will be made visible and the recycler view hidden.
*
*/
public void setEmptyView(View emptyView) {
mEmptyView = emptyView;
}
#Override
public void setAdapter(RecyclerView.Adapter adapter) {
if (getAdapter() != null) {
getAdapter().unregisterAdapterDataObserver(mDataObserver);
}
if (adapter != null) {
adapter.registerAdapterDataObserver(mDataObserver);
}
super.setAdapter(adapter);
updateEmptyView();
}
private void updateEmptyView() {
if (mEmptyView != null && getAdapter() != null) {
boolean showEmptyView = getAdapter().getItemCount() == 0;
if(showEmptyView){
mEmptyView.setVisibility(VISIBLE);
setVisibility(GONE);
}else {
mEmptyView.setVisibility(GONE);
setVisibility(VISIBLE);
}
}
}
}
My VehicleAdapter has setHasStableIds(true) as well as overriden getItemId(position).
public class VehicleAdapter extends RecyclerView.Adapter<VehicleAdapter.VehicleViewHolder> {
private List<Vehicle> vehicleList = new ArrayList<>();
private Context mContext;
public VehicleAdapter(Context context, List<Vehicle> vehicles) {
vehicleList.addAll(vehicles);
setHasStableIds(true);
mContext = context;
}
#Override
public VehicleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new VehicleViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.vehicle_list_item_card_view, parent, false));
}
#Override
public void onBindViewHolder(VehicleViewHolder holder, int position) {
holder.bind(vehicleList.get(position));
}
#Override
public int getItemCount() {
return vehicleList.size();
}
#Override
public long getItemId(int position) {
return vehicleList.get(position).getId();
}
public void addVehicle(Vehicle vehicle){
if(vehicleList != null){
vehicleList.add(0, vehicle);
notifyItemInserted(0);
}
}
public void removeVehicle(int position){
if(vehicleList.get(position).delete()){
vehicleList.remove(position);
notifyItemRemoved(position);
}else {
// TODO
}
}
public void updateVehicle(Vehicle vehicle, int position){
vehicleList.set(position, vehicle);
notifyItemChanged(position);
}
class VehicleViewHolder extends RecyclerView.ViewHolder{
TextView tvRegistrationNumber;
TextView tvOwnerName;
TextView tvColor;
TextView tvOwnerCity;
TextView tvLookupDate;
TextView tvManufacturer;
TextView tvModel;
TextView tvMakeYear;
ImageView ivDetail;
ImageView ivBookmark;
ImageView ivDelete;
public VehicleViewHolder(View itemView) {
super(itemView);
tvRegistrationNumber = (TextView) itemView.findViewById(R.id.tvRegistrationNumber);
tvOwnerName = (TextView) itemView.findViewById(R.id.tvOwnerName);
tvColor = (TextView) itemView.findViewById(R.id.tvColor);
tvOwnerCity = (TextView) itemView.findViewById(R.id.tvOwnerCity);
tvLookupDate = (TextView) itemView.findViewById(R.id.tvLookupDate);
tvManufacturer = (TextView) itemView.findViewById(R.id.tvManufacturer);
tvModel = (TextView) itemView.findViewById(R.id.tvModel);
tvMakeYear = (TextView) itemView.findViewById(R.id.tvMakeYear);
ivDetail = (ImageView) itemView.findViewById(R.id.ivDetail);
ivBookmark = (ImageView) itemView.findViewById(R.id.ivBookmark);
ivDelete = (ImageView) itemView.findViewById(R.id.ivDelete);
}
void setOwnerName(String ownerName){
tvOwnerName.setText(ownerName);
}
void setColor(String color){
tvColor.setText(color);
}
void setOwnerCity(String ownerCity){
tvOwnerCity.setText(ownerCity);
}
void setLookupDate(String lookupDate){
tvLookupDate.setText(lookupDate);
}
void setManufacturer(String manufacturer){
tvManufacturer.setText(manufacturer);
}
void setMakeYear(int makeYear){
tvMakeYear.setText(String.format(Locale.getDefault(),"%s",makeYear));
}
void setModel(String model){
tvModel.setText(model);
}
void setBookmarked(boolean isBookmarked){
ivBookmark.setImageDrawable(ViewUtils.getDrawable(mContext, (isBookmarked ? R.drawable.ic_favorite_black_24dp: R.drawable.ic_favorite_border_black_24dp)));
}
void bind(final Vehicle vehicle){
tvRegistrationNumber.setText(vehicle.getRegistrationNumber());
setOwnerName(vehicle.getOwnerName());
setColor(vehicle.getColor());
setOwnerCity(vehicle.getOwnerCity());
setLookupDate(DateTimeUtils.getPKTDateTime(vehicle.getModifiedAt()));
setManufacturer(vehicle.getManufacturer());
setModel(vehicle.getMakeType());
setMakeYear(vehicle.getMakeYear());
setBookmarked(vehicle.isBookmarked());
setupClickListeners(vehicle);
}
private void setupClickListeners(final Vehicle vehicle) {
ivDelete.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
new MaterialDialog.Builder(mContext)
.title(R.string.confirm_delete_vehicle_heading)
.content(R.string.confirm_delete_vehicle_label)
.positiveText(R.string.confirm_response_yes)
.negativeText(R.string.confirm_response_cancel)
.stackingBehavior(StackingBehavior.ALWAYS)
.onPositive(new MaterialDialog.SingleButtonCallback() {
#Override
public void onClick(#NonNull MaterialDialog dialog, #NonNull DialogAction which) {
if(getAdapterPosition() != RecyclerView.NO_POSITION){
removeVehicle(getAdapterPosition());
}
}
})
.show();
}
});
}
}
}
Problem:
When there is no item in the recyclerview and I add one or more new items to the recyclerview via notifyItemInserted(0), everything works fine.
But when there is only one item in the recyclerview, I delete the item via notifyItemRemoved(position) and now add a new item to recyclerview via notifyItemInserted(0), there is a transition/animation like effect where after hiding the emptyView, the recyclerview is shown with the previously removed item for a fraction of a second and then newly added item fades in to the viewport.
So, here is the 'ideal' sequence of events that should be followed:
[Working fine] I delete the last item in the recyclerview. The recyclerview's visibility is changed to GONE and emptyView (TextView) visibility is changed to VISIBLE.
[Working fine] Then, I add a new item to the recyclerview. The emptyView's visibility is changed to GONE.
[Not working as expected] The recyclerview's visibility should be changed to VISIBLE and it should contain only one item i.e. the newly added item [Not working as expected]
Instead, there is a flickr/blink where recyclerview with the only previously existing item is shown for a fraction of a second and then that item is replaced by the newly added item via a fade-in(?) transition.
Updte: You can have a look at the issue here: https://media.giphy.com/media/l0IydcuJDAqPNlmla/giphy.gif
I am looking forward to a way to get rid of this flickr/blink effect that shows old item/recyclerview state for a very breif preiod of time.
What I have tried:
RecyclerView.ItemAnimator animator = mVehicleListRV.getItemAnimator();
if (animator instanceof SimpleItemAnimator) {
((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
}
Disclaimer:
I am new to Android development and stuck in this problem for many hours. Looking for help/pointers from the great minds at SO to get unblocked. Please bear with my limited knowledge.
I ran into this issue - indeed, when the Android system thinks the RecyclerView isn't visible, it won't relayout its children. This will produce a flicker when re-showing the RecyclerView, as the old children will still visible for a short time, before the views can be updated for the new data.
I experimented with a few different approaches of how we can trick the RecyclerView into not being visible for the user, while still updating its children.
Some approaches that didn't work:
Set RecyclerView visibility to GONE
Set RecyclerView visibility to INVISIBLE
Set RecyclerView alpha to 0
All these approaches caused the RecyclerView to disappear, but to also not relayout its children, causing a flicker.
The approach that did work:
Set the RecyclerView height to 1
I had my RecyclerView in a ConstraintLayout, so I wrote a little utility function. It looks like this:
/**
* Toggle whether [recyclerView] is visible.
* It does not use [View.setVisibility] or [View.setAlpha],
* because doing so will prevent recyclerView children from being updated,
* and this creates a flicker.
*
* Instead, this workaround makes the recyclerView too small to be seen,
* while 'tricking' the Android system into thinking the view is still visible,
* so allows View updates to continue as necessary.
*/
fun toggleRecyclerView(shouldShow: Boolean) {
recyclerView.updateLayoutParams {
// Use 1 as the 'invisible' height - it's the smallest height available to us
height = if(shouldShow) MATCH_CONSTRAINT else 1
}
}
I combine this with the submitList callback provided by ListAdapter - this will only re-show the RecyclerView after the asynchronous diff has been calculated between old and new data, and then a View.post - to wait until the RecyclerView children have been updated:
if (items.isEmpty()) {
toggleRecyclerView(false)
emptyItemsText.visibility = View.VISIBLE
adapter.submitList(emptyList())
return
}
// Only show recyclerview after items are updated, to prevent flicker
adapter.submitList(items) {
recyclerView.post {
toggleRecyclerView(true)
emptyItemsText.visibility = View.GONE
}
}
You can use the visibility INVISIBLE instead of GONE, in that case the onBindViewHolder get's called even if the adapter is not visible.
You can change visibility of dummy FrameLayout around RecyclerView and not change visibility of RecyclerView (all time visible)
it helped me
UPD: AND rv.setHasFixedSize(true)
Like this
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".ui.fragments.VehicleListFragment"
android:orientation="vertical">
<!-- change visibility of this FL rvVehicleListContainer -->
<FragmeLayout
android:id="#+id/rvVehicleListContainer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.boulevardclan.vvp.ui.recyclerviews.VehicleRecyclerView
android:id="#+id/rvVehicleList"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<FragmeLayout />
<TextView
android:id="#+id/tvNoSearchedVehicles"
style="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:text="You haven't searched for any vehicles yet." />
</LinearLayout>

Data on recycleview item is not correct when scroll

My adapter code:
public class BrandAdapter extends RecyclerView.Adapter<BrandAdapter.BrandViewHolder> {
private static final String TAG = BrandAdapter.class.getSimpleName();
private List<BrandItem> brands;
private Context context;
public BrandAdapter(Context context, List<BrandItem> data) {
this.context = context;
this.brands = data;
}
public void setData(List<BrandItem> dataDownload) {
this.brands = dataDownload;
}
#Override
public BrandViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_item_brand, null);
BrandViewHolder holder = new BrandViewHolder(view);
return holder;
}
#Override
public void onBindViewHolder(BrandViewHolder holder, int position) {
BrandItem brandItem = brands.get(position);
String name = brandItem.getName();
int count = brandItem.getCountArticles();
holder.tvName.setText(name);
if (count > 0) {
holder.tvCount.setText("" + count);
} else {
holder.tvCount.setVisibility(View.GONE);
}
}
#Override
public int getItemCount() {
return brands.size();
}
public static class BrandViewHolder extends RecyclerView.ViewHolder {
TextView tvName;
TextView tvCount;
public BrandViewHolder(View itemView) {
super(itemView);
tvName = (TextView) itemView.findViewById(R.id.tv_brand_name);
tvCount = (TextView) itemView.findViewById(R.id.tv_count_article);
}
}
}
Fragment code :
recyclerView = (RecyclerView) view.findViewById(R.id.recycleView);
linearLayoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(linearLayoutManager);
adapter = new BrandAdapter(getActivity(), brands);
recyclerView.setAdapter(adapter);
Data for brands is downloaded from server. After downloaded finished, I just set new data for adapter by this code :
brands = downloadedBrands();
adapter.setData(brands);
adapter.notifyDataSetChanged();
Everything is Ok when data loaded for first time after the download finish. But when I scroll down the recycleview and scroll up again, data for each item is wrong now, all textview tvCount is gone. I do not know why.
Is there any problem from my code ?
Greenrobo's answer is correct but here is an explanation as to WHY you are having this issue.
You are assuming that your view is always set to the default values in your onBindViewHolder method.
The RecyclerView re-uses views that have scrolled off screen and therefore the view you are binding to may have already been previously used (and changed).
You onBindViewHolder method should always set EVERYTHING up. i.e all views reset to the exact values you want and do not assume that because you default an item to visible, it will always be so.
Please make tvCount visible when setting a non-zero count.
if (count > 0) {
holder.tvCount.setText("" + count);
holder.tvCount.setVisibility(View.VISIBLE);
} else {
holder.tvCount.setVisibility(View.GONE);
}
See if this helps.
You told that if count is less than 0, hide the view. What if count is greater than zero ? You are not making the view visible again. So simply make the below changes in your if condition:
if (count > 0) {
holder.tvCount.setText("" + count);
holder.tvCount.setVisibility(View.VISIBLE);
} else {
holder.tvCount.setVisibility(View.GONE);
}

Categories

Resources