Reputation: 1476
I have a ListView with onScrollStateChanged and onScroll event listeners. I want to be able to get the scroll speed of the ListView or some way to get the finalX location of the initiated scroll in some Event listener. Our app targets SDK version 7.
I need to measure or get the speed at which the ListView is scrolling.
Upvotes: 22
Views: 15080
Reputation: 485
Here's the code if you need to know how many pixels per second it's scrolling when the items have different sizes or when you have a very long list. Calculating and caching each item size would not work if some items are missed. BTW if you choose that method, you should cache the item offset instead of the height so you don't have to do so many calculation.
Override 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;
}
Upvotes: 0
Reputation: 1274
There is a simple way to get the speed (NOT Velocity) of the ListView, but it's not the perfect way.
/** 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;
}
onScroll
,in AbsListView, we can get the velocity with FlingRunnable#mScroller.getCurrVelocity(), but unfortunately,the AbsListView
doesn't provide the public method of getCurrVelocity()
, so if we want to get this method, there are two methods to get it
onScroll
callbackgetCurrVelocity()
in this class, let new ListViewEx extends from AbsListViewEx, but it also has some problem: 1) it maybe a complex thing 2) the ListViewEx may has compatibility problem. However, this way I think is better than Reflect method
.Upvotes: 1
Reputation: 2495
So this is just some theoretical pseudo-code, but wouldn't something like this work?
I think the accepted answer doesn't really work the resolution is super low (i.e. you only get a velocity after an item has been scrolled off the entire screen, but what if you have large item views?).
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;
}
Again this is just pseudo-code that I haven't tested, but I think it should work. You'll also have to reset the last time and top position values when the scroll state is STATE_IDLE
.
Upvotes: 2
Reputation: 1497
Division first visible items difference on time difference is not a good solution. OnScroll listener recieves onScroll event every fixed period of time, so in most cases the result of division will be "0".
So you can try something like this:
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) {
}
};
Upvotes: 17
Reputation: 277
you can use android:fastScrollEnabled="true"
in you
and don't forget to : YourListView.requestFocusFromTouch();
after yourAdapter.notifyDataSetChanged();
because it (yourlistview) lose the focus in fast speed
Upvotes: -1
Reputation: 1931
Try it:
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) {
}
}
Upvotes: 5