彻底解决监听Scrollview滑动暂停问题

项目中有个功能要监听Scrollview的滑动暂停状态,百度了一下,都是通过handler机制来比较getScrollY()值来实现,这种方式还是有bug的,在滑动中停止为撒手状态下,或者在底部,头部的时候有监听不到的情况。后来我就想着Scrollview内部有没有滑动停止的标志呢。阅读Scrollview源码之后发现还真有!!!

这个滑动事件肯定和onTouchEvent(MotionEvent ev)有关,首先从这个方法开始读源码

 @Override
    public boolean onTouchEvent(MotionEvent ev) {
        initVelocityTrackerIfNotExists();

        MotionEvent vtev = MotionEvent.obtain(ev);

        final int actionMasked = ev.getActionMasked();

        if (actionMasked == MotionEvent.ACTION_DOWN) {
            mNestedYOffset = 0;
        }
        vtev.offsetLocation(0, mNestedYOffset);

        switch (actionMasked) {
         ....//省略无关代码
            case MotionEvent.ACTION_UP:
                if (mIsBeingDragged) {
                    final VelocityTracker velocityTracker = mVelocityTracker;
                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                    //计算撒手时的速度
                    int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
                 //如果上面计算的速度大于设备能识别的最先速度,执行撒手后的惯性滑动
                    if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
                        //这里就是执行惯性滑动的方法,接下来就是阅读他的源码了
                        flingWithNestedDispatch(-initialVelocity);
                    } else if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0,
                            getScrollRange())) {
                        postInvalidateOnAnimation();
                    }

                    mActivePointerId = INVALID_POINTER;
                    endDrag();
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                if (mIsBeingDragged && getChildCount() > 0) {
                    if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
                        postInvalidateOnAnimation();
                    }
                    mActivePointerId = INVALID_POINTER;
                    endDrag();
                }
                break;
            case MotionEvent.ACTION_POINTER_DOWN: {
                final int index = ev.getActionIndex();
                mLastMotionY = (int) ev.getY(index);
                mActivePointerId = ev.getPointerId(index);
                break;
            }
            case MotionEvent.ACTION_POINTER_UP:
                onSecondaryPointerUp(ev);
                mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
                break;
        }

        if (mVelocityTracker != null) {
            mVelocityTracker.addMovement(vtev);
        }
        vtev.recycle();
        return true;
    }

通过上面源码的阅读,我们找到了撒手后惯性滑动的方法 flingWithNestedDispatch(-initialVelocity);,所以接下来就是分析这个方法

 private void flingWithNestedDispatch(int velocityY) {
        final boolean canFling = (mScrollY > 0 || velocityY > 0) &&
                (mScrollY < getScrollRange() || velocityY < 0);
        if (!dispatchNestedPreFling(0, velocityY)) {
            dispatchNestedFling(0, velocityY, canFling);
            //如果可以惯性滑动,那么就根据上面算出的撒手时的速度进行惯性滑动
            if (canFling) {
                fling(velocityY);
            }
        }
    }

接下来就是要阅读fling(velocityY);

 public void fling(int velocityY) {
        if (getChildCount() > 0) {
            int height = getHeight() - mPaddingBottom - mPaddingTop;
            int bottom = getChildAt(0).getHeight();
            //在这里滑动操作又交给了mScroller去执行
            mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
                    Math.max(0, bottom - height), 0, height/2);

            if (mFlingStrictSpan == null) {
                mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling");
            }

            postInvalidateOnAnimation();
        }
    }

接下来就是阅读 mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,Math.max(0, bottom - height), 0, height/2);就要揭开真相了

 public void fling(int startX, int startY, int velocityX, int velocityY,
            int minX, int maxX, int minY, int maxY, int overX, int overY) {
        //当时看到这个isFinished()方法时,就想着滑动停止标识就是你了
        if (mFlywheel && !isFinished()) {
            float oldVelocityX = mScrollerX.mCurrVelocity;
            float oldVelocityY = mScrollerY.mCurrVelocity;
            if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&
                    Math.signum(velocityY) == Math.signum(oldVelocityY)) {
                velocityX += oldVelocityX;
                velocityY += oldVelocityY;
            }
        }

        mMode = FLING_MODE;
        mScrollerX.fling(startX, velocityX, minX, maxX, overX);
        mScrollerY.fling(startY, velocityY, minY, maxY, overY);
    }

再来看看isFinished()

 /**
     *
     * Returns whether the scroller has finished scrolling.
     *
     * @return True if the scroller has finished scrolling, false otherwise.
     */
    public final boolean isFinished() {
        return mScrollerX.mFinished && mScrollerY.mFinished;
    }

看官方注解就知道了,这个方法就可以判断滑动停止了没有。

接下来就是怎么获取这个方法的返回值了。

public class OverScroller {

    public final boolean isFinished() {
        return mScrollerX.mFinished && mScrollerY.mFinished;
    }
}

isFinished()是OverScroller类里的公共方法,Scrollview里有一个OverScroller私有成员变量,并且没有暴露出isFinished()的调用,就是从Scrollview里没有直接获得滑动停止标志的方法,那么只能用反射来获取了。

public class MyScrollView extends ScrollView {

    public boolean isfinishScroll() {
        boolean isfinish=false;
        Class scrollview=ScrollView.class;
        try {
            //获取Scrollview里的OverScroller这个字段
            Field scrollField=scrollview.getDeclaredField("mScroller");
            scrollField.setAccessible(true);
            //获取到Scrollview里OverScroller的成员变量值
            Object scroller=scrollField.get(this);
            //获取scroller的类类型
            Class overscroller= scrollField.getType();
            //获取到OverScroller中isFinished()方法
            Method finishField=overscroller.getMethod("isFinished");
            finishField.setAccessible(true);
            //调用isFinished()方法
            isfinish= (boolean) finishField.invoke(scroller);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {

        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return isfinish;
    }
}

最后贴出源码吧


public class MyScrollView extends ScrollView {


    public MyScrollView(Context context) {
        super(context);
    }

    public MyScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);

    }


    @Override
    public void computeScroll() {
        super.computeScroll();
        Log.v("scrollveiwj",""+isfinishScroll());
    }

    public boolean isfinishScroll() {

        boolean isfinish=false;
        Class scrollview=ScrollView.class;
        try {
            Field scrollField=scrollview.getDeclaredField("mScroller");
            scrollField.setAccessible(true);
            Object scroller=scrollField.get(this);
            Class overscroller= scrollField.getType();
            Method finishField=overscroller.getMethod("isFinished");
            finishField.setAccessible(true);
            isfinish= (boolean) finishField.invoke(scroller);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {

        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        return isfinish;

    }


    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return super.onTouchEvent(ev);
    }
}

你可能感兴趣的:(android)