Snappy scrolling in RecyclerView

I am trying to use the new RecyclerView class for a scenario where I want the component to snap to a specific element when scrolling (The old Android Gallery comes to mind as an example of such a list with a center-locked item).

This is the approach that I am taking thus far:

  • jar-Binding of ActionBarSherlock for Mono for Android
  • Square`s OkHttp. Download progress
  • Differentiating debug and production build of an Android app
  • PreferenceFragment background color
  • Popup over incoming-call screen
  • Android package naming best practices
  • I have an interface, ISnappyLayoutManager, which contains a method, getPositionForVelocity, which calculates at which position the view should end the scrolling given the initial fling velocity.

    public interface ISnappyLayoutManager {
        int getPositionForVelocity(int velocityX, int velocityY);  
    }
    

    Then I have a class, SnappyRecyclerView, which subclasses RecyclerView and overrides its fling() method in such a manner as to fling the view the exact right amount:

    public final class SnappyRecyclerView extends RecyclerView {
    
        /** other methods deleted **/
    
        @Override
        public boolean fling(int velocityX, int velocityY) {
            LayoutManager lm = getLayoutManager();
    
            if (lm instanceof ISnappyLayoutManager) {
                super.smoothScrollToPosition(((ISnappyLayoutManager) getLayoutManager())
                        .getPositionForVelocity(velocityX, velocityY));
            }
            return true;
        }
    }
    

    I am not very happy with this approach for several reasons. First of all, it seems counter to the philosophy of the ‘RecyclerView’ to have to subclass it to implement a certain type of scrolling. Second, if I want to just use the default LinearLayoutManager, this becomes somewhat complex as I have to mess around with its internals in order to understand its current scroll state and calculate out exactly where this scrolls to. Finally, this doesn’t even take care of all the possible scroll scenarios, as if you move the list and then pause and then lift a finger, no fling event occurs (the velocity is too low) and so the list remains in a halfway position. This can possibly be taken care of by adding an on scroll state listener to the RecyclerView, but that also feels very hacky.

    I feel like I must be missing something. Is there a better way to do this?

    Related posts:

    Deleting Android SMS programmatically
    onCreateOptionsMenu not being called on FragmentActivity when run on phone version
    Android API level annotation for Android libraries
    How to handle multiple markers on Google Maps with same location?
    How to integrate the metaio + Open CV for android application?
    Unable to load Android APK in ARC Welder
  • Linkedin Android SDK - Unable to connect to API (INVALID_REQUEST)
  • How to get height and width of a image used in android
  • Android: where are downloaded files saved?
  • Get list of places surrounding user's current location
  • FileObserver getting strange events
  • Converting iPhone/iPad apps onto Android
  • 13 Solutions collect form web for “Snappy scrolling in RecyclerView”

    I ended up coming up with something slightly different than the above. It’s not ideal, but it’s working acceptably well for me, and may be helpful to someone else. I won’t accept this answer in the hopes that someone else comes along with something better and less hacky (and it’s possible that I’m misunderstanding the RecyclerView implementation and missing some simple way of doing this, but in the meantime, this is good enough for government work!)

    The basics of the implementation are these: The scrolling in a RecyclerView is sort of split up between the RecyclerView and the LinearLayoutManager. There are two cases that I need to handle:

    1. The user flings the view. The default behavior is that the RecyclerView passes the fling to an internal Scroller which then performs the scrolling magic. This is problematic because then the RecyclerView usually settles in an unsnapped position. I solve this by overriding the RecyclerView fling() implementation and instead of flinging, smoothscroll the LinearLayoutManager to a position.
    2. The user lifts their finger with insufficient velocity to initiate a scroll. No fling occurs in this case. I want to detect this case in the event that the view is not in a snapped position. I do this by overriding the onTouchEvent method.

    The SnappyRecyclerView:

    public final class SnappyRecyclerView extends RecyclerView {
    
        public SnappyRecyclerView(Context context) {
            super(context);
        }
    
        public SnappyRecyclerView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public SnappyRecyclerView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }
    
        @Override
        public boolean fling(int velocityX, int velocityY) {
            final LayoutManager lm = getLayoutManager();        
    
          if (lm instanceof ISnappyLayoutManager) {
                super.smoothScrollToPosition(((ISnappyLayoutManager) getLayoutManager())
                        .getPositionForVelocity(velocityX, velocityY));
                return true;
            }
            return super.fling(velocityX, velocityY);
        }        
    
        @Override
        public boolean onTouchEvent(MotionEvent e) {
            // We want the parent to handle all touch events--there's a lot going on there, 
            // and there is no reason to overwrite that functionality--bad things will happen.
            final boolean ret = super.onTouchEvent(e);
            final LayoutManager lm = getLayoutManager();        
    
          if (lm instanceof ISnappyLayoutManager
                    && (e.getAction() == MotionEvent.ACTION_UP || 
                        e.getAction() == MotionEvent.ACTION_CANCEL)
                    && getScrollState() == SCROLL_STATE_IDLE) {
                // The layout manager is a SnappyLayoutManager, which means that the 
                // children should be snapped to a grid at the end of a drag or 
                // fling. The motion event is either a user lifting their finger or 
                // the cancellation of a motion events, so this is the time to take 
                // over the scrolling to perform our own functionality.
                // Finally, the scroll state is idle--meaning that the resultant 
                // velocity after the user's gesture was below the threshold, and 
                // no fling was performed, so the view may be in an unaligned state 
                // and will not be flung to a proper state.
                smoothScrollToPosition(((ISnappyLayoutManager) lm).getFixScrollPos());
            }        
    
          return ret;
        }
    }
    

    An interface for snappy layout managers:

    /**
     * An interface that LayoutManagers that should snap to grid should implement.
     */
    public interface ISnappyLayoutManager {        
    
        /**
         * @param velocityX
         * @param velocityY
         * @return the resultant position from a fling of the given velocity.
         */
        int getPositionForVelocity(int velocityX, int velocityY);        
    
        /**
         * @return the position this list must scroll to to fix a state where the 
         * views are not snapped to grid.
         */
        int getFixScrollPos();        
    
    }
    

    And here is an example of a LayoutManager that subclasses the LinearLayoutManager to result in a LayoutManager with smooth scrolling:

    public class SnappyLinearLayoutManager extends LinearLayoutManager implements ISnappyLayoutManager {
        // These variables are from android.widget.Scroller, which is used, via ScrollerCompat, by
        // Recycler View. The scrolling distance calculation logic originates from the same place. Want
        // to use their variables so as to approximate the look of normal Android scrolling.
        // Find the Scroller fling implementation in android.widget.Scroller.fling().
        private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)
        private static float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
        private static double FRICTION = 0.84;
    
        private double deceleration;
    
        public SnappyLinearLayoutManager(Context context) {
            super(context);
            calculateDeceleration(context);
        }
    
        public SnappyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
            super(context, orientation, reverseLayout);
            calculateDeceleration(context);
        }
    
        private void calculateDeceleration(Context context) {
            deceleration = SensorManager.GRAVITY_EARTH // g (m/s^2)
                    * 39.3700787 // inches per meter
                    // pixels per inch. 160 is the "default" dpi, i.e. one dip is one pixel on a 160 dpi
                    // screen
                    * context.getResources().getDisplayMetrics().density * 160.0f * FRICTION;
        }
    
        @Override
        public int getPositionForVelocity(int velocityX, int velocityY) {
            if (getChildCount() == 0) {
                return 0;
            }
            if (getOrientation() == HORIZONTAL) {
                return calcPosForVelocity(velocityX, getChildAt(0).getLeft(), getChildAt(0).getWidth(),
                        getPosition(getChildAt(0)));
            } else {
                return calcPosForVelocity(velocityY, getChildAt(0).getTop(), getChildAt(0).getHeight(),
                        getPosition(getChildAt(0)));
            }
        }
    
        private int calcPosForVelocity(int velocity, int scrollPos, int childSize, int currPos) {
            final double dist = getSplineFlingDistance(velocity);
    
            final double tempScroll = scrollPos + (velocity > 0 ? dist : -dist);
    
            if (velocity < 0) {
                // Not sure if I need to lower bound this here.
                return (int) Math.max(currPos + tempScroll / childSize, 0);
            } else {
                return (int) (currPos + (tempScroll / childSize) + 1);
            }
        }
    
        @Override
        public void smoothScrollToPosition(RecyclerView recyclerView, State state, int position) {
            final LinearSmoothScroller linearSmoothScroller =
                    new LinearSmoothScroller(recyclerView.getContext()) {
    
                        // I want a behavior where the scrolling always snaps to the beginning of 
                        // the list. Snapping to end is also trivial given the default implementation. 
                        // If you need a different behavior, you may need to override more
                        // of the LinearSmoothScrolling methods.
                        protected int getHorizontalSnapPreference() {
                            return SNAP_TO_START;
                        }
    
                        protected int getVerticalSnapPreference() {
                            return SNAP_TO_START;
                        }
    
                        @Override
                        public PointF computeScrollVectorForPosition(int targetPosition) {
                            return SnappyLinearLayoutManager.this
                                    .computeScrollVectorForPosition(targetPosition);
                        }
                    };
            linearSmoothScroller.setTargetPosition(position);
            startSmoothScroll(linearSmoothScroller);
        }
    
        private double getSplineFlingDistance(double velocity) {
            final double l = getSplineDeceleration(velocity);
            final double decelMinusOne = DECELERATION_RATE - 1.0;
            return ViewConfiguration.getScrollFriction() * deceleration
                    * Math.exp(DECELERATION_RATE / decelMinusOne * l);
        }
    
        private double getSplineDeceleration(double velocity) {
            return Math.log(INFLEXION * Math.abs(velocity)
                    / (ViewConfiguration.getScrollFriction() * deceleration));
        }
    
        /**
         * This implementation obviously doesn't take into account the direction of the 
         * that preceded it, but there is no easy way to get that information without more
         * hacking than I was willing to put into it.
         */
        @Override
        public int getFixScrollPos() {
            if (this.getChildCount() == 0) {
                return 0;
            }
    
            final View child = getChildAt(0);
            final int childPos = getPosition(child);
    
            if (getOrientation() == HORIZONTAL
                    && Math.abs(child.getLeft()) > child.getMeasuredWidth() / 2) {
                // Scrolled first view more than halfway offscreen
                return childPos + 1;
            } else if (getOrientation() == VERTICAL
                    && Math.abs(child.getTop()) > child.getMeasuredWidth() / 2) {
                // Scrolled first view more than halfway offscreen
                return childPos + 1;
            }
            return childPos;
        }
    
    }
    

    With LinearSnapHelper, this is now very easy.

    All you need to do is this:

    SnapHelper helper = new LinearSnapHelper();
    helper.attachToRecyclerView(recyclerView);
    

    It’s that simple! Note that LinearSnapHelper was added in the Support Library starting from version 24.2.0.

    Meaning you have to add this to your app module’s build.gradle

    compile "com.android.support:recyclerview-v7:24.2.0"
    

    I’ve managed to find a cleaner way to do this. @Catherine (OP) let me know if this can be improved or you feel is an improvement over yours 🙂

    Here’s the scroll listener I use.

    https://github.com/humblerookie/centerlockrecyclerview/

    I’ve omitted some minor assumptions here like for eg.

    1) Initial and final paddings: First and last items in the horizontal scroll need to have initial and final paddings respectively set so that the initial and final views are at center when scrolled to first and last respectively.For eg in the onBindViewHolder you could do something like this.

    @Override
    public void onBindViewHolder(ReviewHolder holder, int position) {
    holder.container.setPadding(0,0,0,0);//Resetpadding
         if(position==0){
    //Only one element
                if(mData.size()==1){
                    holder.container.setPadding(totalpaddinginit/2,0,totalpaddinginit/2,0);
                }
                else{
    //>1 elements assign only initpadding
                    holder.container.setPadding(totalpaddinginit,0,0,0);
                }
            }
            else
            if(position==mData.size()-1){
                holder.container.setPadding(0,0,totalpaddingfinal,0);
            } 
    }
    
     public class ReviewHolder extends RecyclerView.ViewHolder {
    
        protected TextView tvName;
        View container;
    
        public ReviewHolder(View itemView) {
            super(itemView);
            container=itemView;
            tvName= (TextView) itemView.findViewById(R.id.text);
        }
    }
    

    The logic is prettty generic and one can use it for a lot of other cases. My case the recycler view is horizontal and stretches the entire horizontal width without margins( basically recyclerview’s center X coordinate is the screen’s center)or uneven paddings.

    Incase anyone is facing issue kindly comment.

    I also needed a snappy recycler view. I want to let the recycler view item snap to the left of a column. It ended up with implementing a SnapScrollListener which I set on the recycler view. This is my code:

    SnapScrollListener:

    class SnapScrollListener extends RecyclerView.OnScrollListener {
    
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            if (RecyclerView.SCROLL_STATE_IDLE == newState) {
                final int scrollDistance = getScrollDistanceOfColumnClosestToLeft(mRecyclerView);
                if (scrollDistance != 0) {
                    mRecyclerView.smoothScrollBy(scrollDistance, 0);
                }
            }
        }
    
    }
    

    Calculation of snap:

    private int getScrollDistanceOfColumnClosestToLeft(final RecyclerView recyclerView) {
        final LinearLayoutManager manager = (LinearLayoutManager) recyclerView.getLayoutManager();
        final RecyclerView.ViewHolder firstVisibleColumnViewHolder = recyclerView.findViewHolderForAdapterPosition(manager.findFirstVisibleItemPosition());
        if (firstVisibleColumnViewHolder == null) {
            return 0;
        }
        final int columnWidth = firstVisibleColumnViewHolder.itemView.getMeasuredWidth();
        final int left = firstVisibleColumnViewHolder.itemView.getLeft();
        final int absoluteLeft = Math.abs(left);
        return absoluteLeft <= (columnWidth / 2) ? left : columnWidth - absoluteLeft;
    }
    

    If the first visible view is scrolled more than the half width out of the screen, the next visible column is snapping to the left.

    Setting the listener:

    mRecyclerView.addOnScrollListener(new SnapScrollListener());
    

    Here’s a simpler hack for smooth scrolling to a certain position on a fling event:

    @Override
    public boolean fling(int velocityX, int velocityY) {
    
        smoothScrollToPosition(position);
        return super.fling(0, 0);
    }
    

    Override the fling method with a call to smoothScrollToPosition(int position), where “int position” is the position of the view you want in the adapter. You will need to get the value of the position somehow, but that’s dependent on your needs and implementation.

    A very simple approach for achieving a snap-to-position behavior –

        recyclerView.setOnScrollListener(new OnScrollListener() {
            private boolean scrollingUp;
    
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                // Or use dx for horizontal scrolling
                scrollingUp = dy < 0;
            }
    
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                // Make sure scrolling has stopped before snapping
                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                    // layoutManager is the recyclerview's layout manager which you need to have reference in advance
                    int visiblePosition = scrollingUp ? layoutManager.findFirstVisibleItemPosition()
                            : layoutManager.findLastVisibleItemPosition();
                    int completelyVisiblePosition = scrollingUp ? layoutManager
                            .findFirstCompletelyVisibleItemPosition() : layoutManager
                            .findLastCompletelyVisibleItemPosition();
                    // Check if we need to snap
                    if (visiblePosition != completelyVisiblePosition) {
                        recyclerView.smoothScrollToPosition(visiblePosition);
                        return;
                    }
    
            }
        });
    

    The only small downside is that it will not snap backwards when you scroll less than half way of the partially visible cell – but if this doesn’t bother you than it’s a clean and simple solution.

    I have implemented a working solution for Horizontal orientation of RecyclerView, that just reads coordinates onTouchEvent, on first MOVE and on UP. On UP calculate the position we need to go to.

    public final class SnappyRecyclerView extends RecyclerView {
    
    private Point   mStartMovePoint = new Point( 0, 0 );
    private int     mStartMovePositionFirst = 0;
    private int     mStartMovePositionSecond = 0;
    
    public SnappyRecyclerView( Context context ) {
        super( context );
    }
    
    public SnappyRecyclerView( Context context, AttributeSet attrs ) {
        super( context, attrs );
    }
    
    public SnappyRecyclerView( Context context, AttributeSet attrs, int defStyle ) {
        super( context, attrs, defStyle );
    }
    
    
    @Override
    public boolean onTouchEvent( MotionEvent e ) {
    
        final boolean ret = super.onTouchEvent( e );
        final LayoutManager lm = getLayoutManager();
        View childView = lm.getChildAt( 0 );
        View childViewSecond = lm.getChildAt( 1 );
    
        if( ( e.getAction() & MotionEvent.ACTION_MASK ) == MotionEvent.ACTION_MOVE
                && mStartMovePoint.x == 0) {
    
            mStartMovePoint.x = (int)e.getX();
            mStartMovePoint.y = (int)e.getY();
            mStartMovePositionFirst = lm.getPosition( childView );
            if( childViewSecond != null )
                mStartMovePositionSecond = lm.getPosition( childViewSecond );
    
        }// if MotionEvent.ACTION_MOVE
    
        if( ( e.getAction() & MotionEvent.ACTION_MASK ) == MotionEvent.ACTION_UP ){
    
            int currentX = (int)e.getX();
            int width = childView.getWidth();
    
            int xMovement = currentX - mStartMovePoint.x;
            // move back will be positive value
            final boolean moveBack = xMovement > 0;
    
            int calculatedPosition = mStartMovePositionFirst;
            if( moveBack && mStartMovePositionSecond > 0 )
                calculatedPosition = mStartMovePositionSecond;
    
            if( Math.abs( xMovement ) > ( width / 3 )  )
                calculatedPosition += moveBack ? -1 : 1;
    
            if( calculatedPosition >= getAdapter().getItemCount() )
                calculatedPosition = getAdapter().getItemCount() -1;
    
            if( calculatedPosition < 0 || getAdapter().getItemCount() == 0 )
                calculatedPosition = 0;
    
            mStartMovePoint.x           = 0;
            mStartMovePoint.y           = 0;
            mStartMovePositionFirst     = 0;
            mStartMovePositionSecond    = 0;
    
            smoothScrollToPosition( calculatedPosition );
        }// if MotionEvent.ACTION_UP
    
        return ret;
    }}
    

    Works fine for me, let me know if something is wrong.

    After messing around with RecyclerView for a bit, this is what I came up with so far and what I’m using right now. It has one minor flaw, but I won’t spill the beans (yet) since you probably won’t notice.

    https://gist.github.com/lauw/fc84f7d04f8c54e56d56

    It only supports horizontal recyclerviews and snaps to the center and can also scale down views based on how far they are from the center. Use as a replacement of RecyclerView.

    Edit: 08/2016
    Made it into a repository:
    https://github.com/lauw/Android-SnappingRecyclerView
    I’ll just keep this up while working on a better implementation.

    To update humblerookie’s answer:

    This scroll listener is indeed effective for centerlocking
    https://github.com/humblerookie/centerlockrecyclerview/

    But here is a simpler way to add padding at the start and end of the recyclerview to center its elements:

    mRecycler.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                int childWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, CHILD_WIDTH_IN_DP, getResources().getDisplayMetrics());
                int offset = (mRecycler.getWidth() - childWidth) / 2;
    
                mRecycler.setPadding(offset, mRecycler.getPaddingTop(), offset, mRecycler.getPaddingBottom());
    
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    mRecycler.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                } else {
                    mRecycler.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                }
            }
        });
    

    If you need snapping support to start, top, end or bottom, use GravitySnapHelper(https://github.com/rubensousa/RecyclerViewSnap/blob/master/app/src/main/java/com/github/rubensousa/recyclerviewsnap/GravitySnapHelper.java).

    Snapping center:

    SnapHelper snapHelper = new LinearSnapHelper();
    snapHelper.attachToRecyclerView(recyclerView);
    

    Snapping start with GravitySnapHelper:

    startRecyclerView.setLayoutManager(new LinearLayoutManager(this,
                    LinearLayoutManager.HORIZONTAL, false));
    
    SnapHelper snapHelperStart = new GravitySnapHelper(Gravity.START);
    snapHelperStart.attachToRecyclerView(startRecyclerView);
    

    Snapping top with GravitySnapHelper:

    topRecyclerView.setLayoutManager(new LinearLayoutManager(this));
    
    SnapHelper snapHelperTop = new GravitySnapHelper(Gravity.TOP);
    snapHelperTop.attachToRecyclerView(topRecyclerView);
    

    And yet another cleaner option is to use custom LayoutManager,
    you can check
    https://github.com/apptik/multiview/tree/master/layoutmanagers

    It’s under development but working quite well.
    A snapshot is available:
    https://oss.sonatype.org/content/repositories/snapshots/io/apptik/multiview/layoutmanagers/

    Example:

    recyclerView.setLayoutManager(new SnapperLinearLayoutManager(getActivity()));
    

    I needed something a little bit different than all of the answers above.

    The main requirements were that:

    1. It works the same when user flings or just releases his finger.
    2. Uses the native scrolling mechanism to have the same “feeling” as a regular RecyclerView.
    3. When it stops, it starts smooth scrolling to the nearest snap point.
    4. No need to use custom LayoutManager or RecyclerView. Just a RecyclerView.OnScrollListener, which is then attached by recyclerView.addOnScrollListener(snapScrollListener). This way the code is much cleaner.

    And two very specific requirements which should be easy to change in the example below to fit to your case:

    1. Works horizontally.
    2. Snaps left edge of item to a specific point in the RecyclerView.

    This solution uses the native LinearSmoothScroller. The difference is that in the final step, when the “target view” is found it changes the calculation of offset so that it snaps to a specific position.

    public class SnapScrollListener extends RecyclerView.OnScrollListener {
    
    private static final float MILLIS_PER_PIXEL = 200f;
    
    /** The x coordinate of recycler view to which the items should be scrolled */
    private final int snapX;
    
    int prevState = RecyclerView.SCROLL_STATE_IDLE;
    int currentState = RecyclerView.SCROLL_STATE_IDLE;
    
    public SnapScrollListener(int snapX) {
        this.snapX = snapX;
    }
    
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
        currentState = newState;
        if(prevState != RecyclerView.SCROLL_STATE_IDLE && currentState == RecyclerView.SCROLL_STATE_IDLE ){
            performSnap(recyclerView);
        }
        prevState = currentState;
    
    }
    
    private void performSnap(RecyclerView recyclerView) {
        for( int i = 0 ;i < recyclerView.getChildCount() ; i ++ ){
            View child = recyclerView.getChildAt(i);
            final int left = child.getLeft();
            int right = child.getRight();
            int halfWidth = (right - left) / 2;
            if (left == snapX) return;
            if (left - halfWidth <= snapX && left + halfWidth >= snapX) { //check if child is over the snapX position
                int adapterPosition = recyclerView.getChildAdapterPosition(child);
                int dx = snapX - left;
                smoothScrollToPositionWithOffset(recyclerView, adapterPosition, dx);
                return;
            }
        }
    }
    
    private void smoothScrollToPositionWithOffset(RecyclerView recyclerView, int adapterPosition, final int dx) {
        final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if( layoutManager instanceof LinearLayoutManager) {
    
            LinearSmoothScroller scroller = new LinearSmoothScroller(recyclerView.getContext()) {
                @Override
                public PointF computeScrollVectorForPosition(int targetPosition) {
                    return ((LinearLayoutManager) layoutManager).computeScrollVectorForPosition(targetPosition);
                }
    
                @Override
                protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
                    final int dy = calculateDyToMakeVisible(targetView, getVerticalSnapPreference());
                    final int distance = (int) Math.sqrt(dx * dx + dy * dy);
                    final int time = calculateTimeForDeceleration(distance);
                    if (time > 0) {
                        action.update(-dx, -dy, time, mDecelerateInterpolator);
                    }
                }
    
                @Override
                protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
                    return MILLIS_PER_PIXEL / displayMetrics.densityDpi;
                }
            };
    
            scroller.setTargetPosition(adapterPosition);
            layoutManager.startSmoothScroll(scroller);
    
        }
    }
    
    Android Babe is a Google Android Fan, All about Android Phones, Android Wear, Android Dev and Android Games Apps and so on.