如何在ListView上获得滚动速度?

时间:2011-10-25 20:31:45

标签: java android

我有一个带有onScrollStateChanged和onScroll事件监听器的ListView。我希望能够获得ListView的滚动速度或某种方式来获取某些事件监听器中启动的滚动的finalX位置。我们的应用程序针对SDK版本7。

我需要测量或获得ListView滚动的速度。

6 个答案:

答案 0 :(得分:17)

分区第一个可见项目的时差差异不是一个好的解决方案。 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) {
    }
};

答案 1 :(得分:5)

试一试:

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) {
    }
}

答案 2 :(得分:2)

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

我认为接受的答案并不真正起作用,分辨率是超低的(即只有在项目滚动到整个屏幕后才能获得速度,但如果你有大项目视图怎么办?)。

   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 (i.e. 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;
   }

这只是我没有测试的伪代码,但我认为它应该可行。当滚动状态为STATE_IDLE时,您还必须重置上次时间和最高位置值。

答案 3 :(得分:1)

有一种简单的方法可以获得ListView的速度 (NOT Velocity) ,但它并不完美方式。

/** 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. 完美的方法是在回调onScroll时使用当前滚动 Velocity ,在AbsListView中,我们可以使用 FlingRunnable#mScroller.getCurrVelocity()获取速度但不幸的是,AbsListView并没有提供getCurrVelocity()的公共方法,所以如果我们想要获得此方法,有两种方法可以获得它
    • 反映这种方法,但我认为它在获取onScroll回调时存在性能问题
    • 复制AbsListView.java源并创建一个新类AbsListViewEx,在此类中提供getCurrVelocity()的公共方法,让新的ListViewEx从AbsListViewEx扩展,但它也有一些问题:1)它可能是一个复杂的事情2)ListViewEx可能有兼容性问题。但是,我认为这种方式比Reflect method更好。

答案 4 :(得分:0)

如果您需要知道当项目具有不同的大小或者您有一个很长的列表时,它需要知道它每秒滚动多少像素,这是代码。如果遗漏了某些项目,则计算和缓存每个项目大小将不起作用。顺便说一句,如果你选择那种方法,你应该缓存项目偏移而不是高度,这样你就不必进行这么多的计算。

覆盖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;
}

答案 5 :(得分:-1)

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