Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GridView with infinite scroll optimization

I've created a simple app that fetches images from Pixabay and then displays them in a GridView with infinite scroll.

My OnScrollListener:

public class BasicOnScrollListener implements AbsListView.OnScrollListener {

private IOnScroll onScroll;

public BasicOnScrollListener(IOnScroll action) {
    this.onScroll = action;
}

@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    if (firstVisibleItem + visibleItemCount >= totalItemCount - visibleItemCount)
        onScroll.onReachedEnd();
}
}

Code resposible for data handling:

private List<Image> images = new ArrayList<>();

....

private void init() {
    this.imageAdapter = new ImageAdapter(this, images);
    this.gridView.setAdapter(imageAdapter);

    populateGridView();
    this.gridView.setOnScrollListener(new BasicOnScrollListener(() -> {
        populateGridView();
    }));
}

private void  populateGridView() {
    if (!isLoading) {
        isLoading = true;
        this.progressBar.setVisibility(View.VISIBLE);
        imagesRepository.getImages(currentPage, PAGE_SIZE, new IOnRepositoryDataReturn<ImagesList>() {
            @Override
            public void onData(ImagesList data) {
                clearIfNeeded();
                images.addAll(data.images);
                imageAdapter.notifyDataSetChanged();
                onFinish();
            }

            @Override
            public void onError(String error) {
                Toast.makeText(getApplicationContext(), error, Toast.LENGTH_LONG).show();
            }

            private void clearIfNeeded() {
                if (images.size() > 1000) {
                    images.subList(0, 300).clear();
                }
            }

            private void onFinish() {
                progressBar.setVisibility(View.INVISIBLE);
                isLoading = false;
                currentPage = currentPage + 1;
            }
        });
    }
}

Everything works fine but I'd like to optimize that. When there are already more than 1000 items in the GridView I'd like to remove the first 300 items, so I won't run into out of memory problems.

The problem is, when I simply remove the first 300 items from list (as shown in clearIfNeeded() method in IOnRepositoryDataReturn implementation), the screen shifts. I no longer see items that I've seen before removal.

Example image. Situation if first row (items 1-2) from images list is removed.

  • left image - grid before removing
  • center - grid after removing (as it is for now, removing items on top shifts all items up)
  • right - how i'd like it to behave (removing items somewhere up doesnt affect what is seen)

The black square is representing the GridView.

enter image description here

I'd like to somehow adjust the viewing position in GridView, so I'd still see the same images as before removal.

Layout xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ProgressBar
        android:id="@+id/ProgressSpinner"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        app:layout_constraintBottom_toBottomOf="@+id/GridView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/GridView" />

    <GridView
        android:id="@+id/GridView"
        android:layout_width="match_parent"
        android:layout_height="406dp"
        android:layout_marginBottom="8dp"
        android:numColumns="3"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </GridView>

Is it even possible with Grid View or should i look for some other possibility?

like image 267
Pan Wolodyjowsky Avatar asked Nov 12 '18 19:11

Pan Wolodyjowsky


2 Answers

When there are already more than 1000 items in the GridView I'd like to remove the first 300 items, so I won't run into out of memory problems. Is it even possible with Grid View or should I look for some other possibility?

Here is my 2 cents. If I understand correctly, your concern seems to be preventing OutOfMemory exceptions and improving memory usage and performance. If you implement the grid of photos using RecyclerView and GridLayoutManager, it would reduce many of your problems with memory. Your approach of trying to remove 300 items which are off screen, seems pretty cumbersome and error prone. Here is an excerpt from the official doc titled Create a List with RecyclerView:

RecyclerView:

The RecyclerView uses a layout manager to position the individual items on the screen and determine when to reuse item views that are no longer visible to the user. To reuse (or recycle) a view, a layout manager may ask the adapter to replace the contents of the view with a different element from the dataset. Recycling views in this manner improves performance by avoiding the creation of unnecessary views or performing expensive findViewById() lookups.

GridLayoutManager:

GridLayoutManager arranges the items in a two-dimensional grid, like the squares on a checkerboard. Using a RecyclerView with GridLayoutManager provides functionality like the older GridView layout.

You can find many tutorials for this on quick search eg: Android GridLayoutManager with RecyclerView

like image 122
Shobhit Puri Avatar answered Sep 27 '22 19:09

Shobhit Puri


You can optimize this by:

  1. Using RecycleView + Grid Layout instead of GridView. RecycleView will help your view scroll smoothly.
  2. To reach scroll to bottom, I no longer use ScrollListener for better performance. I will provide you another option:

    • Create callback public interface AdapterCallback { void onReachLastItem(); }
    • Call onBindViewHolder: @Override protected void onBindContentViewHolder(RecyclerView.ViewHolder viewHolder, int position) { if (0 != position && position == getItemCount() - 1 && null != mCallback) { mCallback.onReachLastItem(); } }

    • Make your fragment or activity implement AdapterCallback to load more data.

like image 24
Louis Solo Avatar answered Sep 27 '22 19:09

Louis Solo