RecyclerView scrolls to end after smoothScrollToPosition(0) - android

I have RecyclerView with LinearLayoutManager (Vertical, not inverted). When I insert or move item to 0 position, such method is invoked:
private void scrollToStart() {
int firstVisiblePosition = ((LinearLayoutManager) recentList.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
boolean notScrolling = recentList.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
if (firstVisiblePosition < 2 && notScrolling) {
recentList.scrollToPosition(0);
}
}
But sometimes it smooth scrolls to the end of list.
(I've seen such behaviour in some apps like Instagram. It looks like list scrolls to top and then starts to move in the opposite direction.)
If I replace smoothScrollToPosition with simple scrollToPosition, it behaves as it should.
How to prevent this scroll?

In my case the fact items are recyclable was the cause to it. Try to place this.setIsRecyclable(false); inside of your ViewHolder

Related

Why RecyclerView smoothScrollToPosition scrolled to 0 happens slowly?

I have RecyclerView and use it instead ViewPager with BottomNavigationView from support library. RecyclerView for nicely and comfort scroll have PagerSnapHelper.
And i faced with terrible and strange problem:
when my listener for BottomNavigationView catches in method onNavigationItemSelected new position i make this:
override fun onNavigationItemSelected(item: MenuItem): Boolean {
var newPos = -1
//little code for checked only a NEW position, the same values ignored
recycler.smoothScrollToPosition(currentPos) //values 0, 1 and 2
return true
}
when i smoothScrollToPosition with currentPos = 0 recycler scroll to First position (0) very slow (compared to other
). Time between call method smoothScrollToPosition and onScrollStateChanged (for recycler view scroll listener with parameter newState = RecyclerView.SCROLL_STATE_IDLE) with values 1 and 2 very small, and with value = 0 about a second (!)
Why is this happening and how can I fix it?
Use recyclerView.scrollToPosition(position); instead of recycler.smoothScrollToPosition(currentPos)
recyclerView.scrollToPosition(position); will not show the animation of scrolling down, the effect will be immediate.
You can also try this if you set linearLayoutManager for your recyclerView
linearLayoutManager.scrollToPositionWithOffset(position, 0);

scrollBy doesn't work properly in nested recyclerview

I have a vertically scrolling RecyclerView with horizontally scrolling inner RecyclerViews just like this.
With this implementation, users can scroll each horizontal recyclerview synchronously. However, when a user scroll vertically to the parent recyclerView, a new horizontal recyclerview which has just attached on window doesn't display on same scroll x position. This is normal. Because it has just created.
So, I had tried to scroll to the scrolled position before it was displayed. Just like this:
Note: this is in adapter of the parent recyclerview whose orientation is vertical.
#Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
super.onViewAttachedToWindow(holder);
CellColumnViewHolder viewHolder = (CellColumnViewHolder) holder;
if (m_nXPosition != 0) {
// this doesn't work properly
viewHolder.m_jRecyclerView.scrollBy(m_nXPosition, 0);
}
}
As you can see, scrollBy doesn't effect for row 10, row 11, row 12 and row 13 After that, I debugged the code to be able find out find out what's happening. When I set scroll position using scrollBy, childCount() return zero for row 10, row 11, row 12 and row 13 So they don't scroll. But why ? and Why others work ?
How can I fix this ?
Is onViewAttachedToWindow right place to scroll new attached recyclervViews ?
Note: I have also test scrollToPosition(), it doesn't get any problem like this. But I can't use it at my case. Because users can scroll to the any x position which may not the exact position. So I need to set scroll position using x value instead of the position.
Edit: You can check The source code
I found a solution that is use scrollToPositionWithOffset method instead using scrollBy. Even if both of two scroll another position, they have really different work process in back side.
For example: if you try to use scrollBy to scroll any pixel position and your recyclerView had not been set any adapter which means there is no any data to display and so it has no any items yet, then scrollBy doesn't work. RecyclerView uses its layoutManager's scrollBy method. So in my case, I am using LinearLayoutManager to the horizontal recyclerViews.
Lets see what it's doing :
int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (getChildCount() == 0 || dy == 0) {
return 0;
}
mLayoutState.mRecycle = true;
ensureLayoutState();
final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
final int absDy = Math.abs(dy);
updateLayoutState(layoutDirection, absDy, true, state);
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);
if (consumed < 0) {
if (DEBUG) {
Log.d(TAG, "Don't have any more elements to scroll");
}
return 0;
}
final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
mOrientationHelper.offsetChildren(-scrolled);
if (DEBUG) {
Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);
}
mLayoutState.mLastScrollDelta = scrolled;
return scrolled;
}
As you can see scrollBy ignores the scroll intentions if there is no any child at that time.
if (getChildCount() == 0 || dy == 0) {
return 0;
}
On the other hand scrollToPosition can work perfectly even if there is no any set data yet.
According to the Pro RecyclerView slide, the below sample works perfectly. However you can not do that with scrollBy.
void onCreate(SavedInstanceState state) {
....
mRecyclerView.scrollToPosition(selectedPosition);
mRecyclerView.setAdapter(myAdapter);
}
As a result, I have changed little thing to use scrollToPositionWithOffset().
Before this implementation I was calculating the exact scroll x position as a pixel.
After that, when the scroll came idle state, calculating the first complete visible position to the first parameter of the scrollToPositionWithOffset().
For second parameter which is the offset, I am getting the value using view.getLeft() function which helps to get left position of this view relative to its parent.
And it works perfectly!!

Check whether a RecyclerView needs to scroll

I have a callback that is fired after a programmatic scroll to a certain item of a RecyclerView via LinearLayoutManager.smoothScrollToPosition(). The user taps on an item and the right item is scrolled to the top of the RecyclerView. I subclassed LinearLayoutManager to have it always snap to the top of the item.
This works in case the scroll event is fired, but when the RecyclerView is already in the right position, I don't get the onScrollStateChanged callback, as no scrolling occurs. Is there a way to get that event anyway? Like decide beforehand whether or not the RecyclerView needs to scroll or not?
Hope the following code would help
if(LinearLayoutManager.findFirstCompletelyVisibleItem() == yourDesiredPosition) {
//do your stuff
} else {
LinearLayoutManager.scrollToPositionWithOffset(yourDesiredPosition, offset);
//onScrollStateChanged would be trigger then.
}
I found the following solution myself:
// get the view the user selected
View view = mLayoutManager.findViewByPosition(index);
// get top offset
int offset = view.getTop();
if (offset == 0) { // the view is at the top of the scrollview
showDetailViewInternal(event);
} else {
// scrolling
}

Recyclerview with header-image stackfrombottom conditionally

I have a recyclerview with a custom first row (header) containing an image and a chatview below.
If I use stackfrombottom and there are many chat messages it works as expected, the last chat item is visible at bottom and scrolling up it scrolls to start of image, like in this image:
The problem: If there are not many chat messages the image is in the center of the recyclerview (because stacked from bottom) like here:
Can this problem be fixed without rearranging the header outside of listview? Like in this third image:
Answering own question:
Here I found a tip for item decoration that just takes the header and alters the first row:
Is it possible to have the last item in a RecyclerView to be docked to the bottom if there is no need to scroll?
The code I used with this inspiration:
public class StickySummaryDecoration extends RecyclerView.ItemDecoration {
#Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
int childCount = parent.getAdapter().getItemCount();
int lastVisibleItemPosition =
((LinearLayoutManager) parent.getLayoutManager()).findLastVisibleItemPosition();
int firstVisiblePosition =
((LinearLayoutManager) parent.getLayoutManager())
.findFirstCompletelyVisibleItemPosition();
if ((firstVisiblePosition == 0) && (lastVisibleItemPosition == (childCount - 1))) {
View summaryView = parent.getChildAt(0);
summaryView.setY(0);
} else {
parent.getChildAt(0).setVisibility(View.VISIBLE);
}
}
}
And on the recyclerview:
recyclerView.addItemDecoration(new StickySummaryDecoration(), 0);

Android: ListView.getScrollY() - does it work?

I am using it, but it always returns 0, even though I have scrolled till the end of the list.
getScrollY() is actually a method on View, not ListView. It is referring to the scroll amount of the entire view, so it will almost always be 0.
If you want to know how far the ListView's contents are scrolled, you can use listView.getFirstVisiblePosition();
It does work, it returns the top part of the scrolled portion of the view in pixels from the top of the visible view. See the getScrollY() documentation. Basically if your list is taking up the full view then you will always get 0, because the top of the scrolled portion of the list is always at the top of the screen.
What you want to do to see if you are at the end of a list is something like this:
public void onCreate(final Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.main);
// The list defined as field elswhere
this.view = (ListView) findViewById(R.id.searchResults);
this.view.setOnScrollListener(new OnScrollListener() {
private int priorFirst = -1;
#Override
public void onScroll(final AbsListView view, final int first, final int visible, final int total) {
// detect if last item is visible
if (visible < total && (first + visible == total)) {
// see if we have more results
if (first != priorFirst) {
priorFirst = first;
//Do stuff here, last item is displayed, end of list reached
}
}
}
});
}
The reason for the priorFirst counter is that sometimes scroll events can be generated multiple times, so you only need to react to the first time the end of the list is reached.
If you are trying to do an auto-growing list, I'd suggest this tutorial.
You need two things to precisely define the scroll position of a listView:
To get current position:
int firstVisiblePosition = listView.getFirstVisiblePosition();
int topEdge=listView.getChildAt(0).getTop(); //This gives how much the top view has been scrolled.
To set the position:
listView.setSelectionFromTop(firstVisiblePosition,0);
// Note the '-' sign for scrollTo..
listView.scrollTo(0,-topEdge);

Categories

Resources