RecycleView adapter data show wrong when scrolling too fast - android

I have a custom Recycle View adapter that list my items. in each Item I check database and draw some circles with colors.
when I scroll listview very fast every drawed data ( not title and texts) show wrongs!
how I can manage dynamic View creation without showing wrong data?!
#Override
public void onBindViewHolder(final ItemViewHolder itemViewHolder, int i) {
itemViewHolder.date.setText(items.get(i).getData()); // set the title
itemViewHolder.relative_layout_tag_place.addView(generateTagImages(items.get(i).getServerId())); // had to generate a Relativelaout with
}
and this is importMenuTags():
private RelativeLayout generateTagImages(String serverId) {
List<String> color_list = new ArrayList<>();
RelativeLayout result = new RelativeLayout(context);
List<String> list = db.getCardTags(serverId);
int i = 0;
for (String string : list) {
RelativeLayout rl = new RelativeLayout(context);
color_list.add(get_the_proper_color);
Drawable drawable = context.getResources().getDrawable(R.drawable.color_shape);
drawable.setColorFilter(Color.parseColor(dao.getTagColor(string)), PorterDuff.Mode.SRC_ATOP);
RelativeLayout.LayoutParams lparams = new RelativeLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
lparams.addRule(RelativeLayout.ALIGN_PARENT_START);
lparams.setMargins(i, 0, 0, 0);
lparams.width = 35;
lparams.height = 35;
rl.setLayoutParams(lparams);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
rl.setBackground(drawable);
} else {
rl.setBackgroundDrawable(drawable);
}
result.addView(rl);
i = i + 25;
}
return result;
}
I also had the same problem in simple custom adapter that it's solved by moving my function place out of
if (convertView == null) {
this is the link.

As per seeing in your code, I found your relative layout must be showing some extra data while scrolling. And thats because of recycling of views. Here
public void onBindViewHolder(final ItemViewHolder itemViewHolder, int i) {
itemViewHolder.date.setText(items.get(i).getData()); // set the title
itemViewHolder.relative_layout_tag_place.addView(generateTagImages(items.get(i).getServerId())); // had to generate a Relativelaout with
//Problem is here.
Suppose you added some child views in above holde.relative_layout , and this ViewHodler is recyclerd and provided to another item view, It already have all previously added views in it. and you are adding new child view with them. Hope you understand your problem.
Solution: Very easy one. remove all previsousley added view in onBindViewHolder
public void onBindViewHolder(final ItemViewHolder itemViewHolder, int i) {
itemViewHolder.date.setText(items.get(i).getData()); // set the title
itemViewHolder.relative_layout_tag_place.removeAllViews();
itemViewHolder.relative_layout_tag_place.addView(generateTagImages(items.get(i).getServerId())); // had to generate a Relativelaout with

I was found this solution after 3 day...hope it will work for you.
I had the same problem and the only solution I found for this is:
holder.setIsRecyclable(false);
Your recycler will not recycle anymore so the items will be the same when you scroll, and if you want to delete some item do not use notifyitemRemoved(position), use notifyDataSetChanged() instead.

Related

How to tell RecyclerView to start at specific item position

I want my RecyclerView with LinearLayoutManager to show up with scroll position at specific item after adapter got updated. (not first/last position)
Means the at first (re-)layout, this given position should be in visible area.
It should not layout with position 0 on top and scroll afterwards to target position.
My Adapter starts with itemCount=0, loads its data in thread and notifies its real count later. But the start position must be set already while count is still 0!
As of now I used some kind of post Runnable containingscrollToPosition but this has side effects (starts at first pos and jumps immediately to target position (0 -> target) and seems not to work well with DiffUtil (0 -> target -> 0))
Edit: To clearify: I need alternative to layoutManager.setStackFromEnd(true);, something like setStackFrom(position). ScrollToPosition does not work, if I call it when itemCount is still 0, so it gets ignored. If I call it when I notify that itemCount is now >0, it will layout from 0 and jumps short after to target position. And it fails completely if I use DiffUtil.DiffResult.dispatchUpdatesTo(adapter)`. (shows from 0, then scrolls to target position and then again back to position 0)
I found a solution myself:
I extended the LayoutManager:
class MyLayoutManager extends LinearLayoutManager {
private int mPendingTargetPos = -1;
private int mPendingPosOffset = -1;
#Override
public void onLayoutChildren(Recycler recycler, State state) {
if (mPendingTargetPos != -1 && state.getItemCount() > 0) {
/*
Data is present now, we can set the real scroll position
*/
scrollToPositionWithOffset(mPendingTargetPos, mPendingPosOffset);
mPendingTargetPos = -1;
mPendingPosOffset = -1;
}
super.onLayoutChildren(recycler, state);
}
#Override
public void onRestoreInstanceState(Parcelable state) {
/*
May be needed depending on your implementation.
Ignore target start position if InstanceState is available (page existed before already, keep position that user scrolled to)
*/
mPendingTargetPos = -1;
mPendingPosOffset = -1;
super.onRestoreInstanceState(state);
}
/**
* Sets a start position that will be used <b>as soon as data is available</b>.
* May be used if your Adapter starts with itemCount=0 (async data loading) but you need to
* set the start position already at this time. As soon as itemCount > 0,
* it will set the scrollPosition, so that given itemPosition is visible.
* #param position
* #param offset
*/
public void setTargetStartPos(int position, int offset) {
mPendingTargetPos = position;
mPendingPosOffset = offset;
}
}
It stores my target position. If onLayoutChildren is called by RecyclerView, it checks if adapters itemCount is already > 0. If true, it calls scrollToPositionWithOffset().
So I can tell immediately what position should be visible, but it will not be told to LayoutManager before position exists in Adapter.
You can try this, it will scroll to a position you want:
rv.getLayoutManager().scrollToPosition(positionInTheAdapter).
If you want to scroll to a specific position and that position is the adapter's position, then you can use StaggeredGridLayoutManager scrollToPosition
StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.VERTICAL);
staggeredGridLayoutManager.scrollToPosition(10);
recyclerView.setLayoutManager(staggeredGridLayoutManager);
If I understand the question, you want to scroll to a specific position but that position is the adapter's position and not the RecyclerView's item position.
You can only achieve this through the LayoutManager.
rv.getLayoutManager().scrollToPosition(youPositionInTheAdapter);
None of the methods above worked for me. I did the following using ViewTreeObserver that is triggered once its children have been added/changed visibility.
recyclerView.apply {
adapter = ...
layoutManager = ...
val itemCount = adapter?.itemCount ?: 0
if(itemCount > 1) {
viewTreeObserver.addOnGlobalLayoutListener(object: ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
viewTreeObserver.removeOnGlobalLayoutListener(this)
(layoutManager as? LinearLayoutManager)?.scrollToPosition(#PositionToStartAt)
}
}
}
Go ahead and set #PositionToStartAt to a specific value. You can also ensure that the RecyclerView initial position setting gets triggered once a specific number of children have been laid out to ensure it is set correctly.
if(recyclerView.childCount() > #PositionCheck) {
viewTreeObserver.removeOnGlobalLayoutListener(this)
(layoutManager as? LinearLayoutManager)?.scrollToPosition(#PositionToStartAt)
}
If your only motive is to start the recyclerView from a specific position without any scroll-like animation I'll suggest using StaggeredGridLayoutManager
val staggeredGridLayoutManager = StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.HORIZONTAL)//or VERTICAL
staggeredGridLayoutManager.scrollToPosition(specificPosition)
recyclerView.apply{
layoutManager = staggeredGridLayoutManager
}
Another contribution to a long running question...
As mentioned, layoutManager.scrollToPosition/WithOffset() does work to get the RecyclerView positioned, but timing this can be tricky. For example with variable length item views the RecyclerView has to work hard to get all the prior item views measured.
The approach below simply delays telling the RecyclerView about the prior items, then calls notifyItemRangeInserted(0, offset). This enabled the view to appear in the right place, with no visible scrolling at the start, and ready to scroll back.
private List<Bitmap> bitmaps = new ArrayList<>();
...
private class ViewAdapter extends RecyclerView.Adapter<ViewHolder> {
private volatile int offset;
private boolean offsetCancelled;
ViewAdapter(int initialOffset) {
this.offset = initialOffset;
this.offsetCancelled = initialOffset > 0;
}
#Override
public int getItemCount() {
return bitmaps.size() - offset;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
ImageView imageView = new ImageView(MyActivity.this); // Or getContext() from a Fragment
RecyclerView.LayoutParams lp = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
imageView.setLayoutParams(lp);
return new ViewHolder(imageView);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
holder.imageView.setImageBitmap(bitmaps.get(position + offset));
if (!offsetCancelled) {
offsetCancelled = true;
recyclerView.post(() -> {
int previousOffset = offset;
offset = 0;
notifyItemRangeInserted(0, previousOffset);
Log.i(TAG, "notifyItemRangeInserted(0, " + previousOffset + ")");
});
}
}
}
private class ViewHolder extends RecyclerView.ViewHolder {
private final ImageView imageView;
ViewHolder(#NonNull ImageView itemView) {
super(itemView);
this.imageView = itemView;
}
}
For context, this is a full example, based around ImageView's and Bitmap's. The key bit is the use of the offset in getItemCount() and onBindViewHolder().

Filtering Firebase data in adapter leaves empty space [duplicate]

I am using Firebase Recycler Adapter (Firebase UI Library) to populate Recycler View. I want to hide an item(row) on a condition.
I have a LinearLayout containing a recycler view.
I set linear layout visibility to Gone in populateViewHolder() method of recycler view adapter.
#Override
protected void populateViewHolder(UsersViewHolder viewHolder, User user, int position) {
if (user.getUserEmail().equals(Utils.decodeEmail(userEmail))) {
viewHolder.llMain.setVisibility(View.GONE);
return;
}
viewHolder.tvUserEmail.setText(user.getUserEmail());
}
It hides the LinearLayout but the row remains there with empty space.
Is there any method I should override to overcome this or is there any way to achieve the result?
In some cases changing only visibility attribute might still end up as allocated blank space (because of parent view's padding, margins, inner elements etc). Then changing height of the parent view helps:
holder.itemView.setVisibility(View.GONE);
holder.itemView.setLayoutParams(new RecyclerView.LayoutParams(0, 0));
Then be sure that in the condition that it should be visible, also set:
holder.itemView.setVisibility(View.VISIBLE);
holder.itemView.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
You need to do that because the viewHolder is recycled as you scroll, if you change properties as this and never return them to their natural state, other elements will be already hidden in the event they reuse the same view.
You should hide all views or parent from UsersViewholder layout xml.
You should hide entire viewholder or each view
Entire viewholder:
itemView.setVisibility(View.GONE);
or each element:
view.setVisibility(View.GONE);
But don't forget to set them VISIBLE otherwise, you will end up with some strange things from recycling
IF
view.setVisibility(View.GONE);
gives you a Blank view
Then follow This.
public static class Data_ViewHolder extends RecyclerView.ViewHolder {
private final LinearLayout layout;
final LinearLayout.LayoutParams params;
public Show_Chat_ViewHolder(final View itemView) {
super(itemView);
.
.
.
layout =(LinearLayout)itemView.findViewById(R.id.show_item_layout);
params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
.
.
.
}
private void Layout_hide() {
params.height = 0;
//itemView.setLayoutParams(params); //This One.
layout.setLayoutParams(params); //Or This one.
}
}
Now Call from Adapter
mFirebaseAdapter = new FirebaseRecyclerAdapte......{
public void populateViewHolder.....{
if(model.getData().equals("..Something.."))
{
viewHolder.Layout_hide();
}
else
viewHolder.Person_Email(model.getEmail());
}
}
If you are hiding whole itemView and facing the problem of blank spaces.
Try this to hide the itemView.
holder.itemView.setVisibility(View.GONE);
ViewGroup.LayoutParams params = holder.itemView.getLayoutParams();
params.height = 0;
params.width = 0;
holder.itemView.setLayoutParams(params);
And this to show it.
holder.itemView.setVisibility(View.VISIBLE);
This is a recyclerView, so use both in if else block or you might encounter some unintended UI issues.
There is no built in way to hide a child in RecyclerView.
But you can implement this feature in your Adapter.
public class MyAdapter extends RecyclerView.Adapter<...>{
List<Object> items;
Map<Integer,Object> deletedItems;
...
public void hideItem(final int position) {
deletedItems.add(position, items.get(position));
items.remove(position);
notifyItemRemoved(position);
}
....
}
"GONE" will not remove the space occupied by the item ....you can use
if (condition) {
item.layoutParams.height = 0
item.layoutParams.width = 0
}
inside "onBindViewHolder"
public class OfferViewHolder extends RecyclerView.ViewHolder {
public TextView textViewOfferName;
public LabelImageView labelImageView;
public TextView textViewOldPrice;
public TextView textViewNewPrice;
public TextView textViewShopName;
public TextView textViewTimeDate;
public TextView textViewDistance;
public LinearLayout linearLayoutMain;
public OfferViewHolder(View view) {
super(view);
linearLayoutMain=(LinearLayout) view.findViewById(R.id.ll_main);
textViewOfferName = (TextView) view.findViewById(R.id.textViewoffername);
labelImageView=(LabelImageView) view.findViewById(R.id.labelImageView) ;
textViewOldPrice=(TextView) view.findViewById(R.id.textViewOldPrice);
textViewNewPrice=(TextView) view.findViewById(R.id.textViewNewPrice);
textViewShopName=(TextView) view.findViewById(R.id.textViewShopName);
textViewTimeDate=(TextView) view.findViewById(R.id.textViewDate);
textViewDistance=(TextView) view.findViewById(R.id.textViewDistance);
linearLayoutMain.setVisibility(View.GONE);
textViewOfferName.setVisibility(View.GONE);
labelImageView.setVisibility(View.GONE);
textViewOldPrice.setVisibility(View.GONE);
textViewNewPrice.setVisibility(View.GONE);
textViewShopName.setVisibility(View.GONE);
textViewTimeDate.setVisibility(View.GONE);
textViewDistance.setVisibility(View.GONE);
}
}`enter code here`
THEN IN YOUR ADAPTER
if (a.equals(offer.getOfferCategory())) {
if (offer.getOfferCategory()==null){
// chatMessageViewHolder.getLinearLayoutMain().setVisibility(View.GONE);
// chatMessageViewHolder.linearLayoutMain.setLayoutParams(new RecyclerView.LayoutParams(0, 0));
}
else {
chatMessageViewHolder.itemView.setVisibility(View.VISIBLE);
chatMessageViewHolder.textViewShopName.setText(offer.getOfferCategory());
chatMessageViewHolder.linearLayoutMain.setVisibility(View.VISIBLE);
chatMessageViewHolder.textViewOfferName.setVisibility(View.VISIBLE);
chatMessageViewHolder.labelImageView.setVisibility(View.VISIBLE);
chatMessageViewHolder.textViewOldPrice.setVisibility(View.VISIBLE);
chatMessageViewHolder.textViewNewPrice.setVisibility(View.VISIBLE);
chatMessageViewHolder.textViewShopName.setVisibility(View.VISIBLE);
chatMessageViewHolder.textViewTimeDate.setVisibility(View.VISIBLE);
chatMessageViewHolder.textViewDistance.setVisibility(View.VISIBLE);
}
Thank you lorescu George Cătălin and Dhalav
holder.itemView.setVisibility(View.VISIBLE); is not working now. I am using this
holder.itemView.findViewById(R.id.card).setVisibility(View.GONE);
you can easily send the ViewHolder value to your action function ..
I do not recommend answers with setting height and width of View to 0 because adapter still needs to render them and if there are too many hidden items this can cause lags, it is better to change the list itself and then send it to the adapter
It seems like RV internally caches root view info so changing it's visibility does nothing to occuppied space by the item.
Wrap you RV item view with FrameLayout and set View.GONE to inner view. This way occupped space will be cleared correctly as well as item won't be shown at all.
private fun hideShowItemView(itemView: View, toShow: Boolean) {
itemView.isVisible = toShow
itemView.layoutParams.height = if (toShow) ViewGroup.LayoutParams.WRAP_CONTENT else 0
}
itemView is an ItemView of the ViewHolder
toShow is a boolean to hide or show item of recyclerview
Use below line of code in onBindViewHolder block as per the requirement,
To hide Item : hideShowItemView(holder.itemView, false)
To show Item : hideShowItemView(holder.itemView, true)

How to hide an item from Recycler View on a particular condition?

I am using Firebase Recycler Adapter (Firebase UI Library) to populate Recycler View. I want to hide an item(row) on a condition.
I have a LinearLayout containing a recycler view.
I set linear layout visibility to Gone in populateViewHolder() method of recycler view adapter.
#Override
protected void populateViewHolder(UsersViewHolder viewHolder, User user, int position) {
if (user.getUserEmail().equals(Utils.decodeEmail(userEmail))) {
viewHolder.llMain.setVisibility(View.GONE);
return;
}
viewHolder.tvUserEmail.setText(user.getUserEmail());
}
It hides the LinearLayout but the row remains there with empty space.
Is there any method I should override to overcome this or is there any way to achieve the result?
In some cases changing only visibility attribute might still end up as allocated blank space (because of parent view's padding, margins, inner elements etc). Then changing height of the parent view helps:
holder.itemView.setVisibility(View.GONE);
holder.itemView.setLayoutParams(new RecyclerView.LayoutParams(0, 0));
Then be sure that in the condition that it should be visible, also set:
holder.itemView.setVisibility(View.VISIBLE);
holder.itemView.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
You need to do that because the viewHolder is recycled as you scroll, if you change properties as this and never return them to their natural state, other elements will be already hidden in the event they reuse the same view.
You should hide all views or parent from UsersViewholder layout xml.
You should hide entire viewholder or each view
Entire viewholder:
itemView.setVisibility(View.GONE);
or each element:
view.setVisibility(View.GONE);
But don't forget to set them VISIBLE otherwise, you will end up with some strange things from recycling
IF
view.setVisibility(View.GONE);
gives you a Blank view
Then follow This.
public static class Data_ViewHolder extends RecyclerView.ViewHolder {
private final LinearLayout layout;
final LinearLayout.LayoutParams params;
public Show_Chat_ViewHolder(final View itemView) {
super(itemView);
.
.
.
layout =(LinearLayout)itemView.findViewById(R.id.show_item_layout);
params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
.
.
.
}
private void Layout_hide() {
params.height = 0;
//itemView.setLayoutParams(params); //This One.
layout.setLayoutParams(params); //Or This one.
}
}
Now Call from Adapter
mFirebaseAdapter = new FirebaseRecyclerAdapte......{
public void populateViewHolder.....{
if(model.getData().equals("..Something.."))
{
viewHolder.Layout_hide();
}
else
viewHolder.Person_Email(model.getEmail());
}
}
If you are hiding whole itemView and facing the problem of blank spaces.
Try this to hide the itemView.
holder.itemView.setVisibility(View.GONE);
ViewGroup.LayoutParams params = holder.itemView.getLayoutParams();
params.height = 0;
params.width = 0;
holder.itemView.setLayoutParams(params);
And this to show it.
holder.itemView.setVisibility(View.VISIBLE);
This is a recyclerView, so use both in if else block or you might encounter some unintended UI issues.
There is no built in way to hide a child in RecyclerView.
But you can implement this feature in your Adapter.
public class MyAdapter extends RecyclerView.Adapter<...>{
List<Object> items;
Map<Integer,Object> deletedItems;
...
public void hideItem(final int position) {
deletedItems.add(position, items.get(position));
items.remove(position);
notifyItemRemoved(position);
}
....
}
"GONE" will not remove the space occupied by the item ....you can use
if (condition) {
item.layoutParams.height = 0
item.layoutParams.width = 0
}
inside "onBindViewHolder"
public class OfferViewHolder extends RecyclerView.ViewHolder {
public TextView textViewOfferName;
public LabelImageView labelImageView;
public TextView textViewOldPrice;
public TextView textViewNewPrice;
public TextView textViewShopName;
public TextView textViewTimeDate;
public TextView textViewDistance;
public LinearLayout linearLayoutMain;
public OfferViewHolder(View view) {
super(view);
linearLayoutMain=(LinearLayout) view.findViewById(R.id.ll_main);
textViewOfferName = (TextView) view.findViewById(R.id.textViewoffername);
labelImageView=(LabelImageView) view.findViewById(R.id.labelImageView) ;
textViewOldPrice=(TextView) view.findViewById(R.id.textViewOldPrice);
textViewNewPrice=(TextView) view.findViewById(R.id.textViewNewPrice);
textViewShopName=(TextView) view.findViewById(R.id.textViewShopName);
textViewTimeDate=(TextView) view.findViewById(R.id.textViewDate);
textViewDistance=(TextView) view.findViewById(R.id.textViewDistance);
linearLayoutMain.setVisibility(View.GONE);
textViewOfferName.setVisibility(View.GONE);
labelImageView.setVisibility(View.GONE);
textViewOldPrice.setVisibility(View.GONE);
textViewNewPrice.setVisibility(View.GONE);
textViewShopName.setVisibility(View.GONE);
textViewTimeDate.setVisibility(View.GONE);
textViewDistance.setVisibility(View.GONE);
}
}`enter code here`
THEN IN YOUR ADAPTER
if (a.equals(offer.getOfferCategory())) {
if (offer.getOfferCategory()==null){
// chatMessageViewHolder.getLinearLayoutMain().setVisibility(View.GONE);
// chatMessageViewHolder.linearLayoutMain.setLayoutParams(new RecyclerView.LayoutParams(0, 0));
}
else {
chatMessageViewHolder.itemView.setVisibility(View.VISIBLE);
chatMessageViewHolder.textViewShopName.setText(offer.getOfferCategory());
chatMessageViewHolder.linearLayoutMain.setVisibility(View.VISIBLE);
chatMessageViewHolder.textViewOfferName.setVisibility(View.VISIBLE);
chatMessageViewHolder.labelImageView.setVisibility(View.VISIBLE);
chatMessageViewHolder.textViewOldPrice.setVisibility(View.VISIBLE);
chatMessageViewHolder.textViewNewPrice.setVisibility(View.VISIBLE);
chatMessageViewHolder.textViewShopName.setVisibility(View.VISIBLE);
chatMessageViewHolder.textViewTimeDate.setVisibility(View.VISIBLE);
chatMessageViewHolder.textViewDistance.setVisibility(View.VISIBLE);
}
Thank you lorescu George Cătălin and Dhalav
holder.itemView.setVisibility(View.VISIBLE); is not working now. I am using this
holder.itemView.findViewById(R.id.card).setVisibility(View.GONE);
you can easily send the ViewHolder value to your action function ..
I do not recommend answers with setting height and width of View to 0 because adapter still needs to render them and if there are too many hidden items this can cause lags, it is better to change the list itself and then send it to the adapter
It seems like RV internally caches root view info so changing it's visibility does nothing to occuppied space by the item.
Wrap you RV item view with FrameLayout and set View.GONE to inner view. This way occupped space will be cleared correctly as well as item won't be shown at all.
private fun hideShowItemView(itemView: View, toShow: Boolean) {
itemView.isVisible = toShow
itemView.layoutParams.height = if (toShow) ViewGroup.LayoutParams.WRAP_CONTENT else 0
}
itemView is an ItemView of the ViewHolder
toShow is a boolean to hide or show item of recyclerview
Use below line of code in onBindViewHolder block as per the requirement,
To hide Item : hideShowItemView(holder.itemView, false)
To show Item : hideShowItemView(holder.itemView, true)

RecyclerView notifyDataSetChange scroling all the way up, not working as i want it to

I am writing a photo picker for Facebook inside my app, i have a recyclerview with a grid layout and i want to prevent for scrolling up, i was able to do this by using scrollToPosition and this works but not the way i want
Problem
When i click in a photo on the 2 row that row jumps to the top and becomes the number 1 visible row, if i click the 3 row the samething happens.
I don't want the recycler to move if the view is visible it should remain the same, so if i click on a a photo that is on the last visible row i want the scroll to stay the same, i don't want it to make the last row the first.
Tries to solve it
I tried several things to fix this, i tried calling setNestedScrollingEnabled i followed this How to disable RecyclerView scrolling?
public static void onItemClick(int position){
//picker.setNestedScrollingEnabled(false);
for(int k = 0; k<photoBag.size();k++) {
if(k == position)
photoBag.set(position, new PhotoBag(photoBag.get(position).getPhoto(), true)); //Here im marking the photo to selected
else
photoBag.set(k, new PhotoBag(photoBag.get(k).getPhoto(), false));//Here im setting unselecting all the other photos
}
picker.setAdapter(adapter);
adapter.notifyDataSetChanged();
picker.scrollToPosition(position);
//Log.d("FacebookPicker", "position " + grid.findFirstCompletelyVisibleItemPosition());
//picker.setNestedScrollingEnabled(true);
}
I thought that maybe disabling the scroll would lock the recyclerview on the corrent position but it didn't jumps right up.
I also tried getting the Vertical offset and set it after calling notifyDataSetChange but i can't find a way to set the offset programmatically
EDIT
Adapter
class PickerAdapter extends RecyclerView.Adapter<PickerAdapter.PickerAdapterHolder> {
public final String TAG = "PickerAdapter";
private ArrayList<PhotoBag> photoBag;
private Context context;
private OnClickListener onClickListener;
class PickerAdapterHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
ImageView photo;
ImageView imageBorder;
PickerAdapterHolder(View view) {
super(view);
photo = (ImageView) view.findViewById(R.id.photoItem);
photo.setOnClickListener(this);
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.photoItem:
FacebookPhotoPicker.onItemClick(getAdapterPosition()); //i know that there are better ways to get the clicked item from other class but since im still debuging i don't need to worry about performace i just need it to work
break;
}
}
}
PickerAdapter(Context context, ArrayList<PhotoBag> itemList) {
this.photoBag = itemList;
this.context = context;
}
#Override
public PickerAdapterHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View layoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.facebook_picker_item, null);
return new PickerAdapterHolder(layoutView);
}
#Override
public void onBindViewHolder(final PickerAdapterHolder holder, final int position) {
if(photoBag.get(position).isSelected()){
int border = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, context.getResources().getDisplayMetrics()));
Bitmap photo = photoBag.get(position).getPhoto();
photo = Bitmap.createScaledBitmap(photo,photo.getWidth() - (border*2), photo.getHeight() - (border*2), false);
photo = addWhiteBorder(photo,border);
holder.photo.setImageBitmap(photo);
}else {
holder.photo.setImageBitmap(photoBag.get(position).getPhoto());
}
}
#Override
public int getItemCount() {
return this.photoBag.size();
}
private Bitmap addWhiteBorder(Bitmap bmp, int borderSize) {
Bitmap bmpWithBorder = Bitmap.createBitmap(bmp.getWidth() + borderSize * 2, bmp.getHeight() + borderSize * 2, bmp.getConfig());
Canvas canvas = new Canvas(bmpWithBorder);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(bmp, borderSize, borderSize, null);
return bmpWithBorder;
}
remove those 2 lines from onItemClick
picker.setAdapter(adapter);
picker.scrollToPosition(position);
every time you setAdapter it resets position, and now you don't need to set a new position again.
this should work. If it doesn't, check this answer of mine (and their comments) about providing ID How to remain at a scroll position in RecyclerView after adding items at its first index and call notifydatasetchange

Hiding views in RecyclerView

I have code like this
public static class MyViewHolder extends RecyclerView.ViewHolder {
#InjectView(R.id.text)
TextView label;
public MyViewHolder(View itemView) {
super(itemView);
ButterKnife.inject(this, itemView);
}
public void hide(boolean hide) {
label.setVisibility(hide ? View.GONE : View.VISIBLE);
}
}
which maps to a single row in a RecyclerView. R.id.text is in fact the root view of the layout that gets inflated and passed in to the constructor here.
I'm using the default implementation of LinearLayoutManager.
In bindViewHolder, I call hide(true) on an instance of MyViewHolder, but instead of collapsing the row as expected, the row becomes invisible, maintaining its height and position in the RecyclerView. Has anyone else run into this issue?
How do you hide items in a RecyclerView?
There is no built in way to hide a child in RV but of course if its height becomes 0, it won't be visible :). I assume your root layout does have some min height (or exact height) that makes it still take space even though it is GONE.
Also, if you want to remove a view, remove it from the adapter, don't hide it. Is there a reason why you want to hide instead of remove ?
Put method setVisibility(boolean isVisible) in ViewHolder.
You can change itemView params(width and height) for LayoutManager:
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
...
public void setVisibility(boolean isVisible){
RecyclerView.LayoutParams param = (RecyclerView.LayoutParams)itemView.getLayoutParams();
if (isVisible){
param.height = LinearLayout.LayoutParams.WRAP_CONTENT;
param.width = LinearLayout.LayoutParams.MATCH_PARENT;
itemView.setVisibility(View.VISIBLE);
}else{
itemView.setVisibility(View.GONE);
param.height = 0;
param.width = 0;
}
itemView.setLayoutParams(param);
}
public ViewHolder(View itemView) {
super(itemView);
...
}
}
and change visibility for ItemDecoration (Divider):
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
...
#Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
...
for (int i = 0; i < parent.getChildCount(); i++) {
if (parent.getChildAt(i).getVisibility() == View.GONE)
continue;
/* draw dividers */
}
}
}
You CAN do it!
First, you need to detect which position of item that you want to hide. You can custom getItemViewType to do it.
Next, on onCreateViewHolder, depend on the view type. You can do something like this:
if(viewType == TYPE_HIDE) {
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.empty_item, parent, false);
vHolder = new ViewHolder(context, v, viewType, this);
break;
}
return vHolder;
-> empty item is a layout that have nothing, (in other word, it is default layout whenever created). or code:
<?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">
</LinearLayout>
Hope it help!
Okay, so the way I did it in the end was I had my whole dataset, say, myObjects and I had scenarios where I would only want to show subsets of that dataset.
Since setting visibility of rows in RecyclerView doesn't cause the heights to collapse, and setting the heights of the rows did not appear to do anything either, what I had to do was just keep a secondary dataset called myObjectsShown which was nothing more than a List<Integer> that would index into myObjects to determine which objects would be displayed.
I would then intermittently update myObjectsShown to contain the correct indices.
Therefore,
public int getItemCount() {
return myObjectsShown.size();
}
and
public void onBindViewHolder(MyViewHolder holder, int position) {
Object myObject = myObjects.get(myObjectsShown.get(position));
// bind object to viewholder here...
}
For hiding view in RecyclerView I hide/show view in OnBindViewHolder:
if (item.isShown) {
vh.FooterLayout.setVisibility(View.Visible);
} else {
vh.FooterLayout.setVisibility(View.Gone);
}
And for example - from activity I simply redraw needed item:
_postListAdapter.notifyItemChanged(position)// if you want show/hide footer - position is amountOfPosts.size() and also change bool variable - amountOfPosts[amountOfPosts.size()].isShown
For the sake of completeness, you should note that setting view visibility to GONE would not hide the margins. You need to do something like this :
if(itemView.getVisibility() != GONE) itemView.setVisibility(GONE);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) itemView.getLayoutParams();
layoutParams.setMargins(0, 0, 0, 0);
itemView.setLayoutParams(layoutParams);

Categories

Resources