I see a few issues and I'll point them out in no particular order; although I haven't seen anything specific yet that may be causing your issue, as I write this answer, I may discover a few things, so let's analyze your code.
What's missing?
You didn't include more code, so I have no clue how you use this adapter, or how does your recyclerview layout is (is it contained inside another scroll view or nested scroll view?)
These are important questions because it may affect how the Android rendering/layout engine is working.
The Layout
You're using a LinearLayout (kinda not needed here) with weights. These are not recommended unless you have a very valid reason; calculating the widget's sizes and what not, is expensive, and more so when there are images and bitmaps involved.
I recommend you switch to a ConstraintLayout and do the right thing. You will thank yourself and your layout will likely perform better (nothing in your current layout is impossible for ConstraintLayout, from what I see).
The Adapter
@Override
public void onClick(View v) {
Listener l;
if ((l = listener) != null) {
mainHandler.post(() -> l.onItemClick(devices.get(getAdapterPosition())));
}
}
A click should always happen on the Main Thread, you don't need to post anything.
I'd write that as:
if (listener instanceof Listener) { //already fails if L is null anyway)
listener.onItemClick(...)
}
- In your
onBind you perform a lot of lookups, you can save cycles if you cache these. E.g.:
Picasso.with(context).load(SetupAppUtils.getDeviceImageUrl(context, device)).into(image);
I have no clue what your SsetupAppUtils.getDeviceImageUrl method does, but it could be expensive, and you could save that locally in a Map if needed (lookup the URL once, reuse) (just an idea, impossible to tell without looking at the code)
You seem to have a lot of business logic embedded in your onBind method (lots of if statements, is usually a red-flag. Your adapter couldn't care less about this, it's just "adapting" the data from model -> view and should not make decisions and perform transformations if it can be avoided. In this case, I see you pass a Device... seems ok but you appear to be doing a lot of checks and transformations, remember this is going to happen for all your items once they are being bound..., every time the user scrolls and a new item needs to be bound, you perform all this work again, when all you want is to "rebind" the viewholder.
You're notifying a full change, which means a LOT for a recyclerview (all has to be re-measured and laid out again) because you're essentially telling the adapter: all the data has changed.
void setDevices(List<Device> devices) {
this.devices.clear();
this.devices.addAll(devices);
notifyDataSetChanged();
}
It's very straightforwards to use a DiffUtil (included with the framework!) and totally recommended.
- You override a method, but all the items have the same id:
@Override
public long getItemId(int i) {
return 0;
}
If you cannot provide a good Id, then don't override this. I'd return item[i].something() (check for null/empty lists before!). Also rename i to position because that's what it is.
Anything Else?
Ouside of these, I don't see anything super strange. I'd make a simple test: remove all the binding code you have there (all the code in onBind) and just put name.SetText(...) and see if you still see the "lag".
If you don't, then you know some of these operations are taking longer than others.
Try with less items, see what happens; a recyclerview should be binding only the views that are visible +/- a few up/down, not 400 items at once.
The only reason I can think of, is if your RecyclerView is inside a NestedScrollView or similar.