修改ListView的smoothScrollToPosition过渡

Modifying ListView's smoothScrollToPosition transition

ListView 调用 smoothScrollToPosition 时的默认行为,它以线速度移动到指定位置。

深入研究 ListView 和 AbsListView 的代码,我可以看到发生此行为是因为 AbsListView 使用 PositionScroller 对象(实现 AbsPositionScroller),而该对象又使用 FlingRunnable 对象,在该对象上使用 linear = true 调用方法 startScroll(这最终让它的 OverScroller 对象使用 LinearInterpolator)。

我想修改此行为,并让它使用例如 OverScroller class 默认使用的 Scroller.ViscousFluidInterpolator class,但我找不到方法去做。

我看到 AbsListView 定义了一个 AbsPosScroller 接口(他自己用 PositionScroller class 实现),我可以尝试用我自己的 class 实现它以使用 ViscousFluidInterpolator,但出于某种原因,此接口对包 android.widget...

是私有的

我是不是遗漏了什么,或者它看起来是不是以某种方式编写的,以防止它具有像定制那样的行为?他们为什么要首先编写 AbsPosScroller 接口?

关于如何获得我想要的行为而不必从头开始编写整个 ListView class 的任何线索?

虽然我仍然不知道为什么他们会以一种无法轻松定制其行为的方式来编写这些组件,而本来可以很容易做到这一点,但我想出了一个替代实现smoothScrollToPosition(下面代码中的 awesomeScrollToPosition)满足我的需要。

此解决方案使用 OverScroller 对象(除非指定其他对象,否则在内部使用 ViscousInterpolator)来提供我正在寻找的效果,用于滚动到 内的元素可见页面(实现跨页面滚动的解决方案比较复杂,但这适用于我需要解决的问题)。

我基本上实现了一个我自己的 ListView subclass (MyListView) 专用的 Runnable class 来处理滚动动画,重新发布到 UI 线程只要动画需要 运行,在每一帧中使用 scrollingListBy(虽然此方法仅在 KitKat [19] 之后可用)。

public class MyListView extends ListView {

    private MyScroller mScroller;

    /* MyListView constructors here */

    public void awesomeScrollToPosition(int position, int duration) {

        if (getChildCount() == 0) {
            // Can't scroll without children (visible list items)
            return;
        }

        if (mScroller == null) {
            mScroller = new MyScroller();
        }

        if (mScroller.isRunning()) {
            mScroller.stop();
        }

        int firstPos = getFirstVisiblePosition();
        int lastPos = getLastVisiblePosition();

        if (!(firstPos <= position && position <= lastPos)) {
            // Can't scroll to an item outside of the visible range this easily
            return;
        }

        int targetPosition = position - firstPos;
        int targetTop = getChildAt(targetPosition).getTop();

        mScroller.start(targetTop, duration);
    }

    private class MyScroller implements Runnable {

        OverScroller mScroller;

        boolean mRunning;
        int mLastY;

        MyScroller() {
            mScroller = new OverScroller(getContext());
            mRunning = false;
        }

        void start(int y, int duration) {

            // start scrolling
            mLastY = 0;
            mScroller.startScroll(0, 0, 0, y, duration);

            mRunning = true;
            postOnAnimation(this);
        }

        boolean isRunning() {
           return mRunning;
        }

        @Override
        public void run() {

            boolean more = mScroller.computeScrollOffset();
            final int currentY = mScroller.getCurrY();

            // actual scrolling
            scrollListBy(currentY - mLastY);

            if (more) {
                mLastY = currentY;

                // schedule next run
                postOnAnimation(this);
            } else {
                stop();
            }
        }

        public void stop() {

            mRunning = false;
            removeCallbacks(this);
        }
    }
}