Nick
Nick

Reputation: 9373

LinearLayoutManager smooth scroll jumps to wrong position

I'm am trying to make a layout similar to how Instagram's filter layout works. Basically when you select a filter it will scroll to the item you selected + 1 showing you that there are more filters.

I currently am trying to build a custom LinearLayoutManager for my horizontal RecyclerView here:

public class LinearLayoutSnapManager extends LinearLayoutManager {
    private int mCurrentPos = 0;

    public LinearLayoutSnapManager(Context context) {
        super(context);
    }

    public LinearLayoutSnapManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    public LinearLayoutSnapManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

    }


    public void snap(RecyclerView rv, int position) {
        if (mCurrentPos == position) {
            // No move
            return;
        }

        boolean goingRight = true;
        if (position < mCurrentPos) {
            goingRight = false;
        }
        mCurrentPos = position;
        smoothScrollToPosition(rv, new RecyclerView.State(), goingRight ? getScrollRightPos(): getScrollLeftPos());
    }

    private int getScrollLeftPos() {
        int newPos = mCurrentPos - 1;
        return (newPos > 0) ? newPos : 0;
    }

    private int getScrollRightPos() {
        return mCurrentPos + 1;
    }
}

Scrolling left works just as intended but when I'm scrolling right it seems to just jump to the end of the list versus the newItem + 1 and I can't figure out why it happens.

enter image description here

Upvotes: 5

Views: 5498

Answers (4)

programming me fun
programming me fun

Reputation: 1

this is because the 'position' which is being entered is not layout position , first find layout position like below and than apply smoothscrolltoposition.

  Common.foodlistnuber = viewHolder.getLayoutPosition(); //position
  new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
  recyclerView.smoothScrollToPosition(Common.foodlistnuber);
        }
    }, 200);

Upvotes: 0

Nick
Nick

Reputation: 9373

I was calling my snap method in the interface callback from the onclicklistener in my horizontal list. Before the interface was getting called I was setting the selected item and then notifying the view had changed in order update the current state. This was causing all the issues with the layoutmanager returning the bad indexes.

I discovered this once I made an empty project and tested my logic. To my surprise with worked fine which allowed me to track down the real issue.

Once I moved the snaplogic before all my view logic everything worked as intended with no change to the code I posted.

Hope this can help someone in the future.

Upvotes: 2

R. Zag&#243;rski
R. Zag&#243;rski

Reputation: 20258

I would suggest a different solution. LinearLayoutManager comes with handy functions:

int findFirstCompletelyVisibleItemPosition()

Returns the adapter position of the first fully visible view.


int findLastCompletelyVisibleItemPosition()

Returns the adapter position of the last fully visible view.


int findFirstVisibleItemPosition()

Returns the adapter position of the first visible view.


int findFirstVisibleItemPosition()

Returns the adapter position of the last visible view.

The first two function return the positions of fully visible views, whereas the last two return even partially visible views. The choice is up to you what behaviour you would like to achieve.

When using them, the functions getScrollLeftPos and getScrollRightPos should look like:

private int getScrollLeftPos() {
    int newPos = findFirstCompletelyVisibleItemPosition() - 1;
    return (newPos > 0) ? newPos : 0;
}

private int getScrollRightPos() {
    return findLastCompletelyVisibleItemPosition()+ 1;
}

And don't invoke method:

smoothScrollToPosition(rv, new RecyclerView.State(), goingRight ? getScrollRightPos(): getScrollLeftPos());

from LayoutManager. If you have the reference to RecyclerView, invoke:

rv.smoothScrollToPosition(newPosition);

If you look into the source code of RecyclerView you will find such implementation:

public void smoothScrollToPosition(int position) {
    if (mLayoutFrozen) {
        return;
    }
    if (mLayout == null) {
        Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " +
                "Call setLayoutManager with a non-null argument.");
        return;
    }
    mLayout.smoothScrollToPosition(this, mState, position);
}

This is basically the same as you do, but be aware that the function of LayoutManager is invoked with appropriate RecyclerView.State. It is just consistent with RecyclerView

Upvotes: 1

compte14031879
compte14031879

Reputation: 1591

If you want the last selected filter is displayed secondly (one filter before, several filters after), you don't have the right approach.

In this case, regardless of direction Right or Left, the process is the same. The position of RecyclerView is always 1 less than the position of the selected filter, except for the first element.

(This example is not optimized for the latest position)

public class LinearLayoutSnapManager extends LinearLayoutManager {
    private int mCurrentSelectedPos = 0;

    public LinearLayoutSnapManager(Context context) {
        super(context);
    }

    public LinearLayoutSnapManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    public LinearLayoutSnapManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

    }


    public void snap(RecyclerView rv, int selectedPosition) {
        if (mCurrentSelectedPos == selectedPosition) {
            // No move
            return;
        }

        mCurrentSelectedPos = selectedPosition;

        int newDisplayPos;
        if (mCurrentSelectedPos > 0) {
            newDisplayPos = mCurrentSelectedPos - 1;
        } else {
            newDisplayPos = 0;
        }

        smoothScrollToPosition(rv, new RecyclerView.State(), newDisplayPos);
    }

}

Upvotes: 0

Related Questions