Reputation: 9373
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.
Upvotes: 5
Views: 5498
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
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
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
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