A Comprehensive Guide to Getting Visible Items in RecyclerView

Nov 22, 2025 · Programming · 11 views · 7.8

Keywords: RecyclerView | Visible Items Detection | LayoutManager | Android Development | Scroll Listeners

Abstract: This article provides an in-depth exploration of methods for accurately determining visible item positions in Android RecyclerView. By analyzing the core APIs of LayoutManager, it details the usage scenarios and implementation principles of key methods such as findFirstVisibleItemPosition and findLastVisibleItemPosition. The article also presents a complete implementation of a custom RecyclerViewPositionHelper class that addresses cross-LayoutManager compatibility issues, and demonstrates through practical code examples how to apply these techniques in scroll listeners to implement advanced features like infinite scrolling.

Core Mechanisms for Visible Item Detection in RecyclerView

In Android development, RecyclerView serves as a modern replacement for ListView, offering more flexible layout management and performance optimizations. However, unlike ListView, RecyclerView does not provide a direct onScroll method for obtaining information about currently visible items. This article systematically introduces how to accurately determine visible item positions in RecyclerView through LayoutManager APIs and custom helper classes.

Using LayoutManager's Basic APIs

Visible item detection in RecyclerView heavily relies on its LayoutManager. For commonly used LinearLayoutManager and GridLayoutManager, the system provides the following core methods:

int findFirstVisibleItemPosition();
int findFirstCompletelyVisibleItemPosition();
int findLastVisibleItemPosition();
int findLastCompletelyVisibleItemPosition();

These methods return the positions in the adapter of the first visible item, first completely visible item, last visible item, and last completely visible item, respectively. It's important to note that the distinction between "visible" and "completely visible" lies in whether the item is partially obscured.

Practical Implementation Examples

The following code demonstrates how to obtain the position of the first visible item in a GridLayoutManager:

GridLayoutManager layoutManager = ((GridLayoutManager) mRecyclerView.getLayoutManager());
int firstVisiblePosition = layoutManager.findFirstVisibleItemPosition();

For LinearLayoutManager, the usage is similar, but it's important to note that the adapter's sorting direction affects the definition of "first" and "last." Crucially, you should not query child views directly from RecyclerView, as LayoutManager may layout more items than actually visible for caching purposes.

Custom RecyclerViewPositionHelper Implementation

To provide a universal solution across different LayoutManagers, we can implement a RecyclerViewPositionHelper class. This class encapsulates the complex logic of visible item detection and supports both horizontal and vertical layouts.

public class RecyclerViewPositionHelper {
    final RecyclerView recyclerView;
    final RecyclerView.LayoutManager layoutManager;
    
    RecyclerViewPositionHelper(RecyclerView recyclerView) {
        this.recyclerView = recyclerView;
        this.layoutManager = recyclerView.getLayoutManager();
    }
    
    public static RecyclerViewPositionHelper createHelper(RecyclerView recyclerView) {
        if (recyclerView == null) {
            throw new NullPointerException("Recycler View is null");
        }
        return new RecyclerViewPositionHelper(recyclerView);
    }
    
    public int getItemCount() {
        return layoutManager == null ? 0 : layoutManager.getItemCount();
    }
    
    public int findFirstVisibleItemPosition() {
        final View child = findOneVisibleChild(0, layoutManager.getChildCount(), false, true);
        return child == null ? RecyclerView.NO_POSITION : recyclerView.getChildAdapterPosition(child);
    }
    
    public int findFirstCompletelyVisibleItemPosition() {
        final View child = findOneVisibleChild(0, layoutManager.getChildCount(), true, false);
        return child == null ? RecyclerView.NO_POSITION : recyclerView.getChildAdapterPosition(child);
    }
    
    public int findLastVisibleItemPosition() {
        final View child = findOneVisibleChild(layoutManager.getChildCount() - 1, -1, false, true);
        return child == null ? RecyclerView.NO_POSITION : recyclerView.getChildAdapterPosition(child);
    }
    
    public int findLastCompletelyVisibleItemPosition() {
        final View child = findOneVisibleChild(layoutManager.getChildCount() - 1, -1, true, false);
        return child == null ? RecyclerView.NO_POSITION : recyclerView.getChildAdapterPosition(child);
    }
    
    View findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible, boolean acceptPartiallyVisible) {
        OrientationHelper helper;
        if (layoutManager.canScrollVertically()) {
            helper = OrientationHelper.createVerticalHelper(layoutManager);
        } else {
            helper = OrientationHelper.createHorizontalHelper(layoutManager);
        }
        
        final int start = helper.getStartAfterPadding();
        final int end = helper.getEndAfterPadding();
        final int next = toIndex > fromIndex ? 1 : -1;
        View partiallyVisible = null;
        
        for (int i = fromIndex; i != toIndex; i += next) {
            final View child = layoutManager.getChildAt(i);
            final int childStart = helper.getDecoratedStart(child);
            final int childEnd = helper.getDecoratedEnd(child);
            
            if (childStart < end && childEnd > start) {
                if (completelyVisible) {
                    if (childStart >= start && childEnd <= end) {
                        return child;
                    } else if (acceptPartiallyVisible && partiallyVisible == null) {
                        partiallyVisible = child;
                    }
                } else {
                    return child;
                }
            }
        }
        return partiallyVisible;
    }
}

Application in Scroll Listeners

Combined with custom scroll listeners, we can implement advanced features such as infinite scrolling. Here's an example implementation of an EndlessRecyclerOnScrollListener:

public abstract class EndlessRecyclerOnScrollListener extends RecyclerView.OnScrollListener {
    private int previousTotal = 0;
    private boolean loading = true;
    private int visibleThreshold = 5;
    int firstVisibleItem, visibleItemCount, totalItemCount;
    private int currentPage = 1;
    RecyclerViewPositionHelper mRecyclerViewHelper;
    
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        mRecyclerViewHelper = RecyclerViewPositionHelper.createHelper(recyclerView);
        visibleItemCount = recyclerView.getChildCount();
        totalItemCount = mRecyclerViewHelper.getItemCount();
        firstVisibleItem = mRecyclerViewHelper.findFirstVisibleItemPosition();
        
        if (loading) {
            if (totalItemCount > previousTotal) {
                loading = false;
                previousTotal = totalItemCount;
            }
        }
        
        if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
            currentPage++;
            onLoadMore(currentPage);
            loading = true;
        }
    }
    
    public abstract void onLoadMore(int currentPage);
}

Technical Considerations and Best Practices

When using these methods, several key points should be considered: First, proper usage of OrientationHelper is crucial for accurately calculating visible areas. Second, for different LayoutManager implementations, the logic for visibility detection may need adjustment. Finally, for performance optimization, complex visibility calculations should not be performed on every scroll event; instead, set appropriate detection frequencies based on actual requirements.

By properly utilizing LayoutManager's APIs and custom helper classes, developers can accurately obtain visible item information in RecyclerView, providing a solid foundation for implementing various advanced UI interaction features.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.