Let take the following code:
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder viewHolder, int position) {
// get the list item
MyListItemObject myListItemObject = getObject(i);
//set some values
viewHolder.prop1.setText(myListItemObject.prop1);
viewHolder.prop2.setText(myListItemObject.prop2);
//We got some setting from another object.
if(externalObject.showProp2){
viewHolder.prop2.setVisibility(View.VISIBLE);
}else{
viewHolder.prop2.setVisibility(View.GONE);
}
}
By some external settings value we decide to show or hide prop2 Now assume this value changes from true to false, how can I rerender the list. notifyDatasetChanged() will not work because it simply had not changed, only some external settings has.
Have you tried notifyDatasetChanged()? According to the documentation:
This event does not specify what about the data set has changed, forcing any observers to assume that all existing items and structure may no longer be valid. LayoutManagers will be forced to fully rebind and relayout all visible views.
You would make the change to hide/show prop2 in onBindViewHolder(). This should work for simple changes. If the external change cause a more involved layout change such as using a whole new view holder layout then a different approach may be in order.
Related
I'm using 3 different view types in a recyclerview. since one of them contains a list of views, it is kind of heavy and needs more time to be created. so whenever the recyclerview arrives at the beginning of these views and needs to create them (in addition to binding), I experience jank.
in order to solve the issue, I need the recyclerview to create as many views as it needs at the initialization level and then when the user scrolls just binds the data to already-created views. how can I force recyclerview to create all needed views in begining?
I already tried to create a RecycledViewPool which contains the required view holders, but the pool does not accept the added viewHolders for some reason:
RecyclerView.RecycledViewPool recycledViewPool = recyclerView.getRecycledViewPool();
recycledViewPool.putRecycledView(recyclerViewAdapter.onCreateViewHolder(recyclerView, Model.TYPE_1));
collectionsRecyclerView.setRecycledViewPool(recycledViewPool);
You should use recyclerViewAdapter.createViewHolder instead of recyclerViewAdapter.onCreateViewHolder because (documentation):
This method calls onCreateViewHolder(ViewGroup, int) to create a new RecyclerView.ViewHolder and initializes some private fields to be used by RecyclerView.
Also make sure to call recyclerViewAdapter.setMaxRecycledViews(Model.TYPE_1, MAX) with a sufficiently large MAX value.
This works great for me even though this interesting article in general warns against using putRecycledView() manually:
Using putRecycledView() manually, e.g. in order to prepare some ViewHolders beforehand, is a bad idea, though. You should create ViewHolder only in onCreateViewHolder() method of your Adapter, otherwise the ViewHolders can appear in states that RecyclerView doesn’t expect.
E.g. the correct view type is set in createViewHolder() method right after the Adapter call, and the field is package local, so you can’t set it yourself.
However I see no problem since we are calling createViewHolder().
Alternatively you could simply use your own pool:
val pool = LinkedList<H>()
var ini = true
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
if (ini)
for (i in 0 until 10)
pool.push(createYourViewHolder())
ini = false
// if it needs more than expected
if (pool.size == 0) pool.push(createYourViewHolder())
return pool.pop()
}
That way, when the recyclerview is created, becomes visible and requests the first view (and no scrolling should be happening), reserve views are created, and following calls to onCreateViewHolder will take virtually no time, see here.
This is what I used previously and also works.
I want to set my view holder to not recycle, here's the code:
#Override
public void onBindViewHolder(final ViewHolder viewHolder, final int position) {
viewHolder.setIsRecyclable(false);
}
However, when I see the documentation here: https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.ViewHolder#setIsRecyclable(boolean), it is written that the setIsRecycleable() should always be paired and I have no idea about this. Can anybody provide a sample code for this case? When should I call the setIsRecyclable(true) again? Thanks.
Not recycling the ViewHolder just means that specific ViewHolder will be retained and not overwritten when there is new data to bind, the problem with that is the Adapter will then need to supply another ViewHolder to make up for the one it can't reuse.
That is why you need to eventually let it recycle i.e. setIsRecycleable(true) because it kinda defeats the point of the RecyclerView if it ends up having to create new views to represent data.
A reason you might want to turn off the recycle is to avoid interruptions, maybe the ViewHolder is playing an animation, or loading a video. Once its done you could then turn on the recycle, to release the ViewHolder to make sure it can be used again.
It's better to use Listview in this case. The whole meaning of recyclerview is to recycle the view.
Else see this post
https://stackoverflow.com/a/36275862/3094367
Actually the recycle is related to viewType.
You just change every itemType is unique
like:
#Override
public int getItemType(int position){
return position
}
I've created an app that has a list of cards within a RecyclerView that each have functionality of their own. I wanted to have each card choose the next color from an array defined in my colors.xml. In order to accomplish this, within my ViewHolder initialization, I set the background color of the card using cardContainer.setBackgroundColor(colors[this.layoutPosition % colors.size]. This would make it so that the colors would be cycled when more cards are created. However, I seem to be encountering the issue where my layout position is negative despite there being a set number of cards (25) created at the beginning.
While trying to search around and find the cause, I read here that if you call notifyDataSetChanged() the adapterPosition will become -1. While I am not using adapterPosition here, I thought that maybe it would be a similar issue, however, I am not adding any additional data at the time of the creation of the list items.
My ViewHolder code can be seen below. This is where the issue arises, but if any additional code is necessary feel free to ask.
class ViewHolder(itemView : View, private val listener: HabitClickListener) : RecyclerView.ViewHolder(itemView) {
val habitTitle: TextView = itemView.habitTitle
val streak: TextView = itemView.dayCounter
val cardContainer: LinearLayout = itemView.cardContainer
private val decreaseCounterButton : Button = itemView.decreaseCounterButton
private val increaseCounterButton : Button = itemView.increaseCounterButton
init {
chooseCardColor() // Choose the color for each card from the available colors
itemView.setOnClickListener {
listener.onCardClick(this.layoutPosition)
}
decreaseCounterButton.setOnClickListener {
listener.onDecrease(this.layoutPosition)
}
increaseCounterButton.setOnClickListener {
listener.onIncrease(this.layoutPosition)
}
}
private fun chooseCardColor() {
val colors = itemView.resources.getIntArray(R.array.cardColors)
cardContainer.setBackgroundColor(colors[this.layoutPosition % colors.size])
}
}
I will try to simplify this further, you should use the getAdapterPosition of ViewHolder
In recyclerview, storing the data and displaying the data are two separate things(Notice how you can use different managers(LinearLayoutManager, GridLayoutManager) to present the data in a different way.When some data changes in recyclerview, it notifies the ui to change what is shown in the screen. Even though it is really small, there is a delay between the change in the content of recyclerview and change in layout, that's why these two behave differently.
My information in this may be outdated but also don't just use the position variable as it can be inconsistent when another element is added/deleted to recyclerview due to how onBindViewHolder()(existing variables position wasn't updated when a new element is added/deleted) behaves. Instead use getAdapterPosition().
Edit: Quick fix if you don't want to deal with viewHolder gimmicks.
Add a new field to your custom object which decides what color it should be. Then make this calculation in your fragment/activity by looking at the index of your object in the list instead of doing the calculation in the viewHolder. Now you can set the color you want inside the viewHolderby looking at your object's new field. Of course you should be careful when adding/deleting a new object when you do this, but same holds true when you do it via viewHolder
When an item is added or removed using DiffUtil, inside android.support.v7.util.AdapterListUpdateCallback only the notifyItemRangeInserted(1, 1) or notifyItemRangeRemoved(1,1) respectively is invoked, notifyItemRangeChanged() is not invoked. I set the positions on each of the views using setTag(position) but are not getting updated for the existing items. Wouldn't prefer to update items manually.
list update:
private void updateItems(final List<Feed> newPosts) {
List<Feed> olderPosts = new ArrayList<>(currentPosts);
final CustomDiffCallback DIFF_CALLBACK = new CustomDiffCallback(olderPosts, newPosts);
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(DIFF_CALLBACK, true);
}
Turns out that we can't use setTag() and getTag() for passing in position to the views. Since the main purpose of DiffUtil is to minimize the updations for views that haven't changed, the views that already have positions set on them cannot get through an updation pass hence would return stale positions. Though In my case, I had the custom listener in a separate class, the update part was a bit unusual. I had to get getLayoutPosition() of ViewHolder. Also can use getAdapterPosition() for the same.
private fun turnOnAllItems() {
items.forEachIndexed { index, item ->
val viewHolder = recyclerView.findViewHolderForAdapterPosition(index)
as SwitchableItemViewHolder
viewHolder.switchButton.isChecked = false
}
}
What this does, is it also changes list items object values isEnabled to false. Looks weird to me, as I actually change viewHolder attribute. Why is this happening? How to avoid this?
I strongly believe that you are doing it the wrong way. RecyclerView is meant to display already modified data, meaning that you have a set of it.
Let's say, 10 tables in restaurant, and at some point table #4 becomes available for new customer and you want to indicate that.
A good approach would be to modify your list of tables somewhere outside RCV, even fragment or activity will do, and then just graphically update (all or just one) item by means of RCV.
Here's a little article I made to illustrate how to properly use RecyclerView, hope it will help you