I want to use a toggle to toggle between two different views but using the same RecyclerView. Basically, once you toggle, I want the RecyclerView adapter to recall onCreateViewHolder() but this time it will use a different layout item file.
Does notifydatasetchanged() cause the adapter to rebuild itself? Or is there another way?
I needed to have two types on Views on my RecyclerView Adapter as well, one for 'regular' mode and one for multi-select mode.
So, you can override getItemViewType to force the Adapter to call your onCreateViewHolder for all views.
Add this to the Adapter code:
public void setActionMode(ActionMode actionMode) {
this.actionMode = actionMode;
notifyDataSetChanged();
}
#Override
public int getItemViewType(int position) {
return (actionMode == null ? 0 : 1);
}
Add this to the ViewHolder:
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
if (viewType == 0) {
view = inflater.inflate(R.layout.layout_1, parent, false);
} else {
view = inflater.inflate(R.layout.layout_2, parent, false);
}
...
}
Since you return a different ViewType when in an ActionMode, the Adapter is forced to throw away all created views, and recreate everything again.
notifyDataSetChanged() calls onBindViewHolder() in case of RecyclerView
THE SIMPLEST SOLUTION
If you want to refresh RecyclerView items and onCreateView() be called too, say for Grid and List LayoutManagers.
void refreshRecyclerView(RecyclerView recyclerView){
Adapter adapterRef=recyclerView.getAdapter();
recyclerView.setAdapter(null);
recyclerView.setAdapter(adapterRef);
}
it will completely refresh the RecyclerView
//example usage
refreshRecyclerView(yourRecyclerView);
To remove and update layout in RecyclerView, you can call
mRecyclerView.removeView(view);
OR
mRecyclerView.removeViewAt(position);
after removing object in your dataset
I spent more than 6 hours on this issue without any success.
Finally!!!
I set a global variable in the adapter and had to set it up every time i toggled the view from list to grid (in my case). the funny thing this approauch was there but I forgot to do it as static!! So my solution could be related to yours , just try it and hope it works out.
public static int mCurrentViewType;
then override the getItemType()
#Override
public int getItemViewType(int position) {
return mCurrentViewType;
}
my toggleItemViewType method:
public void toggleItemViewType () {
if (mCurrentViewType == LIST_ITEM){
mCurrentViewType = GRID_ITEM;
} else {
mCurrentViewType = LIST_ITEM;
}
}
I am accessing the variable from different classes, which is not right, but for now and for the sake of the onCreateViewHolder issue, it worked!
if you have a better solution then good luck and share it with us.
don't forget to make the global variable as "static" :)
Yes it will assume that its current data set is invalid and would need to relayout and rebind all layouts.
Related
My problem is: I have a video streaming happening on one of the views inside the RecyclerView.
When the user scrolls, the view gets recycled and other cameras starts their own streaming on that recycled viewholder. This is bad for user interface since the streaming process takes some seconds to start.
How can I say to the RecyclerView: "Hey Recycler, please, do not recycle that exact position x and give that position ALWAYS the same viewholder you gave it the first time, instead of random one"?
Please someone help me =(
In your getItemViewType(int position) method of adapter, assign unique values for each video, so it will always return same ViewHolder for same video as you wish.
return unique positive number as type for each video type (here i used the adapter position as unique key)
return negative numbers for any non-video items. (nothing special here, just to avoid conflicts with video items, we use negative numbers for non-video items)
I hope you get the idea. cheers :)
#Override
public int getItemViewType(int position) {
// Just as an example, return 0 or 2 depending on position
// Note that unlike in ListView adapters, types don't have to be contiguous
if(dataList.get(position).isVideo()){
return position;
}else{
return -1;//indicates general type, if you have more types other than video, you can use -1,-2,-3 and so on.
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case -1: View view1 = LayoutInflater.from(parent.getContext())
.inflate(R.layout.general_item, parent, false);
return new GeneralViewHolder(view1);
default:View view2 = LayoutInflater.from(parent.getContext())
.inflate(R.layout.video_item, parent, false);
return new VideoViewHolder(view2);
}
}
Perform viewHolder.setIsRecyclable(false) on the ViewHolder you want not to be recycled.
From docs of ViewHolder#setIsRecyclable(boolean):
Informs the recycler whether this item can be recycled. Views which are not recyclable will not be reused for other items until setIsRecyclable() is later set to true.
This will cause only one ViewHolder to be created.
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
...
#Override
public void onViewAttachedToWindow(final RecyclerView.ViewHolder holder) {
if (holder instanceof VideoViewHolder) {
holder.setIsRecyclable(false);
}
super.onViewAttachedToWindow(holder);
}
#Override
public void onViewDetachedFromWindow(final RecyclerView.ViewHolder holder) {
if (holder instanceof VideoViewHolder){
holder.setIsRecyclable(true);
}
super.onViewDetachedFromWindow(holder);
}
...
}
RecyclerView uses one view multiple times, when it contains the list which is not displaying on the screen at a time(means a list contain large amount of items which is not displaying on screen at same time you need to scroll up and down). When user scroll the list the offscreen items are reused to display the remaining list items which is called recycling.
To Stop recycling the items call this method in your onBindViewHolder method:
viewHolder.setIsRecyclable(false);
This statement stop the recycling the views.
To Start recycling the items call this method in your onBindViewHolder method:
viewHolder.setIsRecyclable(true);
I hope this will solve your problem.
Thanks
Your problem comes from the viewholder itself. Viewholders keep reference to views, while the adapter don't. The adapter keeps the data collection only. So, add a field to the viewholder to keep a reference of the data element you used to populate the view in the viewholder. In other words:
public class SomeViewHolder extends RecyclerView.ViewHolder{
private View view;
private Data data;
public SomeViewHolder(View itemView) {
super(itemView);
view = itemView;
}
public void bindData(Data data){
view.setData(data);
this.data = data;
}
public void setData(Data data){
this.data = data;
}
public Data getData(){
return data;
}
public View getView(){
return view;
}
}
Now, the viewholder know which element of the adapter is using. Therefore, when overriding the binding method in the adapter, you can check if the holder has already bonded with some data, and, if the data contains video, you can avoid the binding and forcefully set an already loaded view.
#Override
public void onBindViewHolder(SomeViewHolder holder, int position) {
//videoViewData is a data field you have to put into the adapter.
//videoView is a view field you have to put into the adapter.
if(adapterData.get(position).equals(videoViewData)){
holder.setView(videoView);
holder.setData(adapterData.get(position));
}else{
holder.bindData(adapterData.get(position));
if(adapterData.get(position).isVideo()){
videoViewData = adapterData.get(position);
videoView = holder.getView();
}
}
}
Finally, you'll have to override the onViewRecycled method in the adapter, so, when a view containing a video gets recycled, you can get the view and put it somewhere else.
public void onViewRecycled(SomeViewHolder holder){
if(holder.getData().isVideo()){
videoViewData = holder.getData().
videoView = holder.getView();
videoView.pauseVideo();
}
}
keep in mind, this can cause some serious leaks if you don't manage the stored view. Also, you have to define methods for telling when your data is video, and a properly defined equals method.
Best way to handle item not to recycle in recyclerview this answer will resolve your problem.
Not to recycle item
Try using this for that particular position:
holder.setIsRecyclable(false);
Hope this may help.
If You are using query, you can use
query.limit(//no of items you want to show in your RecyclerView)
give it a try.
or Plese post your QueryCode
I have the following issue. I populate a Recyclerview from my Fragment class. So far everything works out. However when I test my app and scroll up and down the populated recycler list the contents of each item change a.k.a. they get recycled...
How can I save each item's position and restore its content to the same position after scrolling?
Any suggestions?
Doing this holder.setIsRecyclable(false); will transform your RecyclerView into a ListView
Instead do this
Just override this two methods inside your RecyclerAdapter
#Override
public int getItemViewType(int position) {
return position;
}
#Override
public long getItemId(int position) {
return position;
}
Good Question, this is your answer holder.setIsRecyclable(false).
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.product_recycle_buyer_list_item, parent, false);
MyViewHolder holder = new MyViewHolder(view);
holder.setIsRecyclable(false);
return holder;
}
setIsRecyclable(false) is bad solution, as it will create more and more views as you scroll, which ruins the whole point of using RecyclerView. Not only it takes more CPU, but the more you scroll and see new items, the more memory it will use. This is even worse, if you display bitmaps, as bitmaps tend to take huge amount of memory.
What you are supposed to do instead, is to implement onBindViewHolder to bind the view to the data that it's supposed to have. Also use cache in case of using bitmaps.
You can look at a sample code I've made here, which asks of a different problem I'd like to solve.
In case someone experiences this, holder.setIsRecyclable(false) will do it but then, it just makes the recycler view a list view, and also consumes more resources . Overriding the getItemViewType and getItemId should fix it.
As they said above
setIsRecyclable() is a bad choice and won't fix it.
Override the 2 methods getItemId & getItemViewType and return position for both this will fix it.
If I understand the question correctly, you don't want to scroll above when you call set adapter.
The best way I found is to call
YourRcv.swapAdapter(YourAdapter, Bolean removeAndRecycleExistingViews); // true if yes false if no
I found this method at line 1142 in package androidx.recyclerview.widget , class RecyclerView.java
I am trying to get child view by position. I could get view when one item is clicked:
rvSellRecords.addOnItemTouchListener(new RecyclerItemClickListener(getActivity(), new RecyclerItemClickListener.OnItemClickListener() {
#Override
public void onItemClick(View view, int position) {
((MainActivity) getActivity()).showSellRecordFragment(position, view);
}
}));
Now I cannot get child view, without click - let's say by position for example:
rvSellRecords.someMagicalMethodWhichReturnsViewByPosition(5);
Question: How to get child view from RecyclerView?
EDIT FOR BOUNTY:
I have RecyclerView to show products list. When I click on it, I am adding new Fragment where I show product information. While opening I am updating toolbar with view from RecyclerView - this is working perfectly:
rvSellRecords.addOnItemTouchListener(new RecyclerItemClickListener(getContext(), new RecyclerItemClickListener.OnItemClickListener() {
#Override
public void onItemClick(View view, int position) {
sellPresenter.onSellRecordSelected(position, view);
}
}));
When I click blue button with "+", I am incrementing quantity by 1.
public void onIncrementButtonClicked(){
sellRecord.setCount(sellRecord.getCount() + 1);
showQuantity();
bus.post(new SellRecordChangedEvent(sellRecord, sellRecordPosition));
}
Then I am posting updated sellRecord to first fragment using EventBus. There I am updating list data. I supposed that updating value(sell) automatically updates adapter. Now I am getting view from adapter using custom method(getView) which was created by me(you can find it below).
#Subscribe
public void onEvent(SellRecordChangedEvent event){
sell.getSellRecords().set(event.getSellRecordPosition(), event.getSellRecord());
sell.recalculate();
int position = event.getSellRecordPosition();
View view = adapter.getView(position);
bus.post(new TransactionTitleChangedEvent(null, view));
}
This is my adapter class - I changed adapter little bit to collect view in list and added method which returns view for respective position:
public class SellRecordsAdapter extends RecyclerView.Adapter<SellRecordsAdapter.ViewHolder> {
.....
.....
.....
List<View> viewList;
public SellRecordsAdapter(List<SellRecord> sellRecordList) {
.....
viewList = new ArrayList<>();
}
.....
.....
.....
#Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
.....
.....
.....
viewList.add(i, viewHolder.itemView);
}
public View getView(int position){
return viewList.get(position);
}
}
My problem: when I updating view in toolbar, I am getting old view. When quantity is 3, I am getting view with 2. When quantity 10 - view is with 9.
My question: how to get view from recycler view using position of item(without on click listener)?
Use recyclerView.findViewHolderForLayoutPosition(position) or
reyclerView.findViewHolderForAdapterPosition(position) to get the viewholder for postion. Then you can access any child from your viewholder.
Checkout Recyclerview
RecyclerView.ViewHolder holder = recycleView.findViewHolderForAdapterPosition(position);
ImageView imageView = holder.itemView.findViewById(R.id.iv_product);
This is a supplement to #Ravi Teja's answer. You can get the viewHolder from the recyclerView using position of the particular item, then get a particular view from the viewHolder as shown above
You can use RecyclerView's LayoutManager for it.
View view = layoutManager.findViewByPosition(position)
Hope this helps someone:
I was getting null pointer exceptions with:
recyclerView.findViewHolderForAdapterPosition
recyclerView.findViewHolderForItemId
layoutManager.findViewByPosition.
The reason was that there is a slight delay for the viewholder to be created.
I found the solution here: https://stackoverflow.com/a/33414430/7952427
I post an answer because which is really complex to findviews() from RecyclerView.
#Joe: After spending 4hours found one answer. Which gives me the proper view of the index.
mAdapter is adapter of RecyclerView
View v = recyclerView.findViewHolderForItemId(mAdapter.getItemId(index/position)).itemView;
Now just access your views by:
v.findViewById(R.id.edittext) OR any id.
it helped me, make a 100 ms delay before manipulate it, like this:
Handler handler = new Handler();
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
// rcv is my recyclerview
rcvStatus.getChildAt(1).setBackground(getActivity().getResources().getDrawable(R.drawable.disabled));
// or:
rcvStatus.getChildAt(1).setClickable(false);
}
}, 100);
Write this method in adapter.
public Object getItem(int position) {
return yourArrayList.get(position);
}
and you just need to call it like
yourAdapter.getItem(2);
pass your required position.
Hope it solves your problem.
just put this method in your code and you can call it as you likes
void someMagicalMethodWhichReturnsViewByPosition(int position){
//I assumes child views are CardView
CardView c = (CardView)rvSellRecords.getItem(int position);
///optional codes
//////////
}
now I understand your problem. you need to use interface for join recyclerview item and activity.
you must define an interface class like below:
public interface IViewClick {
public void onClickButtonAdd();
}
add this parameter to your adapter class:
private IViewClick mListener;
and initialize it in constructor with value that get from inputs.
when user click on PLUS button, you send event to activity by this line:
mListener.onClickButtonAdd();
in your activity class you must implements IViewClick interface and add your code there, like this:
#Override
public void onClickButtonAdd() {
/// TODO every thing that you want.
/// change your toolbar values.
}
it is not good solution for you.
RecyclerView.ViewHolder holder =
mRecyclerView.findViewHolderForItemId(mAdapter.getItemId(i));
I wouldn't recommend tracking the view list yourself. It could lead to weird issues with item updates, position updates, etc.
Instead on your SellRecordChangedEvent, use findViewHolderForAdapterPosition() instead of adapter.getView().
#Subscribe
public void onEvent(SellRecordChangedEvent event){
sell.getSellRecords().set(event.getSellRecordPosition(), event.getSellRecord());
sell.recalculate();
int position = event.getSellRecordPosition();
View view = yourrecyclerview.findViewHolderForAdapterPosition(position);
bus.post(new TransactionTitleChangedEvent(null, view));
}
http://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#findViewHolderForAdapterPosition(int)
And as a side note, it's better to implement an actual item click listener to the itemView on the ViewHolder instead of using touch listener. There's lots of examples of this online.
So the recyclerview and your product information are in 2 different fragments yes? You are expecting the recyclerview's views to update when they are not even in foreground? also you are changing adapter data item's data at position event.getSellRecordPosition() , but you are not notifying the adapter that its dataset changed, either by adapter.notifyDataSetChanged() or the other notifyItemChanged(position) methods.
I'd modify your onEvent() like so:
#Subscribe
public void onEvent(SellRecordChangedEvent event){
sell.getSellRecords().set(event.getSellRecordPosition(), event.getSellRecord());
sell.recalculate();
int position = event.getSellRecordPosition();
MyViewHolder holder = adapter.onCreateViewHolder(yourRecyclerView, 0);
adapter.onBindViewHolder(holder,position);
View view = adapter.getView(position);
bus.post(new TransactionTitleChangedEvent(null, view));
}
Calling on createViewHolder and next BindViewHolder on your adapter will definitely update the views for that position, then your adapter.getView(position) should return you the latest view.
Here MyViewHolder is your viewholder class and yourRecyclerview, is the reference to your recycler view
for (int i = 0; i < recycler_view.getAdapter().getItemCount(); i++) {
View viewTelefone = recycler_view.getChildAt(i);
}
If you want to replace text on a particular edit text for same position:
for (int i = 0; i < recycler_view.getAdapter().getItemCount(); i++) {
if(adpterPostion==i)
{
View viewTelefone = recycler_view.getChildAt(i);
EditText et_mobile = (EditText) viewTelefone.findViewById(R.id.et_mobile);
et_mobile.setText("1111111");
}
}
I have a RecyclerView which loads some data from API, includes an image url and some data, and I use networkImageView to lazy load image.
#Override
public void onResponse(List<Item> response) {
mItems.clear();
for (Item item : response) {
mItems.add(item);
}
mAdapter.notifyDataSetChanged();
mSwipeRefreshLayout.setRefreshing(false);
}
Here is implementation for Adapter:
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, final int position) {
if (isHeader(position)) {
return;
}
// - get element from your dataset at this position
// - replace the contents of the view with that element
MyViewHolder holder = (MyViewHolder) viewHolder;
final Item item = mItems.get(position - 1); // Subtract 1 for header
holder.title.setText(item.getTitle());
holder.image.setImageUrl(item.getImg_url(), VolleyClient.getInstance(mCtx).getImageLoader());
holder.image.setErrorImageResId(android.R.drawable.ic_dialog_alert);
holder.origin.setText(item.getOrigin());
}
Problem is when we have refresh in the recyclerView, it is blincking for a very short while in the beginning which looks strange.
I just used GridView/ListView instead and it worked as I expected. There were no blincking.
configuration for RecycleView in onViewCreated of my Fragment:
mRecyclerView = (RecyclerView) view.findViewById(R.id.recyclerView);
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
mRecyclerView.setHasFixedSize(true);
mGridLayoutManager = (GridLayoutManager) mRecyclerView.getLayoutManager();
mGridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
return mAdapter.isHeader(position) ? mGridLayoutManager.getSpanCount() : 1;
}
});
mRecyclerView.setAdapter(mAdapter);
Anyone faced with such a problem? what could be the reason?
Try using stable IDs in your RecyclerView.Adapter
setHasStableIds(true) and override getItemId(int position).
Without stable IDs, after notifyDataSetChanged(), ViewHolders usually assigned to not to same positions. That was the reason of blinking in my case.
You can find a good explanation here.
According to this issue page ....it is the default recycleview item change animation... You can turn it off.. try this
recyclerView.getItemAnimator().setSupportsChangeAnimations(false);
Change in latest version
Quoted from Android developer blog:
Note that this new API is not backward compatible. If you previously
implemented an ItemAnimator, you can instead extend
SimpleItemAnimator, which provides the old API by wrapping the new
API. You’ll also notice that some methods have been entirely removed
from ItemAnimator. For example, if you were calling
recyclerView.getItemAnimator().setSupportsChangeAnimations(false),
this code won’t compile anymore. You can replace it with:
ItemAnimator animator = recyclerView.getItemAnimator();
if (animator instanceof SimpleItemAnimator) {
((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
}
This simply worked:
recyclerView.getItemAnimator().setChangeDuration(0);
I have the same issue loading image from some urls and then imageView blinks.
Solved by using
notifyItemRangeInserted()
instead of
notifyDataSetChanged()
which avoids to reload those unchanged old datas.
try this to disable the default animation
ItemAnimator animator = recyclerView.getItemAnimator();
if (animator instanceof SimpleItemAnimator) {
((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
}
this the new way to disable the animation since android support 23
this old way will work for older version of the support library
recyclerView.getItemAnimator().setSupportsChangeAnimations(false)
In Kotlin you can use 'class extension' for RecyclerView:
fun RecyclerView.disableItemAnimator() {
(itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false
}
// sample of using in Activity:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// ...
myRecyclerView.disableItemAnimator()
// ...
}
Kotlin solution:
(recyclerViewIdFromXML.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
Recyclerview uses DefaultItemAnimator as it's default animator.
As you can see from the code below, they change the alpha of the view holder upon item change:
#Override
public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromX, int fromY, int toX, int toY) {
...
final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView);
...
ViewCompat.setAlpha(oldHolder.itemView, prevAlpha);
if (newHolder != null) {
....
ViewCompat.setAlpha(newHolder.itemView, 0);
}
...
return true;
}
I wanted to retain the rest of the animations but remove the "flicker" so I cloned DefaultItemAnimator and removed the 3 alpha lines above.
To use the new animator just call setItemAnimator() on your RecyclerView:
mRecyclerView.setItemAnimator(new MyItemAnimator());
Assuming mItems is the collection that backs your Adapter, why are you removing everything and re-adding? You are basically telling it that everything has changed, so RecyclerView rebinds all views than I assume the Image library does not handle it properly where it still resets the View even though it is the same image url. Maybe they had some baked in solution for AdapterView so that it works fine in GridView.
Instead of calling notifyDataSetChanged which will cause re-binding all views, call granular notify events (notify added/removed/moved/updated) so that RecyclerView will rebind only necessary views and nothing will flicker.
Try this in Kotlin
binding.recyclerView.apply {
(itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
}
In my case, neither any of above nor the answers from other stackoverflow questions having same problems worked.
Well, I was using custom animation each time the item gets clicked, for which I was calling notifyItemChanged(int position, Object Payload) to pass payload to my CustomAnimator class.
Notice, there are 2 onBindViewHolder(...) methods available in RecyclerView Adapter.
onBindViewHolder(...) method having 3 parameters will always be called before onBindViewHolder(...) method having 2 parameters.
Generally, we always override the onBindViewHolder(...) method having 2 parameters and the main root of problem was I was doing the same,
as each time notifyItemChanged(...) gets called, our onBindViewHolder(...) method will be called, in which I was loading my image in ImageView using Picasso, and this was the reason it was loading again regardless of its from memory or from internet. Until loaded, it was showing me the placeholder image, which was the reason of blinking for 1 sec whenever I click on the itemview.
Later, I also override another onBindViewHolder(...) method having 3 parameters. Here I check if the list of payloads is empty, then I return the super class implementation of this method, else if there are payloads, I am just setting the alpha value of the itemView of holder to 1.
And yay I got the solution to my problem after wasting a one full day sadly!
Here's my code for onBindViewHolder(...) methods:
onBindViewHolder(...) with 2 params:
#Override
public void onBindViewHolder(#NonNull RecyclerAdapter.ViewHolder viewHolder, int position) {
Movie movie = movies.get(position);
Picasso.with(context)
.load(movie.getImageLink())
.into(viewHolder.itemView.posterImageView);
}
onBindViewHolder(...) with 3 params:
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position, #NonNull List<Object> payloads) {
if (payloads.isEmpty()) {
super.onBindViewHolder(holder, position, payloads);
} else {
holder.itemView.setAlpha(1);
}
}
Here's the code of method I was calling in onClickListener of viewHolder's itemView in onCreateViewHolder(...):
private void onMovieClick(int position, Movie movie) {
Bundle data = new Bundle();
data.putParcelable("movie", movie);
// This data(bundle) will be passed as payload for ItemHolderInfo in our animator class
notifyItemChanged(position, data);
}
Note: You can get this position by calling getAdapterPosition() method of your viewHolder from onCreateViewHolder(...).
I have also overridden getItemId(int position) method as follows:
#Override
public long getItemId(int position) {
Movie movie = movies.get(position);
return movie.getId();
}
and called setHasStableIds(true); on my adapter object in activity.
Hope this helps if none of the answers above work!
In my case there was a much simpler problem, but it can look/feel very much like the problem above. I had converted an ExpandableListView to a RecylerView with Groupie (using Groupie's ExpandableGroup feature). My initial layout had a section like this:
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/hint_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/white" />
With layout_height set to "wrap_content" the animation from expanded group to collapsed group felt like it would flash, but it was really just animating from the "wrong" position (even after trying most of the recommendations in this thread).
Anyway, simply changing layout_height to match_parent like this fixed the problem.
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/hint_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/white" />
Hey #Ali it might be late replay. I also faced this issue and solved with below solution, it may help you please check.
LruBitmapCache.java class is created to get image cache size
import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import com.android.volley.toolbox.ImageLoader.ImageCache;
public class LruBitmapCache extends LruCache<String, Bitmap> implements
ImageCache {
public static int getDefaultLruCacheSize() {
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
final int cacheSize = maxMemory / 8;
return cacheSize;
}
public LruBitmapCache() {
this(getDefaultLruCacheSize());
}
public LruBitmapCache(int sizeInKiloBytes) {
super(sizeInKiloBytes);
}
#Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
#Override
public Bitmap getBitmap(String url) {
return get(url);
}
#Override
public void putBitmap(String url, Bitmap bitmap) {
put(url, bitmap);
}
}
VolleyClient.java singleton class [extends Application] added below code
in VolleyClient singleton class constructor add below snippet to initialize the ImageLoader
private VolleyClient(Context context)
{
mCtx = context;
mRequestQueue = getRequestQueue();
mImageLoader = new ImageLoader(mRequestQueue,getLruBitmapCache());
}
I created getLruBitmapCache() method to return LruBitmapCache
public LruBitmapCache getLruBitmapCache() {
if (mLruBitmapCache == null)
mLruBitmapCache = new LruBitmapCache();
return this.mLruBitmapCache;
}
Hope its going to help you.
Try to use the stableId in recycler view. The following article briefly explains it
https://medium.com/#hanru.yeh/recyclerviews-views-are-blinking-when-notifydatasetchanged-c7b76d5149a2
I had similar issue and this worked for me
You can call this method to set size for image cache
private int getCacheSize(Context context) {
final DisplayMetrics displayMetrics = context.getResources().
getDisplayMetrics();
final int screenWidth = displayMetrics.widthPixels;
final int screenHeight = displayMetrics.heightPixels;
// 4 bytes per pixel
final int screenBytes = screenWidth * screenHeight * 4;
return screenBytes * 3;
}
for my application, I had some data changing but I didn't want the entire view to blink.
I solved it by only fading the oldview down 0.5 alpha and starting the newview alpha at 0.5. This created a softer fading transition without making the view disappear completely.
Unfortunately because of private implementations, I couldn't subclass the DefaultItemAnimator in order to make this change so I had to clone the code and make the following changes
in animateChange:
ViewCompat.setAlpha(newHolder.itemView, 0); //change 0 to 0.5f
in animateChangeImpl:
oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() { //change 0 to 0.5f
Using appropriate recyclerview methods to update views will solve this issue
First, make changes in the list
mList.add(item);
or mList.addAll(itemList);
or mList.remove(index);
Then notify using
notifyItemInserted(addedItemIndex);
or
notifyItemRemoved(removedItemIndex);
or
notifyItemRangeChanged(fromIndex, newUpdatedItemCount);
Hope this will help!!
In my case I used SwipeRefresh and RecycleView with viewmodel binding and faced with blinking. Solved with ->
Use submitList() to keep the list updated
because DiffUtils have done the job, otherwise the list reloading entirely
refer to CodeLab https://developer.android.com/codelabs/kotlin-android-training-diffutil-databinding#4
when using livedata I solved it with diffUtil
This is how I combined diffUtill and databinding with my adapter
class ItemAdapter(
private val clickListener: ItemListener
) :
ListAdapter<Item, ItemAdapter.ViewHolder>(ItemDiffCallback()) {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(clickListener, getItem(position)!!)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent)
}
class ViewHolder private constructor(val binding: ListItemViewBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(
clickListener: ItemListener,
item: Item) {
binding.item = item
binding.clickListener = clickListener
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = ListItemViewBinding
.inflate(layoutInflater, parent, false)
return ViewHolder(view)
}
}
}
class ItemDiffCallback :
DiffUtil.ItemCallback<Item>() {
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem.itemId == newItem.itemId
}
override fun getChangePayload(oldItem: Item, newItem: Item): Any? {
return newItem
}
override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem == newItem
}
}
}
class ItemListener(val clickListener: (item: Item) -> Unit) {
fun onClick(item: Item) = clickListener(item)
}
In my case (shared element transition between one image to a higher resolution image), I added some delay to the item decorator in order to make it less noticeable:
(yourRv.itemAnimator as? DefaultItemAnimator)?.changeDuration = 2000
My own issue was a very specific problem which I'm going to share some insight for here, though I don't yet fully understand it.
Basically I had a re-usable fragment with RecyclerView, so that I could have a menu with nested menus. Pick an item in the RecyclerView, and it opens another fragment with more options. All facilitated using the JetPack navigation component and data binding using LiveData in the layout xml.
Anyway, here's how I fixed my issue of items flickering when the RecyclerView changed (although it's worth bearing in mind, this was only the appearance as it was a 'new' RecyclerView each time). To update the LiveData list of items (some viewmodels representing objects for the menu items, in my case) I was using LiveData.value = new items. Changing it to postValue(new items) fixed the issue, though I'm not yet sure why.
I've read up the difference between value (setValue in Java) and PostValue, and I understand they're to do with using the main thread or background threading, and the latter only gets applied one time on the main thread when it's ready. But other than that, I'm not sure why this fixed flickering in my RecyclerView. Maybe someone has some insight? In any case, hopefully this will help someone facing a similar problem to me.
for me recyclerView.setHasFixedSize(true); worked
I have been playing around with RecyclerView for a little bit. Is there any easy way to put OnClickListener for items in RecyclerView? I have tried implementing it in ViewHolder. The onClick event never got triggered.
And I have used notifyItemInserted(position) for adding new value into RecyclerView. The UI does not got refreshed automatically. Needed to pull up and down to refresh. But when I invoke notifyDatasetChanged(..), it is ok.
I have applied DefaultItemAnimator to RecyclerView. But, not seeing any animation when new item added.
Thanks advance for any idea.
This is the first Android L component I have tested out and I am stucking there.
Here is my Adapter class:
public class AdapterRecyclerView extends RecyclerView.Adapter<AdapterRecyclerView.MyViewHolder> {
private List<String> arrExperiences;
//Provide a reference to the type of views that you are using - Custom ViewHolder
public class MyViewHolder extends RecyclerView.ViewHolder {
public TextView tvExperienceTitle;
public TextView tvExperienceDesc;
public MyViewHolder(RelativeLayout itemView) {
super(itemView);
tvExperienceTitle = (TextView) itemView.findViewById(R.id.tv_experience_title);
tvExperienceDesc = (TextView) itemView.findViewById(R.id.tv_experience_desc);
}
}
//Provide a suitable constructor : depending on the kind of dataset.
public AdapterRecyclerView(List<String> arrExperiences){
this.arrExperiences = arrExperiences;
}
//Create new view : invoke by a Layout Manager
#Override
public AdapterRecyclerView.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RelativeLayout view = (RelativeLayout) LayoutInflater.from(parent.getContext()).inflate(R.layout.view_item_recycler, parent, false);
MyViewHolder myViewHolder = new MyViewHolder(view);
return myViewHolder;
}
#Override
public void onBindViewHolder(AdapterRecyclerView.MyViewHolder viewHolder, int position) {
//get element from your dataset at this position.
//replace the content of the view with this element.
viewHolder.tvExperienceTitle.setText(arrExperiences.get(position));
}
#Override
public int getItemCount() {
return arrExperiences.size();
}
public void addExperience(String experience, int position){
arrExperiences.add(position, experience);
notifyItemInserted(position);
//notifyDataSetChanged();
}
public void removeExperience(){
int index = (int) (Math.random() * arrExperiences.size());
arrExperiences.remove(index);
notifyItemRemoved(index);
//notifyDataSetChanged();
}
}
Simply add this in your Adapter:
#Override
public void onBindViewHolder(AdapterRecyclerView.MyViewHolder viewHolder, int position) {
yourItems.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
//do your stuff
}
});
}
Please see my answer here. You do need an extra class (which may be included as part of the full release) but it will allow you to create OnItemClickListeners the way you are used to for ListViews.
Since you still didn't mark correct any answer, and even if it's an old question, I will try to provide the way I do. I think it is very clean and professional. The functionalities are taken from different blogs (I still have to mention them in the page), merged and methods have been improved for speed and scalability, for all activities that use a RecycleView.
https://github.com/davideas/FlexibleAdapter
At lower class there is SelectableAdapter that provides selection functionalities and it's able to maintain the state after the rotation, you just need to call the onSave/onRestore methods from the activity.
Then the class FlexibleAdapter handles the content with the support of the animation (calling notify only for the position. Note: you still need to set your animation to the RecyclerView when you create it the activity).
Then you need to extend over again this class. Here you add and implements methods as you wish for your own ViewHolder and your Domain/Model class (data holder). Note: I have provided an example which does not compile because you need to change the classes with the ones you have in your project.
I think that, it's the ViewHolder that should keep the listeners of the clicks and that it should be done at the creation and not in the Binding method (that is called at each invalidate from notify...() ).
Also note that this adapter handles the basic clicks: single and long clicks, if you need a double tap you need to use the way Jabob Tabak does in its answer.
I still have to improve it, so keep an eye on it. I also want to add some new functionalities like the Undo.
Here you get a simple Adapter class which can perform onItemClick event on each list row for the recyclerview.