ReyclerView - programmatically scroll with predefined time - android

I need to do some dragging similar action on my RecyclerView and when I reach the top or bottom end of the RecyclerView I want to scroll it smoothly...
I do this like following: I post this runnable to scroll until the user is not near enough to the start/end of the RecyclerView anymore.
mScroll = new Runnable() {
#Override
public void run() {
rvImages.smoothScrollBy(0, mScrollDist.get());
mHandler.postDelayed(mScroll, 100);
}
};
The problem, I need to define, that scrolling the desired distance should be done in 100ms, so that the overall scrolling is smooth.
How can I achieve that?

Use custom LinearLayoutManager
public class MyCustomLayoutManager extends LinearLayoutManager {
//We need mContext to create our LinearSmoothScroller
private Context mContext;
public MyCustomLayoutManager(Context context) {
super(context);
mContext = context;
}
//Override this method? Check.
#Override
public void smoothScrollToPosition(RecyclerView recyclerView,
RecyclerView.State state, int position) {
//Create your RecyclerView.SmoothScroller instance? Check.
LinearSmoothScroller smoothScroller =
new LinearSmoothScroller(mContext) {
//Automatically implements this method on instantiation.
#Override
public PointF computeScrollVectorForPosition(int targetPosition) {
//What is PointF? A class that just holds two float coordinates.
//Accepts a (x , y)
//for y: use -1 for up direction, 1 for down direction.
//for x (did not test): use -1 for left direction, 1 for right
//direction.
//We let our custom LinearLayoutManager calculate PointF for us
return MyCustomLayoutManager.this.computeScrollVectorForPosition
(targetPosition);
}
};
//Docs do not tell us anything about this,
//but we need to set the position we want to scroll to.
smoothScroller.setTargetPosition(position);
//Call startSmoothScroll(SmoothScroller)? Check.
startSmoothScroll(smoothScroller);
}
}
Customizing Scrool Speed
//Make an instance variable at the top of you custom LayoutManager
private static final float MILLISECONDS_PER_INCH = 50f;
.
.
.
LinearSmoothScroller smoothScroller =
new LinearSmoothScroller(mContext) {
.
.
.
//The holy grail of smooth scrolling
//returns the milliseconds it takes to scroll one pixel.
#Override
protected float calculateSpeedPerPixel
(DisplayMetrics displayMetrics) {
return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
}
};
In Activity:
mLayoutManager = new MyCustomLayoutManager(getActivity());
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.smoothScrollToPosition(position);
source code

Related

Android RecyclerView with gridlayout smoothScrollTo() speed

I have a recyclerview with gridlayoutmanager.
If I run the code
recycler.smoothScrollTo(adapter.getItemCount())
the recycler scrolls really fast to the last element. I tried some solutions on Stackoverflow to make the scrolling slower, but all apply to Linearlayoutmanager not Gridlayoutmanager.
Any help?
I cannot say for sure what your problem is. But I am lucky enough to have a very simple GridLayoutManager recyclerview demo out there, very small sample project. I created a so branch and added a button that does the same you do.
Look it up: https://github.com/Gryzor/GridToShowAds/compare/so?expand=1
.setOnClickListener { mainRecyclerView.smoothScrollToPosition(data.size) }
And that alone just works.
Check the source code, it's a very simple sample for something unrelated, but happens to have a RV with a Grid Layout :)
UPDATE
What you actual want is to control the Speed at which the recyclerView scrolls. Ok.
It's not the RecyclerView that drives the scroll, it's actually the LayoutManager that does. How so?
If you look at RV's source code...
public void smoothScrollToPosition(int position) {
...
mLayout.smoothScrollToPosition(this, mState, position);
}
So it ends up calling mLayout. What is this?
#VisibleForTesting LayoutManager mLayout;
So, your LayoutManager#smoothScroll... method is used.
Decompiling now GridLayoutManager for science:
#Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
int position) {
LinearSmoothScroller linearSmoothScroller =
new LinearSmoothScroller(recyclerView.getContext());
linearSmoothScroller.setTargetPosition(position);
startSmoothScroll(linearSmoothScroller);
}
note: this method is actually in LinearLayoutManager because GridLayoutManager is a subclass and it doesn't override the method
A LinearSmoothScroller!; no parameter to specify the speed though...
Look at it:
public class LinearSmoothScroller extends RecyclerView.SmoothScroller {
private static final boolean DEBUG = false;
private static final float MILLISECONDS_PER_INCH = 25f;
private static final int TARGET_SEEK_SCROLL_DISTANCE_PX = 10000;
...
}
This class has a start() method described as:
* Starts a smooth scroll for the given target position.
So who calls this?
The mLayout.smoothScrollToPosition method does at the end in the startSmoothScroll(...) call.
public void startSmoothScroll(SmoothScroller smoothScroller) {
Starts a smooth scroll using the provided {#link SmoothScroller}.
mSmoothScroller.start(mRecyclerView, this);
So... in lieu of all this, the answer to your question is:
You need to create your extension of GridLayoutManager by subclassing it, and in it, override the smoothScrollToPosition method, to provide your own Scroller logic.
Thread carefully though, LayoutManagers are not the "simplest" classes of all time and they can be quite complicated to master.
Good luck! :)
My simple working solution currently is still implementing a timer then working with it.
final CountDownTimer scrollUp_timer = new CountDownTimer(50000, 30) {
#Override
public void onTick(long millisUntilFinished) {
if (layoutManager != null && layoutManager.findFirstVisibleItemPosition() != 0) searchRecyclerView.smoothScrollToPosition(layoutManager.findFirstVisibleItemPosition()-1);
}
#Override
public void onFinish() {
try{
}catch(Exception e){
// log
}
}
};
scrollUp.setOnDragListener(new View.OnDragListener() {
#Override
public boolean onDrag(View view, DragEvent dragEvent) {
layoutManager = ((GridLayoutManager)searchRecyclerView.getLayoutManager());
int action = dragEvent.getAction();
if (action == DragEvent.ACTION_DRAG_ENTERED) {
scrollUp_timer.start();
} else if (action == DragEvent.ACTION_DRAG_EXITED) {
searchRecyclerView.scrollBy(0,0);
scrollUp_timer.cancel();
}
return true;
}
});
You can extend:
class CSCustomRecyclerSmoothScroller(context: Context, speed: Float = 0.2f)
: LinearSmoothScroller(context) {
override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float = speed
}
And use it like:
val shortAnimationDuration =
view.resources.getInteger(android.R.integer.config_shortAnimTime)
val scroller = CSCustomRecyclerSmoothScroller(this, speed = 0.15)
scroller.targetPosition = position
view.postDelayed({
layoutManager.startSmoothScroll(scroller)
}, shortAnimationDuration.toLong())
postDelayed can be necessary in some cases but maybe not in all.
I use similar code with GridLayoutManager I just tried to extract relevant parts from my way of writing things.

How to tell RecyclerView to start at specific item position

I want my RecyclerView with LinearLayoutManager to show up with scroll position at specific item after adapter got updated. (not first/last position)
Means the at first (re-)layout, this given position should be in visible area.
It should not layout with position 0 on top and scroll afterwards to target position.
My Adapter starts with itemCount=0, loads its data in thread and notifies its real count later. But the start position must be set already while count is still 0!
As of now I used some kind of post Runnable containingscrollToPosition but this has side effects (starts at first pos and jumps immediately to target position (0 -> target) and seems not to work well with DiffUtil (0 -> target -> 0))
Edit: To clearify: I need alternative to layoutManager.setStackFromEnd(true);, something like setStackFrom(position). ScrollToPosition does not work, if I call it when itemCount is still 0, so it gets ignored. If I call it when I notify that itemCount is now >0, it will layout from 0 and jumps short after to target position. And it fails completely if I use DiffUtil.DiffResult.dispatchUpdatesTo(adapter)`. (shows from 0, then scrolls to target position and then again back to position 0)
I found a solution myself:
I extended the LayoutManager:
class MyLayoutManager extends LinearLayoutManager {
private int mPendingTargetPos = -1;
private int mPendingPosOffset = -1;
#Override
public void onLayoutChildren(Recycler recycler, State state) {
if (mPendingTargetPos != -1 && state.getItemCount() > 0) {
/*
Data is present now, we can set the real scroll position
*/
scrollToPositionWithOffset(mPendingTargetPos, mPendingPosOffset);
mPendingTargetPos = -1;
mPendingPosOffset = -1;
}
super.onLayoutChildren(recycler, state);
}
#Override
public void onRestoreInstanceState(Parcelable state) {
/*
May be needed depending on your implementation.
Ignore target start position if InstanceState is available (page existed before already, keep position that user scrolled to)
*/
mPendingTargetPos = -1;
mPendingPosOffset = -1;
super.onRestoreInstanceState(state);
}
/**
* Sets a start position that will be used <b>as soon as data is available</b>.
* May be used if your Adapter starts with itemCount=0 (async data loading) but you need to
* set the start position already at this time. As soon as itemCount > 0,
* it will set the scrollPosition, so that given itemPosition is visible.
* #param position
* #param offset
*/
public void setTargetStartPos(int position, int offset) {
mPendingTargetPos = position;
mPendingPosOffset = offset;
}
}
It stores my target position. If onLayoutChildren is called by RecyclerView, it checks if adapters itemCount is already > 0. If true, it calls scrollToPositionWithOffset().
So I can tell immediately what position should be visible, but it will not be told to LayoutManager before position exists in Adapter.
You can try this, it will scroll to a position you want:
rv.getLayoutManager().scrollToPosition(positionInTheAdapter).
If you want to scroll to a specific position and that position is the adapter's position, then you can use StaggeredGridLayoutManager scrollToPosition
StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.VERTICAL);
staggeredGridLayoutManager.scrollToPosition(10);
recyclerView.setLayoutManager(staggeredGridLayoutManager);
If I understand the question, you want to scroll to a specific position but that position is the adapter's position and not the RecyclerView's item position.
You can only achieve this through the LayoutManager.
rv.getLayoutManager().scrollToPosition(youPositionInTheAdapter);
None of the methods above worked for me. I did the following using ViewTreeObserver that is triggered once its children have been added/changed visibility.
recyclerView.apply {
adapter = ...
layoutManager = ...
val itemCount = adapter?.itemCount ?: 0
if(itemCount > 1) {
viewTreeObserver.addOnGlobalLayoutListener(object: ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
viewTreeObserver.removeOnGlobalLayoutListener(this)
(layoutManager as? LinearLayoutManager)?.scrollToPosition(#PositionToStartAt)
}
}
}
Go ahead and set #PositionToStartAt to a specific value. You can also ensure that the RecyclerView initial position setting gets triggered once a specific number of children have been laid out to ensure it is set correctly.
if(recyclerView.childCount() > #PositionCheck) {
viewTreeObserver.removeOnGlobalLayoutListener(this)
(layoutManager as? LinearLayoutManager)?.scrollToPosition(#PositionToStartAt)
}
If your only motive is to start the recyclerView from a specific position without any scroll-like animation I'll suggest using StaggeredGridLayoutManager
val staggeredGridLayoutManager = StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.HORIZONTAL)//or VERTICAL
staggeredGridLayoutManager.scrollToPosition(specificPosition)
recyclerView.apply{
layoutManager = staggeredGridLayoutManager
}
Another contribution to a long running question...
As mentioned, layoutManager.scrollToPosition/WithOffset() does work to get the RecyclerView positioned, but timing this can be tricky. For example with variable length item views the RecyclerView has to work hard to get all the prior item views measured.
The approach below simply delays telling the RecyclerView about the prior items, then calls notifyItemRangeInserted(0, offset). This enabled the view to appear in the right place, with no visible scrolling at the start, and ready to scroll back.
private List<Bitmap> bitmaps = new ArrayList<>();
...
private class ViewAdapter extends RecyclerView.Adapter<ViewHolder> {
private volatile int offset;
private boolean offsetCancelled;
ViewAdapter(int initialOffset) {
this.offset = initialOffset;
this.offsetCancelled = initialOffset > 0;
}
#Override
public int getItemCount() {
return bitmaps.size() - offset;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
ImageView imageView = new ImageView(MyActivity.this); // Or getContext() from a Fragment
RecyclerView.LayoutParams lp = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
imageView.setLayoutParams(lp);
return new ViewHolder(imageView);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
holder.imageView.setImageBitmap(bitmaps.get(position + offset));
if (!offsetCancelled) {
offsetCancelled = true;
recyclerView.post(() -> {
int previousOffset = offset;
offset = 0;
notifyItemRangeInserted(0, previousOffset);
Log.i(TAG, "notifyItemRangeInserted(0, " + previousOffset + ")");
});
}
}
}
private class ViewHolder extends RecyclerView.ViewHolder {
private final ImageView imageView;
ViewHolder(#NonNull ImageView itemView) {
super(itemView);
this.imageView = itemView;
}
}
For context, this is a full example, based around ImageView's and Bitmap's. The key bit is the use of the offset in getItemCount() and onBindViewHolder().

RecyclerView smooth scroll

I'm tring to make smooth scroller to need position in recyclerView. I'm ovverride method smoothScrollToPosition in LinearLayoutManager, and it's work fine. But, I need to set scrolled position on top of screen(if is possible). I also tried scrollToPositionWithOffset, and it's make that I need, but now, without smooth effect. How to mix this methods, and make smooth scroll with set item on top?
private LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this){
#Override
public void smoothScrollToPosition(RecyclerView recyclerView,
RecyclerView.State state, final int position) {
LinearSmoothScroller smoothScroller =
new LinearSmoothScroller(getApplicationContext()) {
//This controls the direction in which smoothScroll looks
//for your view
#Override
public PointF computeScrollVectorForPosition
(int targetPosition) {
return this
.computeScrollVectorForPosition(targetPosition);
}
//This returns the milliseconds it takes to
//scroll one pixel.
#Override
protected float calculateSpeedPerPixel
(DisplayMetrics displayMetrics) {
return 50f/displayMetrics.densityDpi;
}
};
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}
};
This is late response, but I want to suggest a library SnappySmoothScroller.
This library enables smooth scroll with specific snap position: SnapType.START , SnapType.CENTER or SnapType.END
Use like this:
private LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this) {
#Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
SnappySmoothScroller scroller = new SnappySmoothScroller.Builder()
.setPosition(position)
.setSnapType(SnapType.START)
.setScrollVectorDetector(new LinearLayoutScrollVectorDetector(this))
.build(recyclerView.getContext());
startSmoothScroll(scroller);
}
};

Android : Control Smooth scroll over recycler view

I am using Recyclerview with CardView. I am aware how to control speed on list view. But not for Recyclerview.
I searched a lot in found class name SmoothScroll. How to use that? I have no Idea! Right now Recyclerview by default scroll is fast.
UPDATE:
I Summarized Gil Answer with this
It's unclear what you mean when you say "smoothScroll". You could be referring to the automatic "smoothScrollToPosition" which will automatically scroll to a specified position, you could be talking about manual scrolling and you could be talking about flinging. For the sake of prosperity, I will attempt to answer all of these issues now.
1. Automatic smooth scrolling.
Inside your layout manager, you need to implement the smoothScrollToPosition method:
#Override
public void smoothScrollToPosition(RecyclerView recyclerView, State state, int position)
{
// A good idea would be to create this instance in some initialization method, and just set the target position in this method.
LinearSmoothScroller smoothScroller = new LinearSmoothScroller(getContext())
{
#Override
public PointF computeScrollVectorForPosition(int targetPosition)
{
int yDelta = calculateCurrentDistanceToPosition(targetPosition);
return new PointF(0, yDelta);
}
// This is the important method. This code will return the amount of time it takes to scroll 1 pixel.
// This code will request X milliseconds for every Y DP units.
#Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics)
{
return X / TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, Y, displayMetrics);
}
};
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}
In this example, I use a helper method named "calculateCurrentDistanceToPosition". This can be a bit tricky, since it involves keeping track of your current scroll position, and calculating the scroll position of a given y position. You can find an example of how to keep track of the recycler's scroll y here.
Calculating the scroll y of a given position is really dependent on what your recycler is displaying. Assuming all your items are the same height, you can calculate this by performing the following calculation:
targetScrollY = targetPosition * itemHeight
Then, to calculate the distance you need to scroll, simply subtract the current scroll y with the target scroll y:
private int calculateCurrentDistanceToPosition(int targetPosition) {
int targetScrollY = targetPosition * itemHeight;
return targetScrollY - currentScrollY;
}
2. Slowing down manual scrolling.
Once again, you need to edit your layout manager, this time - the scrollVerticallyBy method:
#Override
public int scrollVerticallyBy(int delta, Recycler recycler, State state)
{
// write your limiting logic here to prevent the delta from exceeding the limits of your list.
int prevDelta = delta;
if (getScrollState() == SCROLL_STATE_DRAGGING)
delta = (int)(delta > 0 ? Math.max(delta * MANUAL_SCROLL_SLOW_RATIO, 1) : Math.min(delta * MANUAL_SCROLL_SLOW_RATIO, -1));
// MANUAL_SCROLL_SLOW_RATIO is between 0 (no manual scrolling) to 1 (normal speed) or more (faster speed).
// write your scrolling logic code here whereby you move each view by the given delta
if (getScrollState() == SCROLL_STATE_DRAGGING)
delta = prevDelta;
return delta;
}
Edit: In the above method, I call "getScrollState()". This is a method of RecyclerView. In this implementation, my custom LayoutManager is a nested class of my custom RecyclerView. If this doesn't work for you, you can try to grab the scroll state via some interface pattern.
3. Slow down the fling speed
Here you want to scale down the fling velocity. You will need to override the fling method inside your RecyclerView subclass:
#Override
public boolean fling(int velocityX, int velocityY)
{
velocityY *= FLING_SCALE_DOWN_FACTOR; // (between 0 for no fling, and 1 for normal fling, or more for faster fling).
return super.fling(velocityX, velocityY);
}
It's difficult for me to provide a more tailored solution, since you didn't post any of your code, or provide much information about your setup, but I hope this covers most bases and will help you find the best solution for you.
I just simplifying Answer how to use it to control smooth scroll.
Create Class
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
public class CustomRecyclerView extends RecyclerView {
Context context;
public CustomRecyclerView(Context context) {
super(context);
this.context = context;
}
public CustomRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public boolean fling(int velocityX, int velocityY) {
velocityY *= 0.7;
// velocityX *= 0.7; for Horizontal recycler view. comment velocityY line not require for Horizontal Mode.
return super.fling(velocityX, velocityY);
}
}
Adjust speed by Replacing 0.7 to your value.
Now use this class in your XML like this.
<<yourpackage>.CustomRecyclerView
android:id="#+id/my_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
Read RecyclerView in Java like.
CustomRecyclerView mRecyclerView;
mRecyclerView = (CustomRecyclerView) findViewById(R.id.my_recycler_view);
Simply implement smoothScrollToPosition() of your LinearLayoutManager:
LinearLayoutManager layoutManager = new LinearLayoutManager(this) {
#Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
LinearSmoothScroller smoothScroller = new LinearSmoothScroller(this) {
private static final float SPEED = 300f;// Change this value (default=25f)
#Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return SPEED / displayMetrics.densityDpi;
}
};
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}
};
use this for smooth scrolling
recyclerViewCart.isNestedScrollingEnabled = false

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