I have a ListView with around 5-6 rows, every row is a RecycleView displaying horizontal Fragments. Cards (like Netflix, HBO apps for instance)
Considerations:
- Every RecycleView has around 30-50 items.
- Every ViewHolder has a "big" picture as background (around 300x260dp depending on the device screen size, the width is around 90% of the device screen)
- The images are loaded by using Glide (code below)
I created a cache (SparseArray) for the RecycleViews in the ListView Adapter to keep the state of every RecycleView, otherwise every time it gets out of the screen it restarted to position 0.
By doing this, just scrolling every RecycleView to the end, makes some of them get a freeze.
Tried several workarounds:
- Reviewed MAT and have refactored some code for avoiding leak because of the static context
- MAT obviously display a massive heap of memory because of the images
- Reduced the size of the images
- Hide the images
- Create a cache of ViewHolders (didn't work)
- Create a memory cache for the Images once Glide download it
- Making the ViewHolders setIsRecyclable to false
But, none of this options has worked, the only way to guarantee the app is not getting a freeze, is avoiding the ListView cache (And losing the last position you visited, if you moves the View out of the screen)
Glide Code (I tried several options here: avoiding Bitmap, avoiding memory cache, updating the disk Strategy to ALL)
Glide.with(context).load(url)
.asBitmap()
.skipMemoryCache(true)
.thumbnail(0.5f)
.centerCrop()
.error(placeHolderId)
.diskCacheStrategy(DiskCacheStrategy.RESULT)
//.priority(Priority.NORMAL)
.dontAnimate()
.into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
if (bitmap != null) {
imgView.setImageBitmap(bitmap);
//cache.put(url, bitmap);
} else {
imgView.setBackground(null);
}
}
});
Any advice/suggestions? Maybe there is another option to keep the state of the View without the cache?
ListView Adapter Code:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
case TYPE_VENUE_FILTER: {
View v;
// Create a new view into the list.
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (inflater != null) {
v = inflater.inflate(R.layout.fragment_venue_filter, parent, false);
}
// We need to get the exact filter to be displayed
int index = position - FIRST_VENUE_FILTER_ROW;
if (index < this.filters.size()) {
VenueFilter filter = this.filters.get(index);
// Recalculate the distance order
filter.sortVenuesForLocation(this.lastLocation);
// Set data into the view.
VenueFilterFragment.loadFragment(this.context, v, filter, this.fm);
}
return v;
}
}
UPDATE 11/04
I finally figure out.
- Reused the converView on the Adapter as suggested
- Included a ScrollListener in the RecycleView and storing in the bean the last scroll position, for recover it when is revisited.
This code is in the Adapter, for inflating the specific row (getView)
// We should reset or recover the scroll state
LinearLayoutManager llm = (LinearLayoutManager) filterContent.getLayoutManager();
if (filter.offset > 0) {
if (llm != null) llm.scrollToPosition(filter.offset + 1);
} else {
if (llm != null) llm.scrollToPosition(0);
}
// We need to clear every time. Because we are reusing the view
recycleView.clearOnScrollListeners();
// We need to create a new specific scroll listener
recycleView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// Keeping the offset for a future load
LinearLayoutManager llm = (LinearLayoutManager) recyclerView.getLayoutManager();
if (llm != null) filter.offset = llm.findFirstVisibleItemPosition();
}
});