package org.qii.weiciyuan.support.lib; import android.content.Context; import android.util.AttributeSet; import android.view.animation.AnimationUtils; import android.widget.AbsListView; import java.util.ArrayList; import java.util.List; /** * User: qii * Date: 13-3-31 * modify to add * int currentItemCount = totalItemCount; * int v = currentItemCount - mLastItemCount; * mFirstVisiblePosition += v; * mLastVisiblePosition += v; * so, when notifyDataSetChanged adapter to show new item, the Velocity is also correct. * <p/> * and modify setOnScrollListener method, so that ListView can own more than just one * OnScrollListener */ public class VelocityListView extends AutoScrollListView { /** * A callback to be notified the velocity has changed. * * @author Cyril Mottier */ public interface OnVelocityListViewListener { void onVelocityChanged(int velocity); } public interface OnVelocityEqualZeroListener { void onZero(); } private static final long INVALID_TIME = -1; /** * This value is really necessary to avoid weird velocity values. Indeed, in * fly-wheel mode, onScroll is called twice per-frame which results in * having a delta divided by a value close to zero. onScroll is usually * being called 60 times per seconds (i.e. every 16ms) so 10ms is a good * threshold. */ private static final long MINIMUM_TIME_DELTA = 10L; private final ForwardingOnScrollListener mForwardingOnScrollListener = new ForwardingOnScrollListener(); private OnVelocityListViewListener mOnVelocityListViewListener; private OnVelocityEqualZeroListener onVelocityEqualZeroListener; private long mTime = INVALID_TIME; private int mVelocity; private int mFirstVisiblePosition; private int mFirstVisibleViewTop; private int mLastVisiblePosition; private int mLastVisibleViewTop; private int mLastItemCount; public static final int TOWARDS_BOTTOM = 0; public static final int TOWARDS_TOP = 1; private int towardsOrientation = TOWARDS_BOTTOM; public VelocityListView(Context context) { super(context); init(); } public VelocityListView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public VelocityListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { super.setOnScrollListener(mForwardingOnScrollListener); mForwardingOnScrollListener.selfListener = mOnScrollListener; } public void setOnVelocityEqualZeroListener(OnVelocityEqualZeroListener l) { onVelocityEqualZeroListener = l; } @Override public void setOnScrollListener(OnScrollListener l) { mForwardingOnScrollListener.clientListener.add(l); } public void setOnVelocityListener(OnVelocityListViewListener l) { mOnVelocityListViewListener = l; } /** * Return an approximative value of the ListView's current velocity on the * Y-axis. A negative value indicates the ListView is currently being * scrolled towards the bottom (i.e items are moving from bottom to top) * while a positive value indicates it is currently being scrolled towards * the top (i.e. items are moving from top to bottom). * * @return An approximative value of the ListView's velocity on the Y-axis */ public int getVelocity() { return mVelocity; } private void setVelocity(int velocity) { if (mVelocity != velocity) { mVelocity = velocity; if (mOnVelocityListViewListener != null) { mOnVelocityListViewListener.onVelocityChanged(velocity); } if (onVelocityEqualZeroListener != null && (mTime == INVALID_TIME)) { onVelocityEqualZeroListener.onZero(); } if (velocity < 0) { towardsOrientation = TOWARDS_BOTTOM; } else if (velocity > 0) { towardsOrientation = TOWARDS_TOP; } } } public int getTowardsOrientation() { return towardsOrientation; } /** * @author Cyril Mottier */ private static class ForwardingOnScrollListener implements OnScrollListener { private OnScrollListener selfListener; private List<OnScrollListener> clientListener = new ArrayList<OnScrollListener>(); @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (selfListener != null) { selfListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } for (OnScrollListener l : clientListener) { l.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (selfListener != null) { selfListener.onScrollStateChanged(view, scrollState); } for (OnScrollListener l : clientListener) { l.onScrollStateChanged(view, scrollState); } } } private OnScrollListener mOnScrollListener = new OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { switch (scrollState) { case SCROLL_STATE_IDLE: mTime = INVALID_TIME; setVelocity(0); break; default: break; } } @Override public void onScroll(AbsListView view, int firstVisiblePosition, int visibleItemCount, int totalItemCount) { if (visibleItemCount == 0) { return; } final long now = AnimationUtils.currentAnimationTimeMillis(); final int lastVisiblePosition = firstVisiblePosition + visibleItemCount - 1; //calc position,because ListView may has new item because adapter has something new int currentItemCount = totalItemCount; int v = currentItemCount - mLastItemCount; mFirstVisiblePosition += v; mLastVisiblePosition += v; if (mTime != INVALID_TIME) { final long delta = now - mTime; if (now - mTime > MINIMUM_TIME_DELTA) { int distance = 0; //@formatter:off if (mFirstVisiblePosition >= firstVisiblePosition && mFirstVisiblePosition <= lastVisiblePosition) { distance = getChildAt(mFirstVisiblePosition - firstVisiblePosition).getTop() - mFirstVisibleViewTop; } else if (mLastVisiblePosition >= firstVisiblePosition && mLastVisiblePosition <= lastVisiblePosition) { distance = getChildAt(mLastVisiblePosition - firstVisiblePosition).getTop() - mLastVisibleViewTop; //@formatter:on } else { // We're in a case were the item we were previously // referencing has moved out of the visible window. // Let's compute an approximative distance int heightSum = 0; for (int i = 0; i < visibleItemCount; i++) { heightSum += getChildAt(i).getHeight(); } distance = heightSum / visibleItemCount * (mFirstVisiblePosition - firstVisiblePosition); } setVelocity((int) (1000d * distance / delta)); } } mFirstVisiblePosition = firstVisiblePosition; mFirstVisibleViewTop = getChildAt(0).getTop(); mLastVisiblePosition = lastVisiblePosition; mLastVisibleViewTop = getChildAt(visibleItemCount - 1).getTop(); mLastItemCount = totalItemCount; mTime = now; } }; }