wrong item changes in recyclerview - android

Hi everyone I'm stuck in this and need help :
Each item has a CheckBox and I set setOnLongClickListener for root element of my items in RecyclerView like this :
holder.faviorateVideoItemRelative.setOnLongClickListener(new View.OnLongClickListener() {
public boolean onLongClick(View arg0) {
if (chk_visible)
{
return true ;
}
holder.chk_faviorateVideo.setChecked(!holder.chk_faviorateVideo.isChecked());
chk_visible = true ;
checkedItemsCNT = 1 ;
deleteListVideoCourses.add(data.get(holder.getAdapterPosition())) ;
notifyDataSetChanged() ;
return true ;
}
});
If I scroll down , when I make a long click on one of items , the CheckBox of wrong item get checked !

It because as you use RecycleView it reuse your view every time when you scroll. RecycleView reuse your resource like this
So when you scroll it's showing the wrong state of your view
Solution
If you write any logic for check-in onBindViewHolder then you have to use both part for true and false
if(yourCondition){
//code if condition is true
}else {
//code if condition is false
}
Other Solution
Simply you can solve it just using one statement to stop your RecycleView to reuse your view state like this
#Override
public void onBindViewHolder(ReqNotificationAdapter.MyViewHolder holder, int position) {
holder.setIsRecyclable(false);
//.................your other code
}
I use it to solve my problem.. hope it will solve yours if you don't have a problem stop Recycling with your purpose.

Could be an issue in your bind rather than setting of the value.
A common mistakes is not un-checking the view in the bind.
Make sure where you are setting checked, you have an else statement and set it to unchecked.
RecyclerView and Listview reuse views as they scroll, which includes any previously checked boxes. So it is important to un-check them if appropriate.
public void bindView(View view, ..... //varies on implementation. rough idea.
{
CheckBox mycheckbox = (CheckBox)view.findViewById(R.id.myidofcheckbox);
int pos = view.getPosition();
if(pos == 1) //example
mycheckbox.setChecked(true);
else
mycheckbox.setChecked(false);

Solution 1:
You can override two method getItemId, getItemViewType.
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
Like this:
public class EasyAdapter extends RecyclerView.Adapter<EasyAdapter.ViewHolder>{
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
}
Solution 2:
Set setIsRecyclable false.
EasyAdapter easyAdapter=new EasyAdapter();
easyAdapter.setIsRecyclable(false);

Yes it happens with ListView and RecyclerView
Try this code,
holder.faviorateVideoItemRelative.setTag(position);
holder.faviorateVideoItemRelative.setOnLongClickListener(new View.OnLongClickListener() {
public boolean onLongClick(View arg0) {
int tempPos = (int)holder.faviorateVideoItemRelative.getTag();
if (chk_visible)
{
return true ;
}
if(!holder.chk_faviorateVideo.isChecked())
{
holder.chk_faviorateVideo.setChecked(true);
}
else
{
holder.chk_faviorateVideo.setChecked(false);
}
holder.chk_faviorateVideo.setChecked(!holder.chk_faviorateVideo.isChecked());
chk_visible = true ;
checkedItemsCNT = 1 ;
deleteListVideoCourses.add(data.get(tempPos)) ;
notifyDataSetChanged() ;
return true ;
}
});
Hope it will help you.

When you initialize your OnLongClickListener, it is reused in views when they are no longer visible because they are cached.
To solve the worry, create a new class to explicitly link an object to a listener and not just the view.
For example :
private class BufferVideoLongClickListener implements View.OnLongClickListener {
private VideoObject video; // And other stuff like checked, etc
BufferVideoLongClickListener(VideoObject video, ...) {
this.video = video;
...
}
#Override
public void onLongClick(View view) {
// Do your check and other things
if(checked)
...
}
}
And in your onBindViewHolder :
holder.faviorateVideoItemRelative.setOnLongClickListener(new BufferVideoLongClickListener(video, ...));

After using the above mentioned solutions,
The following solutions worked like a charm for me.
#Override
public long getItemId(int position) {
return position;
}
#Override
public int getItemViewType(int position) {
return position;
}
using holder.setIsRecyclable(false) removes the desired selection as well when you come back to it after the whole scrolling process.

Related

Recycler View: I want my recycler view item(rows) to be highlighted after specific interval of time

I want my recycler view rows to be highlighted after a specific interval of time, say after 2 seconds.
Searched all over the internet but no luck so far.
How about, in the recycler adapter OnBindViewHolder method, put a [Handler.postDelayed](https://developer.android.com/reference/android/os/Handler.html#postDelayed(java.lang.Runnable, long)) in which you set the specific time where you want the item to change.
Inside the runner that you pass in the handler, you put in a boolean flag to check if the row will have a different colour/behaviour + a notifyDataSetChanged() in the adapter. (You will have to change your data object to accomodate this new variable)
The question is not very clear. I had two questions in mind that I mentioned in the comment of the question.
Do you want to highlight some specific rows?
Do you want to toggle the highlight after each two seconds?
So I'm going for a general solution for both.
Let us assume the object you're populating in your each row is like the following.
public class ListItem {
int value;
boolean highlight = false;
}
The list of ListItem object can be inserted in an ArrayList to be populated in the RecyclerView. Here is your adapter which may look like this.
// Declare the yourListItems globally in your Activity
List<ListItem> yourListItems = new ArrayList<ListItem>();
populateYourListItems();
public class YourAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public class YourViewHolder extends RecyclerView.ViewHolder {
private final TextView valueTextView;
private final LinearLayout background;
public YourViewHolder(final View itemView) {
super(itemView);
valueTextView = (TextView) itemView.findViewById(R.id.value_text_view);
background = (LinearLayout) itemView.findViewById(R.id.background);
}
public void bindView(int pos) {
int value = yourListItems.get(pos).value;
boolean isHighlighted = yourListItems.get(pos).hightlight;
valueTextView.setText(value);
// Set the background colour if the highlight value is found true.
if(isHighlighted) background.setBackgroundColor(Color.GREEN);
else background.setBackgroundColor(Color.WHITE);
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_activity_log, parent, false);
return new YourViewHolder(v);
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
try {
if (holder instanceof YourViewHolder) {
YourViewHolder vh = (YourViewHolder) holder;
vh.bindView(position);
}
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public int getItemCount() {
if (yourListItems == null || yourListItems.isEmpty())
return 0;
else
return yourListItems.size();
}
#Override
public int getItemViewType(int position) {
return 1;
}
}
Now when you want to highlight some specific items of your RecyclerView you need to just set the highlight value to true and then call notifyDataSetChanged() to bring into the change in effect.
So you might need a timer like the following which will highlight your rows as per your demand in every two seconds.
// Declare the timer
private Timer highlightTimer;
private TimerTask highlightTimerTask;
highlightTimer = new Timer();
highlightTimerTask = new TimerTask() {
public void run() {
highLightTheListItems();
}
};
highlightTimer.schedule(highlightTimerTask, 2000);
Now implement your highLightTheListItems function as per your need.
public void highLightTheListItems() {
// Modify your list items.
// Call notifyDataSetChanged in your adapter
yourAdapter.notifyDataSetChanged();
}
Hope that helps. Thanks.
Do you mean highlight as in colour the row's background? If so, you could do this in your listViewAdapter
#Override
public View getView(final int position, View row, ViewGroup parent){
if (row==null){
row = LayoutInflater.from(getContext()).inflate(mResource, parent, false);
}
if(foo){
row.setBackgroundColor(getResources().getColor(R.color.translucent_green));
}
else row.setBackgroundColor(Color.TRANSPARENT);
return row;
}
Then in colours.xml
<color name="translucent_green">#667cfc00</color>
The first 2 numbers(66) is the alpha value, ie opacity. The next 6 are RBG in hexadecimal.

Removing duplicate entry in RecyclerView : Scrolling Issue

Hello I am calling below function to set data in my onBindview() method of Recycler view Adapter.
I am calling method as below :
#Override
public void onBindViewHolder(RecyclerViewHolders holder, int position) {
holder.setData(position);
}
My setData() method is as below :
public class RecyclerViewHolders extends RecyclerView.ViewHolder {
public TextView txt_HappyHour;
public RecyclerViewHolders(View itemView) {
super(itemView);
txt_HappyHour = (TextView) itemView.findViewById(R.id.txt_HappyHour);
}
public void setData(final int position) {
final MyModel.MyData modelMyList = itemList.get(position);
if (modelMyList.happy_hours.equals("1")) {
txt_HappyHour.setVisibility(View.VISIBLE);
} else {
txt_HappyHour.setVisibility(View.GONE);
}
}
}
I am setting visibility for TextView txt_HappyHour according to conditions in above setData() function. Now, When I run the app, I got the Textview visible for more than one or multiple time while Scrolling my RecyclerView.
I heared that I can solve this scrolling issue by using setTag() and getTag() method. But, I am confused that in my case, How can I use or apply it ?
Need Help.
Try this
In your onBindViewHolder use setTag like this
txt_HappyHour.setTag(position);
And in your setData Method
if(itemView.get((Integer) txt_HappyHour.getTag()).happy_hours.equals("1"))
{
txt_HappyHour.setVisibility(View.VISIBLE);
} else {
txt_HappyHour.setVisibility(View.GONE);
}
Can you check this answer ?
if (null == txt_HappyHour.getTag) {
txt_HappyHour.setTag(modelMyList.happy_hours);
}
String Value = (String) txt_HappyHour.getTag();
if (Value.equals("1")) {
txt_HappyHour.setVisibility(View.VISIBLE);
} else {
txt_HappyHour.setVisibility(View.INVISIBLE);
}

Android recyclerview issue with animations

I'm trying to make cardviews in recyclerview expand. I got the expanding part working, but when adding transition to it, some visual bugs start to occur. The transitions works fine, when there are no off-screen items, but when I add more than (in my case) 4 items to the recyclerview, it starts to occur.
GIF with 4 items
GIF with more than 4 items
The cardview expanding works fine with more than 4 items when I disable the transition animation. I think the problem is related to positions changing, but I can't find any solution to the problem.
The guide I used to implement the cardview expanding can be found here: https://stackoverflow.com/a/38623873/6673949
And my complete recyclerview adapter
public class BasketRecyclerAdapter extends RecyclerView.Adapter<BasketRecyclerAdapter.CustomViewHolder> {
private String letter;
private Context mContext;
private ColorGenerator generator = ColorGenerator.MATERIAL;
private List<Basket> baskets;
private int mExpandedPosition = -1;
private RecyclerView r1;
public BasketRecyclerAdapter(Context context, List<Basket> baskets, RecyclerView r1) {
this.mContext = context;
this.baskets = baskets;
this.r1 = r1;
}
#Override
public CustomViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.basket_menu_item, null);
CustomViewHolder viewHolder = new CustomViewHolder(view);
return viewHolder;
}
#Override
public void onBindViewHolder(final BasketRecyclerAdapter.CustomViewHolder holder, final int position) {
String basketName = baskets.get(position).getBasketName();
holder.basketName.setText(basketName);
letter = "" + basketName.charAt(0);
TextDrawable drawable = TextDrawable.builder()
.buildRound(letter, generator.getColor(basketName));
holder.imageLetter.setImageDrawable(drawable);
final boolean isExpanded = position == mExpandedPosition;
holder.expandedLayout.setVisibility(isExpanded?View.VISIBLE:View.GONE);
holder.itemView.setActivated(isExpanded);
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mExpandedPosition = isExpanded ? -1:position;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
TransitionManager.beginDelayedTransition(r1);
}
notifyDataSetChanged();
}
});
}
What can be done to solve the problem? Thanks.
Edit: I tried to get it to work by using just ListView instead of RecyclerView, but ListView adapter doesn't expand with same code - will try to figure out why.
Edit2: Got it working by using another library called "ExpandableLayout" but still can't seem to figure out why is it not working without additional libraries.
I had similar issue.
Add in adapter:
#Override
public long getItemId(int position) {
return position;
}
And myAdapter.setHasStableIds(true);
I had the same problem. This is how I worked it out and works like you showed on GIF with 4 items:)
In your custom adapter add notifyItemChanged(position) and notifyItemChanged(previousExpandedPosition) instead of notifyDataSetChanged():
public void onBindViewHolder(#NonNull ViewHolder holder, final int position) {
final boolean isExpanded = position==mExpandedPosition;
holder.expandedLayout.setVisibility(isExpanded ? View.VISIBLE : View.GONE);
holder.itemView.setActivated(isExpanded);
if (isExpanded)
mPreviousExpandedPosition = position;
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mExpandedPosition = isExpanded ? -1:position;
TransitionManager.beginDelayedTransition(mRecyclerView);
notifyItemChanged(mPreviousExpandedPosition);
notifyItemChanged(position);
}
});
}
Declare global variables mExpandedPosition and mPreviousExpandedPosition and initialize them to -1.
Pass the mRecyclerView as a parameter in the constructor for the adapter.
In your fragment or activity set mRecyclerView.setItemAnimator(null) after setting adapter for recycle view, like this:
mRecyclerView.setAdapter(mRecyclerViewAdapter);
mRecyclerView.setItemAnimator(null);
This should solve your problem.
I had a similar issue. When there were no items offscreen it worked fine, but when you have items offscreen the animation does not work properly. The solution was to not update every item in the adapter using notifyDataSetChanged(). You need to only update the position you want to expand AND the position you want to collapse. My guess is that the ViewHolders for items off screen are messing up the animation.
final boolean isExpanded = position == mExpandedPosition;
vh.expandableView.setVisibility(isExpanded ? View.VISIBLE : View.GONE);
vh.itemView.setActivated(isExpanded);
vh.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// collapse any currently expanded items
if (mExpandedPosition != RecyclerView.NO_POSITION) {
notifyItemChanged(mExpandedPosition);
}
//Update expanded position
mExpandedPosition = isExpanded ? -1 : position;
TransitionManager.beginDelayedTransition(mRecyclerView);
//Expand new item clicked
notifyItemChanged(position);
}
});

notifyDataSetChanged doesn't refresh RecyclerView

I have a weird problem. I switched to RecyclerView from ListView and I can't refresh or notify of change in my ListView. I tried calling Item.this.notifyDataSetChanged();
and other methods to refresh View but it doesn't work.
Instead RecyclerView is refreshed when I scroll(regardless of direction). How can I notify my RecyclerView when there is a change?
CODE:
#Override
public void onBindViewHolder(Ids holder, final int position) {
rowItemClass = (ListViewRow) rowItems.get(position);
Log.e("swag", "OYOYOYOYOYO");
if (Globals.isPlaying && Globals.pos == position) {
if (pausedSamePos == true) {
holder.pauseed_play.setVisibility(View.VISIBLE);
holder.playing_pause.setVisibility(View.GONE);
} else {
holder.pauseed_play.setVisibility(View.GONE);
holder.playing_pause.setVisibility(View.VISIBLE);
}
holder.song_currenttime_sb.setActive();
holder.song_duration.setVisibility(View.INVISIBLE);
holder.song_duration_sb.setVisibility(View.VISIBLE);
holder.seekbar.setActive();
} else {
holder.seekbar.setInactive();
holder.song_currenttime_sb.setInactive();
holder.song_icon.setImageResource(rowItemClass.getImageId());
holder.song_duration_sb.setVisibility(View.INVISIBLE);
holder.song_duration.setVisibility(View.VISIBLE);
holder.pauseed_play.setVisibility(View.GONE);
holder.playing_pause.setVisibility(View.GONE);
}
sharedPreference = new SharedPreference();
holder.song_duration.setTypeface(Globals
.getTypefaceSecondary(context));
holder.song_duration_sb.setTypeface(Globals
.getTypefaceSecondary(context));
holder.song_name.setTypeface(Globals.getTypefacePrimary(context));
holder.song_currenttime_sb.setTypeface(Globals
.getTypefaceSecondary(context));
holder.song_name.setText(rowItemClass.getTitle());
holder.song_duration.setText(rowItemClass.getDesc());
holder.song_duration_sb.setText(rowItemClass.getDesc());
holder.favorite.setTag(position);
holder.song_currenttime_sb.setTag(position);
holder.seekbar.setTag(position);
holder.clickRegister.setTag(position);
holder.song_icon.setTag(position);
holder.song_name.setTag(position);
holder.song_duration.setTag(position);
holder.song_duration_sb.setTag(position);
holder.more_options.setTag(position);
// int task_id = (Integer) holder.seekbar.getTag();
final Ids finalHolder = holder;
holder.clickRegister.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
try {
if ((Globals.isPlaying.booleanValue())
&& (Globals.pos == position)) {
pausePlaying();
} else {
Globals.stopPlaying();
pausedSamePos = false;
Globals.pos = position;
Globals.isPlaying = true;
Item.this.notifyDataSetChanged();
Globals.mp = MediaPlayer.create(context, Integer
.valueOf(Item.this.songPos[position])
.intValue());
Globals.mp.start();
Globals.pos = position;
Globals.isPlaying = Boolean.valueOf(true);
Item.this.notifyDataSetChanged();
Globals.mp
.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(
MediaPlayer mpOnComplete) {
mpOnComplete.release();
Globals.isPlaying = false;
pausedSamePos = false;
Globals.isPlaying = Boolean
.valueOf(false);
finalHolder.menu_options
.startAnimation(new ViewExpandAnimation(
finalHolder.menu_options));
Item.this.notifyDataSetChanged();
}
});
}
} catch (Exception localException) {
}
}
});
holder.clickRegister
.setOnLongClickListener(new View.OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
Globals.stopPlaying();
Item.this.notifyDataSetChanged();
return true;
}
});
}
I've been struggling hard with a similar issue, trying to handle a use-case where all of the adapter's content has to be replaced and the recycler-view should start from scratch: calling notifyDataSetChanged(), swapAdapter() with numerous combinations of subsequent calls to view/layout-manager invalidation requests resulted in nothing but a (seemingly) empty recycler-view. The view didn't even try to rebind the view holders.
What seemed to have worked it out is this hack-ish fix:
view.swapAdapter(sameAdapter, true);
view.scrollBy(0, 0);
As it turns out, scrollBy (even with 0 offsets) drives the recycler-view into laying out its views and executing the pending view-holders rebinding.
if you want notify your recycleListView just simple call notifyDataSetChanged(); in your class adapter. this is my method in my adapter class :
public class ListAdapter extends RecyclerView.Adapter<Listdapter.ViewHolder> {
....
private List<DesaObject> desaObjects= new ArrayList<DesaObject>();
public void setListObjects(List<DesaObject> desaObjects){
if(this.desaObjects.size()>0)
this.desaObjects.clear();
this.desaObjects = desaObjects;
notifyDataSetChanged();
}
....
public static class ViewHolder extends RecyclerView.ViewHolder {
public final View mView;
public final AppCompatTextView desa;
public ViewHolder(View view) {
super(view);
mView = view;
desa = (AppCompatTextView) view.findViewById(R.id.desa);
}
}
}
in your code you dont actually set or change your listObjects in the OnClickListener and please try notifyDataSetChanged(); instead of Item.this.notifyDataSetChanged();
I am not sure why is this, but notifyDataSetChanged(); didn't work. So I tried keeping track of changed items and refreshing them manually with notifyItemChanged(int); and so far it seems to be working. I am still not sure why refreshing whole RecyclerView didn't work.
Initialize your Adapter Again with changed data and use method 'swapadapter'. hope it helps.

Force RecyclerView to call onCreateViewHolder

I have a RecyclerView that can show items as list, small grids or large grid and this can be change at runtime. Depending on what style user chooses i inflate different layout in onCreateViewHolder.
I also use layoutManger.setSpanSizeLookUp() to switch between styles. My code looks like this
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
if(showType == ProductAdapter.SHOW_TYPE_SMALL_GRID)
return 1;
else
return columnCount; //show one item per row
}
});
#Override
public void onClick(View v) {
if(showType == ProductAdapter.SHOW_TYPE_SMALL_GRID)
showType = ProductAdapter.SHOW_TYPE_LARGE_GRID;
else
showType = ProductAdapter.SHOW_TYPE_SMALL_GRID;
int firstVisibleItem = layoutManager.findFirstVisibleItemPosition();
adapter = new ProductAdapter(getActivity(), productList, showType);
recyclerView.setAdapter(adapter);
layoutManager.scrollToPosition(firstVisibleItem);
}
The problem is to force onCreateViewHolder to be called I'm creating a new object every time user changes the style. Is there any other way?! to force onBindViewHolder() to be recalled. I simply use adapter.notifyDataSetChanged() How can i get something similar for onCreateViewHolder?
Any solution that doesn't uses multiple adapters is good enough!
What you need to do is:
Modify your Adapter:
Specify two types of Views that your Adapter can inflate:
private static final int LARGE_GRID_ITEM = -1;
private static final int SMALL_GRID_ITEM = -2;
Create a field that can store current type mCurrentType
Use your Adapter's getItemViewType. For example like this:
#Override
public int getItemViewType (int position) {
return mCurrentType;
}
In your createViewHolder use the viewType to decide what type of ViewHolder you need to create.
public final RecyclerView.ViewHolder createViewHolder (ViewGroup parent, int viewType){
if (viewType == LARGE_GRID_ITEM) {
//return large grid view holder
} else {
//return small grid view holder
}
}
Additionally you can create methods:
public void toggleItemViewType () {
if (mCurrentType == LARGE_GRID_ITEM){
mCurrentType = SMALL_GRID_ITEM;
} else {
mCurrentType = LARGE_GRID_ITEM;
}
}
public boolean displaysLargeGrid(){
return mCurrentType == LARGE_GRID_ITEM;
}
Modify the code you posted:
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
if (adapter.displaysLargeGrid()) {
return 1;
} else {
return columnCount; //show one item per row
}
}
});
#Override
public void onClick(View v) {
adapter.toggleItemViewType();
adapter.notifyDataSetChanged();
}
Its not the optimal choice but it's better to create a new Adapter, which will call onCreateViewHolder(). This way you can avoid your troubles, by the cost of very tiny performance issues.

Categories

Resources