如何获得ListView的滚动速度?

我有一个ListView与onScrollStateChanged和onScroll事件侦听器。 我希望能够获得ListView的滚动速度,或者某种方式来获取某个Event侦听器中启动的滚动的finalX位置。 我们的应用程序目标SDK版本7。

我需要测量或获取ListView正在滚动的速度。

Solutions Collecting From Web of "如何获得ListView的滚动速度?"

第一个看得见的项目差异上的时差是不是一个好的解决scheme。 OnScroll监听器每隔一段固定的时间收到onScroll事件,所以在大多数情况下,除法的结果是“0”。

所以你可以尝试这样的事情:

 private OnScrollListener onScrollListener = new OnScrollListener() { private int previousFirstVisibleItem = 0; private long previousEventTime = 0; private double speed = 0; @Override public void onScroll(HtcAbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (previousFirstVisibleItem != firstVisibleItem){ long currTime = System.currentTimeMillis(); long timeToScrollOneElement = currTime - previousEventTime; speed = ((double)1/timeToScrollOneElement)*1000; previousFirstVisibleItem = firstVisibleItem; previousEventTime = currTime; Log.d("DBG", "Speed: " +speed + " elements/second"); } } @Override public void onScrollStateChanged(HtcAbsListView view, int scrollState) { } }; 

尝试一下:

 private class SpeedMeterOnScrollListener implements OnScrollListener { private long timeStamp; private int lastFirstVisibleItem; public SpeedMeterOnScrollListener() { timeStamp = System.currentTimeMillis(); lastFirstVisibleItem = 0; } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { long lastTime = System.currentTimeMillis(); //calculate speed by firstVisibleItem, lastFirstVisibleItem, timeStamp and lastTime timeStamp = lastTime; lastFirstVisibleItem = firstVisibleItem; } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } } 

所以这只是一些理论上的伪代码,但不是这样的工作?

我认为接受的答案并不是真的有效,分辨率是非常低的(例如,当一个项目从整个屏幕上滚动后,你只能得到一个速度,但如果你有大的项目视图?)。

  int mTrackingPosition = -1; int mLastTop; long mLastEventTime; @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // Get a new tracking position if our old one is no longer on screen // (this also includes the first time) if (first > mTrackingPosition || last < mTrackingPosition) { // We get the middle position here since that's likely to stay // on screen for a bit when scrolling up or down. mTrackingPosition = firstVisibleItem + visibleItemCount / 2; // Reset our times since we can't really get velocity from this // one measurement mLastTop = mLastEventTime = -1; // Handle the case that this happens more than once in a // row if that's even reasonably possible (ie they // scrolled rediculously fast) } // Get the measurements of the tracking view View v = view.getViewForPosition(mTrackingPosition); int top = v.getTop(); long time = System.currentTimeMillis(); // We can only get speed if we have a last recorded time if (mLastTop != -1 && mLastEventTime != -1) { // Velocity = distance / time float velocity = (mLastTop - top) / (time - mLastEventTime); // What do you want to do with the velocity? ... } // Update the last top and time so that we can track the difference mLastTop = top; mLastEventTime = time; } 

再次,这只是我没有testing过的伪代码,但我认为它应该工作。 当滚动状态为STATE_IDLE时,您还必须重置上次和顶部的位置值。

有一个简单的方法来获得 ListView 的速度 (不速度) ,但它不是一个完美的方式。

 /** The scroll speed threshold, it's a empiric value. */ private static final int LOW_SPEED_SCROLL_THRESHOLD = 3000; /** The offset, in pixels, by which the content of this view is scrolled vertically. */ private long mScrollY = 0; /** The last offset, in pixels, by which the content of this view is scrolled vertically. */ private long mLastScrollY = 0; /** The last scroll time, in millisecond */ private long mLastTime = 0; public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) { final View currentItemView = absListView.getChildAt(0); if (currentItemView != null) { // The first try to scroll, reset the last time flag if (mLastTime == 0) { mLastTime = System.currentTimeMillis(); return; } final int height = currentItemView.getHeight(); final int currentPos = firstVisibleItem; final int currentPosOffset = currentItemView.getTop(); // < 0 mScrollY = currentPos * height - currentPosOffset; final long deltaOffset = mScrollY - mLastScrollY; final long currTime = System.currentTimeMillis(); if (currTime == mLastTime) { return; } final long speed = deltaOffset * 1000 / (currTime - mLastTime); if (Math.abs(speed) < LOW_SPEED_SCROLL_THRESHOLD) { // low speed } else { // high speed } mLastTime = currTime; mLastScrollY = mScrollY; } else { resetScrollState(); } } private void resetScrollState() { mLastTime = 0; mScrollY = 0; mLastScrollY = 0; } 
  1. 完美的方法是在callbackonScroll时使用当前滚动速度 ,在AbsListView中,我们可以用FlingRunnable#mScroller.getCurrVelocity()获得速度,但不幸的是, AbsListView不提供getCurrVelocity()的公共方法,所以如果我们想要得到这个方法,有两种方法来获取它
    • 反映这种方法,但我认为它有onScrollcallback时的性能问题
    • 复制AbsListView.java源文件并创build一个新的AbsListViewEx类,在这个类中提供一个getCurrVelocity()的公共方法,让新的ListViewEx从AbsListViewEx扩展,但它也有一些问题:1)它可能是一个复杂的事情2) ListViewEx可能有兼容性问题。 但是,这种方式我认为比Reflect method更好。

这里是代码,如果你需要知道有多less像素每秒滚动,当项目有不同的大小,或者当你有一个很长的名单。 如果某些项目丢失,计算和caching每个项目大小将不起作用。 顺便说一句,如果你select的方法,你应该caching项目偏移而不是高度,所以你不必这么多的计算。

覆盖OnScrollListener:

 private HashMap<Integer, Integer> offsetMap = new HashMap<>(); private long prevScrollTime = System.currentTimeMillis(); @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { float traveled = 0; Set<Integer> oldKeys = new HashSet<>(offsetMap.keySet()); for (int i = 0; i < visibleItemCount; i++) { int pos = firstVisibleItem + i; int newOffset = view.getChildAt(i).getTop(); if (offsetMap.containsKey(pos) && traveled == 0) { traveled = Math.abs(newOffset - offsetMap.get(pos)); } offsetMap.put(pos, newOffset); oldKeys.remove(pos); } // remove those that are no longer in view for (Integer key : oldKeys) { offsetMap.remove(key); } long newTime = System.currentTimeMillis(); long t = newTime - prevScrollTime; if (t > 0) { float speed = traveled / t * 1000f; } else { // speed = 0 } prevScrollTime = newTime; } 

你可以使用android:fastScrollEnabled="true" ,不要忘了: YourListView.requestFocusFromTouch(); yourAdapter.notifyDataSetChanged(); 因为它(yourlistview)以快速的速度失去了焦点