Finding fully visible items in a RecyclerView (Like TikTok and Instagram Story)

Viewed 284

So what I have here is a custom recyclerview created to achieve a fullscreen portrait video playing functionality like TikTok and Instagram story. Everything works very well except that the RecyclerView cannot find which item is currently visible everytime. When the recyclerview is scrolled too fast, or the if data is refreshed multiple times without pause using a swiperefreshlayout, the viewholder tag's position becomes incorrect in the former case and -1 in the latter case even though the first item is fully visible. This is the most integral part as this is where the recyclerview decides which video is to be played, as in TikTok or in Instagram Story. The playVideo method is where all the visible-item-calculating takes place. What went wrong? Is there a better solution? Hope you will answer. Regards.

public class TiktokRecyclerview extends RecyclerView {

    int playPosition = - 1;
    boolean scrollingUp = false;
    int targetPosition;
    int currentPosition;
    Context context;
    TiktokViewHolder holder;

    public TiktokRecyclerview (@NonNull Context context) {
        super(context);
        init(context);
    }
    public TiktokRecyclerview (@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(final Context context) {
        this.context = context;
        addOnScrollListener(new OnScrollListener () {
            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                scrollingUp = dy > 0;
            }
            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                    playVideo(context);
                }
            }
        });
    }

    private void playVideo(Context context) {
        if (!scrollingUp) {
            targetPosition =
                ((androidx.recyclerview.widget.LinearLayoutManager) Objects . requireNonNull (getLayoutManager())).findLastVisibleItemPosition();
        } else {
            targetPosition =
                ((androidx.recyclerview.widget.LinearLayoutManager) Objects . requireNonNull (getLayoutManager())).findFirstVisibleItemPosition();
        }


        if (targetPosition != playPosition) { // Checks whether source and destination scroll item are same

            playPosition = targetPosition;

            if (!scrollingUp) {
                currentPosition =
                    ((androidx.recyclerview.widget.LinearLayoutManager) Objects . requireNonNull (getLayoutManager())).findLastCompletelyVisibleItemPosition();
            } else {
                currentPosition =
                    ((androidx.recyclerview.widget.LinearLayoutManager) Objects . requireNonNull (getLayoutManager())).findFirstCompletelyVisibleItemPosition();
            }

            View child = getChildAt (currentPosition);
            if (child == null) {
                return;
            }
            if (child.getTag() == null) {
                return;
            }
            holder = (TiktokViewHolder) child . getTag ();
            Log.d(TAG, "" + holder);
        }
    }
}
1 Answers

Not sure why you used tags for vh in RecyclerView since now they have ViewHolders

But, you can remove all code starting View child = getChildAt(currentPosition); and replace with just: holder = (RVAdapter.TiktokViewHolder) findViewHolderForLayoutPosition(currentPosition);

So your code would be:

  ...
  ...
  if (targetPosition != playPosition) { // Checks whether source and destination scroll item are same

        playPosition = targetPosition;

        if (!scrollingUp) {
            currentPosition =
                ((androidx.recyclerview.widget.LinearLayoutManager) Objects . requireNonNull (getLayoutManager())).findLastCompletelyVisibleItemPosition();
        } else {
            currentPosition =
                ((androidx.recyclerview.widget.LinearLayoutManager) Objects . requireNonNull (getLayoutManager())).findFirstCompletelyVisibleItemPosition();
        }

       //You may need to check if currentPosition is valid
        if (currentPosition < 0 || currentPosition >= getChildCount()) return;

        holder = (RVAdapter.TiktokViewHolder) findViewHolderForLayoutPosition(currentPosition);
        Log.d(TAG, "" + holder);
    }
}

This should get you the exact fully visible positition

Related