package com.marshalchen.common.ui.FloatingActionButtonWithListView; import android.view.View; import android.widget.AbsListView; import com.marshalchen.common.R; /** * Detects which direction list view was scrolled. * <p/> * Set {@link ScrollDirectionListener} to get callbacks * {@link ScrollDirectionListener#onScrollDown()} or * {@link ScrollDirectionListener#onScrollUp()} * * @author Vilius Kraujutis * @since 2014-10-09 01:20 */ public abstract class ScrollDirectionDetector implements AbsListView.OnScrollListener { private ScrollDirectionListener mScrollDirectionListener; private int mPreviousScrollY; private int mPreviousFirstVisibleItem; public int mLastChangeY; private AbsListView mListView; private int mMinSignificantScroll; @Override public void onScrollStateChanged(AbsListView view, int scrollState) { mMinSignificantScroll = view.getContext().getResources().getDimensionPixelOffset(R.dimen.fab_min_significant_scroll); } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { int newScrollY = estimateScrollY(); if (mScrollDirectionListener != null && isSameRow(firstVisibleItem) && 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 ScrollDirectionListener#onScrollUp()} or {@link 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 (mListView == null || mListView.getChildAt(0) == null) return 0; View topChild = mListView.getChildAt(0); return mListView.getFirstVisiblePosition() * topChild.getHeight() - topChild.getTop(); } public void setListView(AbsListView listView) { mListView = listView; } }