package com.melnykov.fab;
import com.afollestad.materialdialogs.R;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
/**
* Detects which direction list view was scrolled.
* <p/>
* Set {@link ScrollDirectionListener} to get callbacks
* {@link ScrollDirectionListener#onScrollDown()} or
* {@link ScrollDirectionListener#onScrollUp()}
*
* @author Aidan Follestad
*/
public abstract class ScrollDirectionRecyclerViewDetector extends RecyclerView.OnScrollListener {
private ScrollDirectionListener mScrollDirectionListener;
private int mPreviousScrollY;
private int mPreviousFirstVisibleItem;
public int mLastChangeY;
private RecyclerView mRecyclerView;
private int mMinSignificantScroll;
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
mMinSignificantScroll = recyclerView.getContext().getResources().getDimensionPixelOffset(R.dimen.fab_min_significant_scroll);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int newScrollY = estimateScrollY();
if (mScrollDirectionListener != null && isSameRow(getFirstVisibleItem()) && isSignificantDelta(newScrollY)) {
if (isScrollUp(newScrollY)) {
mScrollDirectionListener.onScrollUp();
} else {
mScrollDirectionListener.onScrollDown();
}
}
}
public ScrollDirectionListener getScrollDirectionListener() {
return mScrollDirectionListener;
}
public void setScrollDirectionListener(ScrollDirectionListener mScrollDirectionListener) {
this.mScrollDirectionListener = mScrollDirectionListener;
}
/**
* @return true if scrolled up or false otherwise
* @see #isSignificantDelta(int) which ensures, that events are not fired it there was no scrolling
*/
private boolean isScrollUp(int newScrollY) {
boolean scrollUp = newScrollY > mPreviousScrollY;
mPreviousScrollY = newScrollY;
return scrollUp;
}
/**
* Make sure wrong direction method is not called when stopping scrolling
* and finger moved a little to opposite direction.
*
* @see #isScrollUp(int)
*/
private boolean isSignificantDelta(int newScrollY) {
boolean isSignificantDelta = Math.abs(mLastChangeY - newScrollY) > mMinSignificantScroll;
if (isSignificantDelta)
mLastChangeY = newScrollY;
return isSignificantDelta;
}
/**
* <code>newScrollY</code> position might not be correct if:
* <ul>
* <li><code>firstVisibleItem</code> is different than <code>mPreviousFirstVisibleItem</code></li>
* <li>list has rows of different height</li>
* </ul>
* <p/>
* It's necessary to track if row did not change, so events
* {@link com.melnykov.fab.ScrollDirectionListener#onScrollUp()} or {@link com.melnykov.fab.ScrollDirectionListener#onScrollDown()} could be fired with confidence
*
* @see #estimateScrollY()
*/
private boolean isSameRow(int firstVisibleItem) {
boolean rowsChanged = firstVisibleItem == mPreviousFirstVisibleItem;
mPreviousFirstVisibleItem = firstVisibleItem;
return rowsChanged;
}
/**
* Will be incorrect if rows has changed and if list has rows of different heights
* <p/>
* So when measuring scroll direction, it's necessary to ignore this value
* if first visible row is different than previously calculated.
*
* @deprecated because it should be used with caution
*/
private int estimateScrollY() {
if (mRecyclerView == null || mRecyclerView.getChildAt(0) == null) return 0;
View topChild = mRecyclerView.getChildAt(0);
return getFirstVisibleItem() * topChild.getHeight() - topChild.getTop();
}
private int getFirstVisibleItem() {
RecyclerView.LayoutManager mLayoutManager = mRecyclerView.getLayoutManager();
if (mLayoutManager == null)
throw new IllegalStateException("Your RecyclerView does not have a LayoutManager.");
if (mLayoutManager instanceof LinearLayoutManager) {
return ((LinearLayoutManager) mLayoutManager).findFirstVisibleItemPosition();
} else {
throw new RuntimeException("Currently only LinearLayoutManager is supported for the RecyclerView.");
}
}
public void setRecyclerView(RecyclerView recyclerView) {
mRecyclerView = recyclerView;
}
}