I am using a custom recyclerview adapter with checkbox so that user can select multiple checked items.
In the beginning I faced duplicate checkbox selection while scrolling down so I added a position array to keep checkbox selection true or false.Now duplicate checkbox selection problem is gone but while scrolling down selected checkbox are deselected.My recyclerview adpater is given below,
class IngredientsAdapter(var context: Context?,var activity: Activity, var ingredientList:ArrayList<String>, var imageID:Int):
RecyclerView.Adapter<IngredientsAdapter.IngredientViewHolder>() {
private var positionArray:ArrayList<Boolean> = ArrayList(ingredientList.size)
private val selectedList=ArrayList<String>()
init {
for (i in ingredientList.indices) {
positionArray.add(false)
}
}
override fun onCreateViewHolder(p0: ViewGroup, p1: Int): IngredientViewHolder {
val layoutInflater = LayoutInflater.from(p0.context)
return (IngredientsAdapter.IngredientViewHolder(layoutInflater.inflate(R.layout.ingredient_list_row,p0,false)))
}
override fun getItemCount(): Int {
return ingredientList.size
}
override fun onBindViewHolder(p0: IngredientViewHolder, p1: Int) {
p0.imageView.setImageResource(imageID)
p0.textView.text = ingredientList[p1]
p0.checkBox.isChecked = positionArray[p1]
val sharedPreferences= SharedPreferenceHelper(context, SystemConstants.SHARED_PREFS_CHECKDATA)
val checked=sharedPreferences.getPrefsBooleanValue(ingredientList[p1])
p0.checkBox.isChecked = checked
p0.checkBox.setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
if(isChecked){
positionArray[p1] = true
selectedList.add(ingredientList[p1])
ingredientList.get(p1)
sharedPreferences.addPrefsBooleanVal(ingredientList[p1],true)
Log.d("debug","selecteditem==>$selectedList")
}else{
positionArray[p1] = false
selectedList.remove(ingredientList[p1])
sharedPreferences.addPrefsBooleanVal(ingredientList[p1],false)
Log.d("debug","selecteditem==>$selectedList")
}
})
}
class IngredientViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var textView= itemView.txt_row!!
var imageView= itemView.image_view!!
var checkBox= itemView.chk_row!!
}
}
Any suggestion is appreciated.
OnClickListener won't work when sliding the Switch.
Since RecyclerView is recycling views, a previously attached OnCheckedChangeListener can be triggered when setting checked value for the Switch of the new item.
When binding new data to an item:
switch.setOnCheckedChangeListener(null) // remove any existing listener from recycled view
switch.isChecked = [true/false] // will no longer trigger any callback to listener
switch.setOnCheckedChangeListener { btnView, isChecked ->
// do exiting stuff
}
What #Bek suggested is the correct solution. You will need to use OnClickListener. Also what you commented is not wrong either, just what you used to use(ListView) is or was!
Let me break it down for you:
ListView: The older version of RecyclerView.
Wonder why the developer created another redundant component? Basically Memory Issue! ListView creates as many ItemViews as it requires whereas RecyclerView just recycles the itemView.
For example if for a list of 100 items if we use ListView it will initially create as many ItemViews as the phone can display. Then as we scroll down it keeps creating more Views, till the 100th item(100 ItemViews in memory). But RecyclerView does this more efficiently it creates as many views it can show + 1 (Next view in list) then it keeps recycling them so we always have only As many views in screen +1 and not 100 when we reach the bottom of the list.
For more details read this and this.
OnCheckChangeListener: The Other problem maker! This listener is called whenever check changes for the checkbox, Whenever! So if i where to refresh a checkbox this listener(theoretically) will be called! Getting where am going with this? Yup when used along with RecyclerView its gonna cause an havoc in ones code. The moment recyclerView destroys or reuses an ItemView the checkbox is reset which fires the listener and causing your SharedPref to rewrite the check! I tested this by adding logs inside the listener and saw it get triggered for outer most views when it got recycled.
This is just my findings, there maybe some way or fix for this but i too would suggest using OnClickListener and write a listener to change the model in main class rather than sharedPref in adapter.
Oh! You can use ViewHolder.SetIsRecyclable(false), this would stop RecyclerView from Recycling the views and create as many views as there are items in list. But i wouldn't suggest this as the UI as well as UX will be compromised as u might find a bit lag while scrolling(Memory Issue)!
Long Story Short use OnClickListener with RecyclerView!
When you call setOnCheckedChangeListener, checkbox has a listener, then recyclerview will multiplex view when you scroll.
and you apply p0.checkBox.isChecked = positionArray[p1] , actually you change last poisiton.
For example , you check the first checkbox, arrray[0] is true, then you scroll the view , the next item may the fourth or fifth will have the same checkbox refrence.
Then you call p0.checkBox.isChecked = positionArray[p1] again, but the listener belong the first item . then the listener will change array[0].
So you should call p0.checkBox.setOnCheckedChangeListener before p0.checkBox.isChecked = positionArray[p1]
You can use Model class to keep track of each recyclerView item's checkbox. Use setTag and getTag is used to keep track of checkbox status.
Make Model
public class Model {
private boolean isSelected;
private String animal;
public String getAnimal() {
return animal;
}
public void setAnimal(String animal) {
this.animal = animal;
}
public boolean getSelected() {
return isSelected;
}
public void setSelected(boolean selected) {
isSelected = selected;
}
}
create integer.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="btnplusview">1</integer>
<integer name="btnpluspos">2</integer>
</resources>
Finally adapter looks like this:
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
public class CustomAdapter extends
RecyclerView.Adapter<CustomAdapter.MyViewHolder> {
private LayoutInflater inflater;
public static ArrayList<Model> imageModelArrayList;
private Context ctx;
public CustomAdapter(Context ctx, ArrayList<Model> imageModelArrayList) {
inflater = LayoutInflater.from(ctx);
this.imageModelArrayList = imageModelArrayList;
this.ctx = ctx;
}
#Override
public CustomAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int
viewType) {
View view = inflater.inflate(R.layout.rv_item, parent, false);
MyViewHolder holder = new MyViewHolder(view);
return holder;
}
#Override
public void onBindViewHolder(final CustomAdapter.MyViewHolder holder, int
position) {
holder.checkBox.setText("Checkbox " + position);
holder.checkBox.setChecked(imageModelArrayList.get(position).getSelected());
holder.tvAnimal.setText(imageModelArrayList.get(position).getAnimal());
// holder.checkBox.setTag(R.integer.btnplusview, convertView);
holder.checkBox.setTag(position);
holder.checkBox.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Integer pos = (Integer) holder.checkBox.getTag();
Toast.makeText(ctx, imageModelArrayList.get(pos).getAnimal() + "
clicked!", Toast.LENGTH_SHORT).show();
if (imageModelArrayList.get(pos).getSelected()) {
imageModelArrayList.get(pos).setSelected(false);
} else {
imageModelArrayList.get(pos).setSelected(true);
}
}
});
}
#Override
public int getItemCount() {
return imageModelArrayList.size();
}
class MyViewHolder extends RecyclerView.ViewHolder {
protected CheckBox checkBox;
private TextView tvAnimal;
public MyViewHolder(View itemView) {
super(itemView);
checkBox = (CheckBox) itemView.findViewById(R.id.cb);
tvAnimal = (TextView) itemView.findViewById(R.id.animal);
}
}
}
The setup I've got a RecyclerView with a custom adapter and custom ViewHolders.
I understand the onCreateViewHolder() method is called when the RecyclerView is running out of ViewHolders to recycle and needs a new one. So I'm just inflating a layout in there and passing it to a new ViewHolder.
Furthermore, onBindViewHolder() is responsible for filling the ViewHolder with data as soon as a new ViewHolder has been created or recycled by the RecyclerView. So what I'm doing in there is calling my method holder.setNode() to pass a data object to the ViewHolder.
The behavior I'm seeing When the activity first launches, all entries are correct. When I'm adding new entries or deleting existing ones, however, things start to get a bit funny.
the title TextView is always set correctly
the background color of the main layout changes seemingly at will, I'm assuming because the RecyclerView is reusing old ones
as does the custom view I have implemented, even though I'm invalidating it and passing it new values which change its appearance noticeably
So I'm wondering: Why aren't those values changed in onBindViewHolder() as soon as views get reused? Or if I'm wrong, what's the real reason for the random switching of layouts?
TaskListAdapter
class TaskListAdapter extends RecyclerView.Adapter<TaskListAdapter.TaskViewHolder> {
private ArrayList<NodeHandler.DbNode> dbNodeList = new ArrayList<>();
...
#Override
public TaskViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.small_task_view, parent, false);
return new TaskViewHolder(v);
}
#Override
public void onBindViewHolder(TaskViewHolder holder, int position) {
final NodeHandler.DbNode dbNode = dbNodeList.get(position);
holder.setNode(dbNode);
holder.wrapper.findViewById(R.id.card_details).setVisibility(View.GONE);
}
...
public static class TaskViewHolder extends RecyclerView.ViewHolder implements ItemTouchHelperViewHolder {
private FrameLayout wrapper;
private TextView title;
private NodeHandler.DbNode dbNode;
public TaskViewHolder(View view) {
...
}
public void setTitle(String str) {
title.setText(str);
}
public void setMarkers(#IntRange(from = 1, to = Node.MAX_URGENCY) int urgency, #IntRange(from = 1, to = Node.MAX_IMPORTANCE) int importance) {
if(!dbNode.isAppointment()) {
wrapper.setBackgroundColor(ContextCompat.getColor(wrapper.getContext(), R.color.lightGray));
}
((QuadrantView) wrapper.findViewById(R.id.quadrant_view)).setDimensions(importance, urgency);
// setDimensions will invalidate the view
}
public void setNode(NodeHandler.DbNode dbNodeObject) {
this.dbNode = dbNodeObject;
setTitle(dbNode.toString());
setMarkers(dbNode.getUrgency(), dbNode.getImportance());
setTips();
}
}
}
Let me know if anything else could matter here. I'd be happy to update the question accordingly.
Values are indeed changed in onBindViewHolder as soon as views get reused.
The real reason for the seemingly random switching of layouts is that onBindViewHolder is currently implemented in a way that assumes that the ViewHolder was freshly created and is being bound for its first time. onBindViewHolder should instead be implemented in a way that assumes that the ViewHolder being bound is being reused so it should either:
reset all the values of the ViewHolder to default values first before setting them to other values or
make sure that everything is set inside onBindViewHolder, so one cannot tell that it was ever previously bound to something else.
Random background color changes:
You are right for suspecting that the random background color problem is caused by the RecyclerView reusing ViewHolders.
The reason why this is happening is because of the following code:
if(!dbNode.isAppointment()) {
wrapper.setBackgroundColor(ContextCompat.getColor(wrapper.getContext(), R.color.lightGray));
}
It only sets the background if the ViewHolder is not an appointment. So if a ViewHolder that is being reused was previously not for an appointment, but is currently for one that is now an appointment, it's background color will be inappropriate.
to fix this, do any of the following:
set the background color of the ViewHolder to some default color before the if statement is executed (as per solution 1 mentioned above):
wrapper.setBackgroundColor(/* default background color */);
if(!dbNode.isAppointment()) {
wrapper.setBackgroundColor(ContextCompat.getColor(wrapper.getContext(), R.color.lightGray));
}
add an else block to the if statement to set the background color of the ViewHolder to the appropriate color (as per solution 2 mentioned above)
if(!dbNode.isAppointment()) {
wrapper.setBackgroundColor(ContextCompat.getColor(wrapper.getContext(), R.color.lightGray));
}
else
{
wrapper.setBackgroundColor(/* appointment background color */);
}
override the RecyclerView.Adapter's getItemViewType to return different view types based on dbNode.isAppointment(), and create different ViewHolder subclasses for displaying each of them
p.s. I don't know what the problem could be regarding the custom views...sorry
I am replacing existing code by databinding. But I face a problem.
I have some layout files shared by more than one activity/fragment. E.g there is a layout file layout_sub used by SubFragmentA and its extending class SubFragmentB. And the data model used in these two fragment are not the same.
The code looks like following.
public class SubFragmentA extends Fragment {
private DataA dataA;
#Override
public View onCreateView(Bundle Bundle) {
View v = LayoutInflator.from(getActivity()).inflate(R.layout.shared_layout);
initView(v, dataA);
return v;
}
private void initView(view v, DataA dataA) {
// use dataA to init v
}
}
public class SubFragmentB extends Fragment {
private DataB dataB;
#Override
public View onCreateView(Bundle Bundle) {
View v = LayoutInflator.from(getActivity()).inflate(R.layout.shared_layout);
initView(v, dataB);
return v;
}
private void initView(view v, DataB dataB) {
// use dataB to init v
}
}
So far, I think using DataA and DataB in layout_sub file at the same time is not a good idea, because it would require a lot of redundant code to decide which object to be used.
Please share your ideas on this problem.
Finally, I got a solution. The databinding is used for MVVM pattern. That means one layout corresponds to one ViewModel. And the ViewModel contains every data for UI layout. So I should prepare one ViewModel for each layout file. And every fragment/activity should just handle the ViewModel.
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.