How to Accurately Track Card Exposure in Android RecyclerView

This article explains the shortcomings of hard‑coded exposure tracking in the Beike Zhaofang Android app, benchmarks industry practices, proposes a flexible API‑driven strategy, and provides a complete RecyclerView scroll‑listener implementation with a double‑ended queue to record precise card visibility durations.

Beike Product & Technology
Beike Product & Technology
Beike Product & Technology
How to Accurately Track Card Exposure in Android RecyclerView

Background

The Beike Zhaofang Android app currently uses a hard‑coded exposure timing mechanism. An exposure is only recorded when a card is fully displayed on the screen, and the same card may be counted multiple times during list scrolling.

Problems with the Existing Approach

The exposure threshold is fixed in the client; it should be configurable via an API.

The duration a card stays visible on the screen is not recorded.

Edge cases illustrate the flaws: a list with 1,000 items scrolled quickly would generate exposure events for unseen items, and a card that is only 80 % visible may still be counted as fully exposed.

Industry Benchmark

Products such as Toutiao and Baidu Mobile implement fine‑grained exposure tracking, including entry/exit timestamps and configurable visibility‑percentage thresholds.

Proposed Solution

Define a distinct exposure strategy for each card type.

Trigger exposure events based on threshold values delivered by the backend API.

Record the exact time each card remains on screen to calculate true exposure duration.

Handle page destruction, show/hide events (e.g., pressing the Home button) through API‑controlled feature switches.

Two diagrams illustrate the overall flow and the double‑ended queue used to cache visible ViewHolders.

Implementation Details

A double‑ended queue caches all currently visible ViewHolder objects. During the RecyclerView scroll callback, the first and last visible positions are obtained, the scroll direction is detected, and the visible proportion of each card is calculated using the top and bottom values of the item view. When the proportion exceeds the API‑provided threshold, a start‑time is recorded; when the card falls below the threshold, an exposure event is fired, yielding the total display period.

public class CardExposureHelper extends RecyclerView.OnScrollListener {
    // Cache of visible cards
    private Deque<BaseHomeCard> deque;
    // Position of the top card in the queue
    private int preFirstExposure;
    // Position of the bottom card in the queue
    private int preLastExposure;

    /**
     * Handle vertical exposure
     * @param manager LinearLayoutManager
     * @param isUp    true if scrolling up
     */
    private void onVerticalExposure(LinearLayoutManager manager, boolean isUp) {
        int firstVisiblePosition = manager.findFirstVisibleItemPosition();
        int lastVisiblePosition = manager.findLastVisibleItemPosition();
        // Adjust positions based on exposure ratio
        firstVisiblePosition = isVerticalExposure(firstVisiblePosition) ? firstVisiblePosition : firstVisiblePosition + 1;
        lastVisiblePosition = isVerticalExposure(lastVisiblePosition) ? lastVisiblePosition : lastVisiblePosition - 1;
        if (preFirstExposure == 0 && preLastExposure == 0) {
            offerVerticalVisibleQueue(firstVisiblePosition, lastVisiblePosition, true);
        } else if (isUp) {
            // Scrolling up: dequeue cards that moved out at the top, enqueue new cards at the bottom
            popVerticalVisibleQueue(preFirstExposure, firstVisiblePosition - 1, true);
            offerVerticalVisibleQueue(preLastExposure + 1, lastVisiblePosition, false);
        } else {
            // Scrolling down: opposite strategy
            popVerticalVisibleQueue(lastVisiblePosition + 1, preLastExposure, false);
            offerVerticalVisibleQueue(firstVisiblePosition, preFirstExposure - 1, true);
        }
        preFirstExposure = firstVisiblePosition;
        preLastExposure = lastVisiblePosition;
    }

    private void offerVerticalVisibleQueue(int start, int end, boolean isFirst) {
        if (start >= 0 && end < recyclerView.getAdapter().getItemCount() && start <= end) {
            if (isFirst) {
                for (int i = end; i >= start; i--) {
                    onVerticalItemSlideInto(i, true);
                }
            } else {
                for (int i = start; i <= end; i++) {
                    onVerticalItemSlideInto(i, false);
                }
            }
        }
    }

    private void popVerticalVisibleQueue(int start, int end, boolean isFirst) {
        if (start >= 0 && end < recyclerView.getAdapter().getItemCount() && start <= end) {
            if (isFirst) {
                for (int i = start; i <= end; i++) {
                    onVerticalItemSlideOut(i, isFirst);
                }
            } else {
                for (int i = end; i >= start; i--) {
                    onVerticalItemSlideOut(i, isFirst);
                }
            }
        }
    }

    private void onVerticalItemSlideInto(int position, boolean isFirst) {
        BaseHomeCard card = getBaseHomeCard(position);
        if (isFirst) {
            deque.offerFirst(card);
        } else {
            deque.offerLast(card);
        }
        // Callback for exposure start
        callItemExposure(card, position);
    }

    private void onVerticalItemSlideOut(int position, boolean isFirst) {
        BaseHomeCard card;
        if (isFirst) {
            card = deque.removeFirst();
        } else {
            card = deque.removeLast();
        }
        // Callback for exposure end
        callItemEndExposure(card, position, isFirst);
    }
}

Outlook

The implementation is currently a technical reserve for the Android client. Before production deployment, product managers need to define detailed product requirements and configuration options.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Mobile DevelopmentperformanceAndroidAPIRecyclerViewQueueExposure Tracking
Beike Product & Technology
Written by

Beike Product & Technology

As Beike's official product and technology account, we are committed to building a platform for sharing Beike's product and technology insights, targeting internet/O2O developers and product professionals. We share high-quality original articles, tech salon events, and recruitment information weekly. Welcome to follow us.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.