RecyclerView ViewHolder creation inside NestedScrollView - android

I have two problems caused by the same source.
I'm working on a social media app, I have a profile view containing a CollapsingToolbarLayout for the profile picture and RecyclerView inside a NestedScrollView for the posts feed.
The behaviour I didn't expect is that let's say I have 20 posts in the RecyclerView and the screen can only display 3, the recycler adapter creates 20 view holders and they are all considered as visible.
This causes two problems for me :
1 - The posts may contain videos and I want the video to be stopped if the post is not visible on the screen. I used to do this on my other RecyclerViews.
#Override
public void onViewDetachedFromWindow(#NonNull RecyclerView.ViewHolder holder) {
if (holder instanceof PostViewHolder) {
PostViewHolder postViewHolder = (PostViewHolder) holder;
pauseVideo(postViewHolder.videoPlayer);
}
}
This method is never called because the RecyclerView or adapter or whatever considers all the view holders to be visible on the screen. To make sure my assumption is correct I did a log on onViewAttachedToWindow and if the list contains 20 posts, it gets called 20 times when I add the list to the RecyclerView.
2 - I want posts to be loaded dynamically (load small batches on scroll). This was achieved using this method :
WrapContentLinearLayoutManager llManager = new WrapContentLinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
recyclerView.setLayoutManager(llManager);
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if(dy > 0) { //check for scroll down
if (llManager.findFirstVisibleItemPosition() + 10 > llManager.getItemCount() && !loadingMorePosts) {
loadingMorePosts = true;
dbListeners.getMoreUserPosts();
}
}
}
});
This doesn't work too because llManager.findFirstVisibleItemPosition() always returns 0.
Am I doing something wrong or is this the expected behaviour from a RecyclerView inside a nested ScrollView?
And is there a solution or a workaround for the second problem because loading all the posts at once is not acceptable.
And Thanks.

You have to handle pagination at scroll listener of nesterscroll view
nestedScrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
#Override
public void onScrollChange(NestedScrollView view, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if (scrollY == (view.getChildAt(0).getMeasuredHeight() - view.getMeasuredHeight())&& !loadingMorePosts) {
loadingMorePosts = true;
dbListeners.getMoreUserPosts();
}
}
});

Related

Android horizontal RecycleView with never-ending scroll animation

I'm trying to implement an horizontal RecycleView in my Android app that displays content with never-ending scroll animation. See example of required UI here.
My question is, what would be the best practice on how to achieve this goal? (RecycleView should support scroll gestures from the user).
If I'm correct you're looking for the endless scrolling or infinite scrolling (Not Official names for that)
If it is correct then it shows like say you have 5 images in your recyclerview now when the user reaches the end item then the recycler view starts again from the first item like
item1, item2, item3, item4, item5 and again item5, item1, item2.....
To achieve this
Go to your adapter class and do the following code.
#Override
public int getItemCount() {
return items == null ? 0 : items.size() * 2; //Here instead of items.size(); you have to change like this
}
Now in your OnBindViewHolder method
MODEL_CLASS item = items.get(position%items.size());
Now head over to the activity where you set the adapter to recyclerview and add the following code
YOUR_ADAPTER_NAME.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int firstItemVisible = linearLayoutManager.findFirstVisibleItemPosition();
if (firstItemVisible != 0 && firstItemVisible % YOUR_LIST_ITEM.size() == 0) {
recyclerView.getLayoutManager().scrollToPosition(0);
}
}
});
By this you can see the items when scrolled to right it continues loops in your list.

How can I identify that RecyclerView's last item is visible on screen?

I have one RecyclerView and I added list of data into the RecyclerView. I wanted to add more data in list, when last RecyclerView item is visible on screen. After that I want to make a web service call and update the RecyclerView data. How can I achieve this?
Any suggestions?
One option would involve editing your LayoutManager. The idea here is to find the position of the last visible item. If that position is equal to the last item of your dataset, then you should trigger a reload.
#Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
final int result = super.scrollVerticallyBy(dy, recycler, state);
if (findLastVisibleItemPosition() == mData.length - 1) {
loadMoreData();
}
return result;
}
#Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
super.onLayoutChildren(recycler, state);
if (findLastVisibleItemPosition() == mData.length - 1) {
loadMoreData();
}
}
Alternatively, you could do this via your adapter's onBindViewHolder method, although this is admittedly a bit of a "hack":
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (position == mData.length - 1) {
// load more data here.
}
/// binding logic
}
3rd option would be to add an OnScrollListener to the RecyclerView. #velval's answer on this page explains this well.
Regardless which option you go for, you should also include code to prevent the data load logic from triggering too many times (e.g., before the previous request to fetch more data completes and returns new data).
If someone stumble across this post this is a quick and simple tutorial on how to do it:
All you need to do is:
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int visibleItemCount = lm.getChildCount();
int totalItemCount = lm.getItemCount();
int firstVisibleItemPosition= lm.findFirstVisibleItemPosition();
// Load more if we have reach the end to the recyclerView
if ( (visibleItemCount + firstVisibleItemPosition) >= totalItemCount && firstVisibleItemPosition >= 0) {
loadMoreItems();
}
}
});
Then your loadMoreItems() should look something like this:
private void loadMoreItems() {
// init offset=0 the frist time and increase the offset + the PAGE_SIZE when loading more items
queryOffset = queryOffset + PAGE_SIZE;
// HERE YOU LOAD the next batch of items
List<Items> newItems = loadItems(queryOffset, PAGE_SIZE);
if (newItems.size() > 0) {
items.addAll(newItems);
adapter.notifyDataSetChanged();
}
}
Seen many of the above answers but my answer is different one and it will work in your cases also. My approach is based on scroll state of recylerview. Maintain below variable "check" and this should update only once when api responds. Put below code in your api response. If you want to handle last item only on every call of api.
final boolean[] check = {true};
recyclerView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
if (!recyclerView.canScrollVertically(1)) {
// last item of recylerview reached.
if (check[0]) {
//your code for last reached item
scroll_handler.setVisibility(View.GONE);
}
} else {
scroll_handler.setVisibility(View.VISIBLE);
check[0] = false;
}
}
});
If you want to handle your last item every time then do it as below
recyclerView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
if (!recyclerView.canScrollVertically(1))
// Bottom of recyler view.
arrow_img.setRotation(180);
}
}
});
See also Android - Detect when the last item in a RecyclerView is visible.
private fun isLastItemVisible(): Boolean {
val layoutManager = recycler_view.layoutManager
val position = layoutManager.findLastCompletelyVisibleItemPosition()
return position >= adapter.itemCount - 1
}

RecyclerView ItemTouchHelper swipe remove animation

I've got a remove on swipe, that draws a background (much like the Inbox app), implemented by an ItemTouchHelper - by overriding the onChilDraw method and drawing a rectangle on the provided canvas:
ItemTouchHelper mIth = new ItemTouchHelper(
new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) {
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
remove(viewHolder.getAdapterPosition());
}
public boolean onMove(RecyclerView recyclerview, RecyclerView.ViewHolder v, RecyclerView.ViewHolder target) {
return false;
}
#Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
View itemView = viewHolder.itemView;
Drawable d = ContextCompat.getDrawable(context, R.drawable.bg_swipe_item_right);
d.setBounds(itemView.getLeft(), itemView.getTop(), (int) dX, itemView.getBottom());
d.draw(c);
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
});
The remove method called above is in the Adapter:
public void remove(int position) {
items.remove(position);
notifyItemRemoved(position);
}
The background draws out nicely, but when notifyItemRemoved is called (according to Mr. Debugger), the RecyclerView first deletes my pretty green background, and then pushes the two adjacent items together.
I would like it to keep the background there while it does that (just like the Inbox app). Is there any way to do that?
I had the same issue and I didn't wanna introduce a new lib just to fix it. The RecyclerView is not deleting your pretty green background, it's just redrawing itself, and your ItemTouchHelper is not drawing anymore. Actually it's drawing but the dX is 0 and is drawing from the itemView.getLeft() (which is 0) to dX (which is 0) so you see nothing. And it's drawing too much, but I'll come back to it later.
Anyway back to the background while rows animate: I couldn't do it within ItemTouchHelper and onChildDraw. In the end I had to add another item decorator to do it. It goes along these lines:
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (parent.getItemAnimator().isRunning()) {
// find first child with translationY > 0
// draw from it's top to translationY whatever you want
int top = 0;
int bottom = 0;
int childCount = parent.getLayoutManager().getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getLayoutManager().getChildAt(i);
if (child.getTranslationY() != 0) {
top = child.getTop();
bottom = top + (int) child.getTranslationY();
break;
}
}
// draw whatever you want
super.onDraw(c, parent, state);
}
}
This code takes into account only rows animating up, but you should also consider rows coming down. That happens if you swipe delete the last row, rows above are gonna animate down to that space.
When I said your ItemTouchHelper is drawing too much what I meant was: Looks like ItemTouchHelper keeps ViewHolders of removed rows in case they need to be restored. It's also calling onChildDraw for those VHs in addition to the VH being swiped. Not sure about memory management implications of this behavior but I needed an additional check in the start of onChildDraw to avoid drawing for "fantom" rows.
if (viewHolder.getAdapterPosition() == -1) {
return;
}
In your case it's drawing from left=0 to right=0 so you don't see anything but the overhead is there. If you start seeing previously swiped away rows drawing their backgrounds that is the reason.
EDIT: I had a go at this, see this blog post and this github repo.
I managed to get it to work by using Wasabeefs's recyclerview-animators library.
My ViewHolder now extends the library's provided AnimateViewHolder:
class MyViewHolder extends AnimateViewHolder {
TextView textView;
public MyViewHolder(View itemView) {
super(itemView);
this.textView = (TextView) itemView.findViewById(R.id.item_name);
}
#Override
public void animateAddImpl(ViewPropertyAnimatorListener listener) {
ViewCompat.animate(itemView)
.translationY(0)
.alpha(1)
.setDuration(300)
.setListener(listener)
.start();
}
#Override
public void preAnimateAddImpl() {
ViewCompat.setTranslationY(itemView, -itemView.getHeight() * 0.3f);
ViewCompat.setAlpha(itemView, 0);
}
#Override
public void animateRemoveImpl(ViewPropertyAnimatorListener listener) {
ViewCompat.animate(itemView)
.translationY(0)
.alpha(1)
.setDuration(300)
.setListener(listener)
.start();
}
}
The overrided function implementations are identical to what is in recyclerview-animators' readme on github.
It also seems necessary to change the ItemAnimator to a custom one and set the removeDuration to 0 (or another low value - this is to prevent some flickering):
recyclerView.setItemAnimator(new SlideInLeftAnimator());
recyclerView.getItemAnimator().setRemoveDuration(0);
This doesn't cause any problems as the normal (non-swiping) remove animation used is the one in the AnimateViewHolder.
All other code was kept the same as in the question. I haven't had the time to figure out the inner workings of this yet, but if anyone feels like doing it feel free to update this answer.
Update: Setting recyclerView.getItemAnimator().setRemoveDuration(0); actually breaks the "rebind" animation of the swipe. Fortunately, removing that line and setting a longer duration in animateRemoveImpl (500 works for me) also solves the flickering problem.
Update 2: Turns out that ItemTouchHelper.SimpleCallback uses ItemAnimator's animation durations, which is why the above setRemoveDuration(0) breaks the swipe animation. Simply overriding it's method getAnimationDuration to:
#Override
public long getAnimationDuration(RecyclerView recyclerView, int animationType, float animateDx, float animateDy) {
return animationType == ItemTouchHelper.ANIMATION_TYPE_DRAG ? DEFAULT_DRAG_ANIMATION_DURATION
: DEFAULT_SWIPE_ANIMATION_DURATION;
}
solves that problem.
Just update the adapter position and then remove the animation
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
if (direction == ItemTouchHelper.LEFT) {
remove(position);
} else {
mAdapter.notifyItemChanged(position);
}
}

(Smooth)ScrollToPosition doesn't work properly with RecyclerView

I'm using basic RecyclerView with GridLayoutManager. I observed that nor smoothScrollToPosition nor scrollToPosition works properly.
a) when using smoothScrollToPosition I often receive error from RecyclerView
"RecyclerView﹕ Passed over target position while smooth scrolling."
and RecyclerView is not scrolled properly (often it misses the targeted row). This is observed mostly when I'm trying to scroll to the 1st item of some row
b) when using scrollToPosition it seems to work quite ok but most of the time I can see only the 1st item of the row and the rest are not displayed.
Can you give me some hints how to make work properly at least one of the methods?
Thanks a lot!
Finally I was able to make it work! LinearLayoutManager.scrollToPositionWithOffset(int, int) did the trick.
I also have same issue, but managed to fix the issue by Customizing SmoothScroller
let Custom LayoutManager as below
public class CustomLayoutManager extends LinearLayoutManager {
private static final float MILLISECONDS_PER_INCH = 50f;
private Context mContext;
public CustomLayoutManager(Context context) {
super(context);
mContext = context;
}
#Override
public void smoothScrollToPosition(RecyclerView recyclerView,
RecyclerView.State state, final int position) {
LinearSmoothScroller smoothScroller =
new LinearSmoothScroller(mContext) {
//This controls the direction in which smoothScroll looks
//for your view
#Override
public PointF computeScrollVectorForPosition
(int targetPosition) {
return CustomLayoutManager.this
.computeScrollVectorForPosition(targetPosition);
}
//This returns the milliseconds it takes to
//scroll one pixel.
#Override
protected float calculateSpeedPerPixel
(DisplayMetrics displayMetrics) {
return MILLISECONDS_PER_INCH/displayMetrics.densityDpi;
}
};
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}
}
(documentation commented inside the code given above)Please set the above LayoutManager
to the recyerview
CustomLayoutManagerlayoutManager = new CustomLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
recyclerView.smoothScrollToPosition(position);
by using the custom Layout manager
scrollToPosition
also working well in my case u can use
recyclerView.scrollToPosition(position)
also if you want to adjust the speed of smoothScrollToPosition please override the
private static final float MILLISECONDS_PER_INCH = 50f;
in CustomLayoutManager.
So if we put the value as 1f the smoothScrollToPosition will be faster like scrollToPosition.increasing value make delay and decreasing will make the speed of scroll.
Hope this will useful.
In My case,
mRecyclerView.scrollToPosition(10);
also did not work.
But
mRecyclerView.smoothScrollToPosition(10);
works fine for me...
To scroll down smoothly to bottom from any position in the RecyclerView on clicking EditText.
edittext.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
rv_commentList.postDelayed(new Runnable() {
#Override
public void run() {
rv_commentList.scrollToPosition(rv_commentList.getAdapter().getItemCount() - 1);
}
}, 1000);
}
});
Another reason why any of the before mentioned solutions may not work is if your RecyclerView is embedded in a NestedScrollView. In this case you have to call the scroll action on the NestedScrollView.
for example:
nestedScrollview.smoothScrollTo(0,0)
This extension is so useful, try please.
fun RecyclerView.smoothSnapToPosition(position: Int, snapMode: Int = LinearSmoothScroller.SNAP_TO_START) {
val smoothScroller = object : LinearSmoothScroller(this.context) {
override fun getVerticalSnapPreference(): Int = snapMode
override fun getHorizontalSnapPreference(): Int = snapMode
}
smoothScroller.targetPosition = position
layoutManager?.startSmoothScroll(smoothScroller)
}
I was facing a weird issue wherein smoothScrollToPosition only worked occasionally.
After putting the smoothScrollToPosition inside Handler Post
Delayed with 1 second delay, it worked fine.
Refer to the following Kotlin example:
Handler().postDelayed({
recyclerViewObject.smoothScrollToPosition(0) // mention the position in place of 0
}, 1000) // 1000 indicates the 1 second delay.
recyclerView.getLayoutManager().smoothScrollToPosition(recyclerView,new RecyclerView.State(),currentPosition);
Try measuring item width or height and call smoothScrollBy(int dx, int dy).
How to perform smooth scrolling and save RecyclerView vertical position after device rotating:
This is the method that works for my case,
public class MainFragment extends Fragment { //OR activity it's //fragment in my case
....
#Override
public void onLoadFinished(#NonNull Loader<List<Report>> loader, List<Report> objects) { // or other method of your choice, in my case it's a Loader
RecyclerView recyclerViewRv = findViewById(........;
.....
recyclerViewRv.setAdapter(.....Your adapter);
recyclerViewRv.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
recyclerScrollY = recyclerViewRv. computeVerticalScrollOffset();
}
});
//Apply smooth vertical scroll
recyclerViewRv.smoothScrollBy(0,recyclerScrollY);
}
//Save vertical scroll position before rotating devices
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("recyclerScrollY",recyclerScrollY);
}
//BackUp vertical scroll position after rotating devices
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState != null) {
recyclerScrollY = savedInstanceState.getInt("recyclerScrollY");
}
}
//If you want to perform the same operation for horizontal scrolling just add a variable called recyclerScrollX = recyclerScrollY = recyclerViewRv. computeHorizontalScrollOffset(); then save in bundle
Calling the recyclerView smoothScroll isn't effective, as the recyclerView itself doesn't handle its layout.
What you should do is calling the layout manager scroll method instead.
This should look something like this
mRecyclerView.getLayoutManager().scrollToPosition(desiredPosition);
If you are trying to do a quick scroll to a position at the top of the RecyclerView, just use LinearLayoutManager.scrollToPositionWithOffset with 0 as the offset.
Example:
mLinearLayoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(mLinearLayoutManager);
mLinearLayoutManager.scrollToPositionWithOffset(myPosition, 0);
smoothScrollToPosition is very slow. If you want something fast go with scrollToPositionWithOffset.
when you use scrollToPosition it will show it on top of the recycler view.
But if you use smoothScrollToPosition it will scroll till it come in to Window Visible. that's why while smoothScrool to item below, it will show it on bottom
Actually, if you have a RecyclerView inside a NestedScrollView, you must use both of these lines every time you want to go to the beginning of the RecyclerView:
nestedScrollView.smoothScrollTo(0, 0);
layoutManager.scrollToPositionWithOffset(0, 0);
This completely works for me.
this worked for me
Handler().postDelayed({
(recyclerView.getLayoutManager() as LinearLayoutManager).scrollToPositionWithOffset( 0, 0)
}, 100)
None of these answers worked for me. I needed to smoothScrollToPosition but #Ramz answer didn't work. I was finding it would consistently overscroll but only in one direction. I discovered that it seemed to be the item decorators throwing it off. I had a horizontal layout and I wanted to add a space after every item except the last and it didn't like that asymmetry. As soon as I included a space after every item, it worked!
nestedScroll.smoothScrollTo(0, recycler.top)
So i was looking for a solution to get back to the top with a recyclerview inside another layout that has a view on top of it (in my case I had a LinearLayout with a TextView and my recyclerview inside). Because of that the smoothscroll would go only to half the way to the first item.
Here's what I did which works really well (Kotlin solution):
back_to_top.setOnClickListener {
GlobalScope.launch(Dispatchers.Main) {
GlobalScope.launch(Dispatchers.Main) {
recyclerview.layoutManager?.smoothScrollToPosition(recyclerview, RecyclerView.State(), 0)
delay((recyclerview.layoutManager as LinearLayoutManager).findLastVisibleItemPosition() * 100L)
}.join()
recyclerview.scrollToPosition(0)
}
back_to_top.visibility = View.GONE
}
}
Here what I do is I smoothscroll to the first element and delay the scroll by 100ms times the last item visible and then call the scrollToPosition(0) (which goes to the top.correctly)

Scroll RecyclerView to show selected item on top

I'm looking for a way to scroll a RecyclerView to show the selected item on top.
In a ListView I was able to do that by using scrollTo(x,y) and getting the top of the element that need to be centered.
Something like:
#Override
public void onItemClick(View v, int pos){
mylistView.scrollTo(0, v.getTop());
}
The problem is that the RecyclerView returns an error when using it's scrollTo method saying
RecyclerView does not support scrolling to an absolute position
How can I scroll a RecyclerView to put the selected item at the top of the view?
If you are using the LinearLayoutManager or Staggered GridLayoutManager, they each have a scrollToPositionWithOffset method that takes both the position and also the offset of the start of the item from the start of the RecyclerView, which seems like it would accomplish what you need (setting the offset to 0 should align with the top).
For instance:
//Scroll item 2 to 20 pixels from the top
linearLayoutManager.scrollToPositionWithOffset(2, 20);
If you looking for vertical LinearLayout Manager you can achieve smooth scrolling using a custom LinearSmoothScroller:
import android.content.Context;
import android.graphics.PointF;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.LinearSmoothScroller;
import android.support.v7.widget.RecyclerView;
public class SnappingLinearLayoutManager extends LinearLayoutManager {
public SnappingLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
#Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
int position) {
RecyclerView.SmoothScroller smoothScroller = new TopSnappedSmoothScroller(recyclerView.getContext());
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}
private class TopSnappedSmoothScroller extends LinearSmoothScroller {
public TopSnappedSmoothScroller(Context context) {
super(context);
}
#Override
public PointF computeScrollVectorForPosition(int targetPosition) {
return SnappingLinearLayoutManager.this
.computeScrollVectorForPosition(targetPosition);
}
#Override
protected int getVerticalSnapPreference() {
return SNAP_TO_START;
}
}
}
use an instance of the layoutmanager in recycle view and then calling recyclerView.smoothScrollToPosition(pos); will smooth scroll to selected position to top of the recycler view
//Scroll item pos
linearLayoutManager.scrollToPositionWithOffset(pos, 0);
You just need to call recyclerview.scrollToPosition(position). That's fine!
If you want to call it in adapter, just let your adapter has the instance of recyclerview or the activity or fragment which contains recyclerview,than implements the method getRecyclerview() in them.
I hope it can help you.
If you want to scroll automatic without show scroll motion then you need to write following code:
mRecyclerView.getLayoutManager().scrollToPosition(position);
If you want to display scroll motion then you need to add following code.
=>Step 1: You need to declare SmoothScroller.
RecyclerView.SmoothScroller smoothScroller = new
LinearSmoothScroller(this.getApplicationContext()) {
#Override
protected int getVerticalSnapPreference() {
return LinearSmoothScroller.SNAP_TO_START;
}
};
=>step 2: You need to add this code any event you want to perform scroll to specific position.
=>First you need to set target position to SmoothScroller.
smoothScroller.setTargetPosition(position);
=>Then you need to set SmoothScroller to LayoutManager.
mRecyclerView.getLayoutManager().startSmoothScroll(smoothScroller);
just call this method simply:
((LinearLayoutManager)recyclerView.getLayoutManager()).scrollToPositionWithOffset(yourItemPosition,0);
instead of:
recyclerView.scrollToPosition(yourItemPosition);
same with speed regulator
public class SmoothScrollLinearLayoutManager extends LinearLayoutManager {
private static final float MILLISECONDS_PER_INCH = 110f;
private Context mContext;
public SmoothScrollLinearLayoutManager(Context context,int orientation, boolean reverseLayout) {
super(context,orientation,reverseLayout);
mContext = context;
}
#Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
int position) {
RecyclerView.SmoothScroller smoothScroller = new TopSnappedSmoothScroller(recyclerView.getContext()){
//This controls the direction in which smoothScroll looks for your view
#Override
public PointF computeScrollVectorForPosition(int targetPosition) {
return new PointF(0, 1);
}
//This returns the milliseconds it takes to scroll one pixel.
#Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
}
};
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}
private class TopSnappedSmoothScroller extends LinearSmoothScroller {
public TopSnappedSmoothScroller(Context context) {
super(context);
}
#Override
public PointF computeScrollVectorForPosition(int targetPosition) {
return SmoothScrollLinearLayoutManager.this
.computeScrollVectorForPosition(targetPosition);
}
#Override
protected int getVerticalSnapPreference() {
return SNAP_TO_START;
}
}
}
Try what worked for me cool!
Create a variable private static int displayedposition = 0;
Now for the position of your RecyclerView in your Activity.
myRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
LinearLayoutManager llm = (LinearLayoutManager) myRecyclerView.getLayoutManager();
displayedposition = llm.findFirstVisibleItemPosition();
}
});
Place this statement where you want it to place the former site displayed in your view .
LinearLayoutManager llm = (LinearLayoutManager) mRecyclerView.getLayoutManager();
llm.scrollToPositionWithOffset(displayedposition , youList.size());
Well that's it , it worked fine for me \o/
what i did to restore the scroll position after refreshing the RecyclerView on button clicked:
if (linearLayoutManager != null) {
index = linearLayoutManager.findFirstVisibleItemPosition();
View v = linearLayoutManager.getChildAt(0);
top = (v == null) ? 0 : (v.getTop() - linearLayoutManager.getPaddingTop());
Log.d("TAG", "visible position " + " " + index);
}
else{
index = 0;
}
linearLayoutManager = new LinearLayoutManager(getApplicationContext());
linearLayoutManager.scrollToPositionWithOffset(index, top);
getting the offset of the first visible item from the top before creating the linearLayoutManager object and after instantiating it the scrollToPositionWithOffset of the LinearLayoutManager object was called.
I don't know why I didn't find the best answer but its really simple.
recyclerView.smoothScrollToPosition(position);
No errors
Creates Animations
What i may add here is how to make it work together with DiffUtil and ListAdapter
You may note that calling recyclerView.scrollToPosition(pos) or (recyclerView.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(pos, offset) wouldn't work if called straight after adapter.submitList. It is because the differ looks for changes in a background thread and then asynchronously notifies adapter about changes. On a SO i have seen several wrong answers with unnecessary delays & etc to solve this.
To handle the situation properly the submitList has a callback which is invoked when changes have been applied.
So the proper kotlin implementations in this case are:
//memorise target item here and a scroll offset if needed
adapter.submitList(items) {
val pos = /* here you may find a new position of the item or just use just a static position. It depends on your case */
recyclerView.scrollToPosition(pos)
}
//or
adapter.submitList(items) { recyclerView.smoothScrollToPosition(pos) }
//or etc
adapter.submitList(items) { (recyclerView.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(pos, offset) }
Introduction
None of the answers explain how to show last item(s) at the top. So, the answers work only for items that still have enough items above or below them to fill the remaining RecyclerView. For instance, if there are 59 elements and a 56-th element is selected it should be at the top as in the picture below:
So, let's see how to implement this in the next paragraph.
Solution
We could handle those cases by using linearLayoutManager.scrollToPositionWithOffset(pos, 0) and additional logic in the Adapter of RecyclerView - by adding a custom margin below the last item (if the last item is not visible then it means there's enough space fill the RecyclerView). The custom margin could be a difference between the root view height and the item height. So, your Adapter for RecyclerView would look as follows:
...
#Override
public void onBindViewHolder(ViewHolder holder, final int position) {
...
int bottomHeight = 0;
int itemHeight = holder.itemView.getMeasuredHeight();
// if it's the last item then add a bottom margin that is enough to bring it to the top
if (position == mDataSet.length - 1) {
bottomHeight = Math.max(0, mRootView.getMeasuredHeight() - itemHeight);
}
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)holder.itemView.getLayoutParams();
params.setMargins(0, 0, params.rightMargin, bottomHeight);
holder.itemView.setLayoutParams(params);
...
}
...
If your LayoutManager is LinearLayoutManager you can use scrollToPositionWithOffset(position,0); on it and it will make your item the first visible item in the list. Otherwise, you can use smoothScrollToPosition on the RecyclerView directly.
I ended up using the below code.
RecyclerView.LayoutManager layoutManager = mainList.getLayoutManager();
if (layoutManager instanceof LinearLayoutManager) {
// Scroll to item and make it the first visible item of the list.
((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(position, 0);
} else {
mainList.smoothScrollToPosition(position);
}
scroll at particular position
and this helped me alot.
by click listener you can get the position in your adapter
layoutmanager.scrollToPosition(int position);
In my case my RecyclerView have a padding top like this
<android.support.v7.widget.RecyclerView
...
android:paddingTop="100dp"
android:clipToPadding="false"
/>
Then for scroll a item to top, I need to
recyclerViewLinearLayoutManager.scrollToPositionWithOffset(position, -yourRecyclerView.getPaddingTop());
please note that if scrollToPosition not work notice that your RecyclerView was inside a NestedScrollView; refer to this post
This is pretty simple
recyclerView.scrollToPosition(position)
If you've Recycler view inside nestedscrollview :
val y = recyclerview.getChildAt(0).y
recyclerview.smoothScrollTo(0, y.toInt())
If your Recycler view is not inside nestedscrollview :
recyclerview.smoothScrollToPosition(index)
or
recyclerview.layoutManager?.smoothScrollToPosition(recyclerview, null ,index)
I use the code below to smooth-scroll an item (thisView) to the top.
It works also for GridLayoutManager with views of different heights:
View firstView = mRecyclerView.getChildAt(0);
int toY = firstView.getTop();
int firstPosition = mRecyclerView.getChildAdapterPosition(firstView);
View thisView = mRecyclerView.getChildAt(thisPosition - firstPosition);
int fromY = thisView.getTop();
mRecyclerView.smoothScrollBy(0, fromY - toY);
Seems to work good enough for a quick solution.

Categories

Resources