实现一个ScrollView

根据深入了解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;
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容