实现一个ScrollView

作者: shawn_yy | 来源:发表于2017-06-12 21:11 被阅读262次

    根据深入了解ScrollView的学习,所以就照猫画虎的做了一个带阻尼回弹的ScrollView,效果还挺不错的,实现的原理很简单。但是必须要理解OverScroller和VelocityTracker类的用法。

    项目地址:https://github.com/cyuanyang/ScrollView.git

    OverScroller的用法

    这个其实是一个滚动帮助类,功能和Scroller类似,但是多了一个springBack()滑出边界回弹的功能。如果像做回弹效果非得它出马才好实现。

    OverScroller使用起来非常的简单,如果想让ScrollView滚动就调用startScroll()传入相应参数,会把计算的结果回调给View的computeScroll()方法,这个方法中一般这么写。然后根据得到的Y或者X的值做相应的逻辑 ,例如滚动View 。

    if (mScroller.computeScrollOffset()) {
               int curY = mScroller.getCurrY();
               int curX = mScroller.getCurrX();
               ...
               //根据得到的Y活着X的值做相应的逻辑 ,例如滚动View 
           }
    

    当手机离开后调用fling也会毁掉这个方法,在里面做相应的逻辑处理即可。

    VelocityTracker的用法

    这个用法就更加单了,这个主要就是用来计算运动速率的,当手指离开后ScrollView还需要做一段惯性运动,速率越大,fling的距离越远。ScrollView滚动时,手指离开后就可以调用OverScroller.fling()方法来让其做fling滚动,而这个方法就需要velocityX和velocityY参数,正是由VelocityTracker得到的

     public void fling(int startX, int startY, int velocityX, int velocityY,
                int minX, int maxX, int minY, int maxY) 
    

    down事件的时候调用 初始化VelocityTracker

        //OnEvent事件的时候调用 初始化它
        private void initVelocityTrackerIfNotExists(MotionEvent event) {
            if (mVelocityTracker == null) {
                mVelocityTracker = VelocityTracker.obtain();
            }
            mVelocityTracker.addMovement(event);
        }
        private void recycleVelocityTracker() {
            if (mVelocityTracker != null) {
                mVelocityTracker.recycle();
                mVelocityTracker = null;
            }
        }
    

    在Up事件中得到速率,得到之后一定要销毁VelocityTracker

       case MotionEvent.ACTION_UP:
                   ... 其他代码
                   mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                   int velocityY = (int) mVelocityTracker.getYVelocity();
                   ... 其他代码
                   fling(-velocityY);
                   ... 其他代码
                   //销毁
                   recycleVelocityTracker();
                   break;
    

    说到这里其实怎么实现ScrollView其实已经很简单了。还是说一下关键的代码
    在onInterceptTouchEvent判断是不是拖动

    public boolean onInterceptTouchEvent(MotionEvent ev) {
            int action = ev.getAction();
            switch (action){
                case MotionEvent.ACTION_DOWN:
                    mDragging = false;
                    mLastY = ev.getY();
                    //计算点击的时候是不是滑动
                    mScroller.computeScrollOffset();
                    mDragging = !mScroller.isFinished();
                    break;
    
                case MotionEvent.ACTION_MOVE:
                    int deltaY = (int) Math.abs((int)ev.getY() - mLastY);
                    if (deltaY > mTouchSlop){
                        mDragging = true;
                    }
                    break;
    
                case MotionEvent.ACTION_UP:
    
                    break;
            }
            return mDragging;
        }
    

    在onTouchEvent处理

        @Override
        public boolean onTouchEvent(MotionEvent event) {
            initVelocityTrackerIfNotExists(event);
            int action = event.getActionMasked();
            float y = event.getY();
    
            switch (action)
            {
                case MotionEvent.ACTION_DOWN:
                    if (!mScroller.isFinished()){
                        mScroller.abortAnimation();
                    }
                    mLastY = (int) event.getY();
                    mActivePointerId = event.getPointerId(0);
                    return true;
                case MotionEvent.ACTION_MOVE:
                    int dy = (int) (mLastY - y);
                    if (!mDragging && Math.abs(dy) > mTouchSlop) {
                        mDragging = true;
                    }
                    if (mDragging) {
                        //如果滑动超出边界了 将dy按系数取值
                        if (getScrollY()<0 || getScrollY()>getScrollRange()){
                            dy /= mOverDyFactor;
                        }
                        overScrollBy(0 , dy, 0, getScrollY() , 0 , getScrollRange() , 0 , maxOverScrollY , true );
                    }
                    mLastY = y;
                    break;
                case MotionEvent.ACTION_CANCEL:
                    mDragging = false;
                    recycleVelocityTracker();
                    if (!mScroller.isFinished()) {
                        mScroller.abortAnimation();
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    mDragging = false;
                    mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                    int velocityY = (int) mVelocityTracker.getYVelocity(mActivePointerId);
                    Log.i(TAG , "velocityY="+velocityY + " mMinimumVelocity = " +mMinimumVelocity);
                    if (getScrollY()>0 && getScrollY()<getScrollRange() && Math.abs(velocityY) > mMinimumVelocity) {
                        fling(-velocityY);
                    }else if (mScroller.springBack(0 , getScrollY() , 0 , 0 , 0 , getScrollRange())){
                        postInvalidateOnAnimation();
                    }
                    recycleVelocityTracker();
                    mActivePointerId = -1;
                    break;
            }
    
            return super.onTouchEvent(event);
        }
    

    onTouchEvent当中,当为move事件直接调用overScrollBy(),这个方法是View的方法,可以帮我们计算滚动距离的,这个方法只是把值计算出来,到底滚不滚动还是要看onOverScrolled()方法是怎么实现的。

        @Override
        protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
            scrollTo(scrollX , scrollY);
            awakenScrollBars();
        }
    

    我这里只是滚动并唤醒滚动条这样就实现了手指触摸的滚动

    手指离开后的调用用fling

        public void fling(int velocityY) {
            mScroller.fling(0, getScrollY() , 0, velocityY, 0, 0, 0, getScrollRange() , 0 , maxOverScrollY);
        }
    

    最终会回调computeScroll()

       @Override
        public void computeScroll() {
            if (mScroller.computeScrollOffset()) {
                overScrollBy(0 , mScroller.getCurrY()-getScrollY() , 0 , getScrollY() , 0 , getScrollRange() , 0 , maxOverScrollY , false);
                if (!awakenScrollBars()) {
                    postInvalidateOnAnimation();
                }
            }
        }
    

    这里我有继续调用overScrollBy()来帮我们计算滚动,然后同理会把计算结果传给onOverScrolled()从而完成手指离开后的fling滚动。

    遇到的问题1

    在computeScroll()中不要直接调用scrollTo(),笔者页试过,因为这个滑动的时候mScroller.getCurrY()会和mScroller.getFinalY()的值有偏差,造成ScrolView在fling动作即将完成后总会跳动。

    遇到的问题2

    如果不重写任何方法就调用awakenScrollBars();是看不到任何滚动条的。重写下面几个方法来控制滚动条。用一张图来说明一下


    未标题-1.png
    • offset对应computeVerticalScrollOffset方法,滑动的时候这里的返回值回根据offset变化
    • extent对应computeVerticalScrollExtent方法,返回看见区域的高度,大白话就是ScrollView的高度。
    • computeVerticalScrollRange方法就是可以滑动的区域,这个一般需要计算得到。我们把黄色的当作LinerLayout的高度,那个可滑动的区域就等于 LinerLayout的高度-ScrollView的高度
        @Override
        protected int computeVerticalScrollExtent() {
            return getHeight();
        }
    
        @Override
        protected int computeVerticalScrollOffset() {
            return Math.max(0, super.computeVerticalScrollOffset());
        }
    
        @Override
        protected int computeVerticalScrollRange() {
            final int count = getChildCount();
            final int contentHeight = getHeight() - 0 - 0;
            if (count == 0) {
                return contentHeight;
            }
            int scrollRange = getChildAt(0).getBottom();
            final int scrollY = getScrollY();
            final int overScrollBottom = Math.max(0, scrollRange - contentHeight);
            if (scrollY < 0) {
                scrollRange -= scrollY;
            } else if (scrollY > overScrollBottom) {
                scrollRange += scrollY - overScrollBottom;
            }
            return scrollRange;
        }
    

    相关文章

      网友评论

        本文标题:实现一个ScrollView

        本文链接:https://www.haomeiwen.com/subject/orblqxtx.html