實現(xiàn)一個ScrollView

根據(jù)深入了解ScrollView的學習茧妒,所以就照貓畫虎的做了一個帶阻尼回彈的ScrollView桐筏,效果還挺不錯的九昧,實現(xiàn)的原理很簡單铸鹰。但是必須要理解OverScroller和VelocityTracker類的用法展姐。

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

OverScroller的用法

這個其實是一個滾動幫助類圾笨,功能和Scroller類似擂达,但是多了一個springBack()滑出邊界回彈的功能板鬓。如果像做回彈效果非得它出馬才好實現(xiàn)俭令。

OverScroller使用起來非常的簡單,如果想讓ScrollView滾動就調(diào)用startScroll()傳入相應(yīng)參數(shù)赫蛇,會把計算的結(jié)果回調(diào)給View的computeScroll()方法棍掐,這個方法中一般這么寫作煌。然后根據(jù)得到的Y或者X的值做相應(yīng)的邏輯 粟誓,例如滾動View 鹰服。

if (mScroller.computeScrollOffset()) {
           int curY = mScroller.getCurrY();
           int curX = mScroller.getCurrX();
           ...
           //根據(jù)得到的Y活著X的值做相應(yīng)的邏輯 套菜,例如滾動View 
       }

當手機離開后調(diào)用fling也會毀掉這個方法逗柴,在里面做相應(yīng)的邏輯處理即可戏溺。

VelocityTracker的用法

這個用法就更加單了旷祸,這個主要就是用來計算運動速率的,當手指離開后ScrollView還需要做一段慣性運動闰围,速率越大,fling的距離越遠涧狮。ScrollView滾動時者冤,手指離開后就可以調(diào)用OverScroller.fling()方法來讓其做fling滾動,而這個方法就需要velocityX和velocityY參數(shù)邢滑,正是由VelocityTracker得到的

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

down事件的時候調(diào)用 初始化VelocityTracker

    //OnEvent事件的時候調(diào)用 初始化它
    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;

說到這里其實怎么實現(xiàn)ScrollView其實已經(jīng)很簡單了摇予。還是說一下關(guān)鍵的代碼
在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按系數(shù)取值
                    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事件直接調(diào)用overScrollBy()蜕猫,這個方法是View的方法丹锹,可以幫我們計算滾動距離的楣黍,這個方法只是把值計算出來租漂,到底滾不滾動還是要看onOverScrolled()方法是怎么實現(xiàn)的。

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

我這里只是滾動并喚醒滾動條這樣就實現(xiàn)了手指觸摸的滾動

手指離開后的調(diào)用用fling

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

最終會回調(diào)computeScroll()

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

這里我有繼續(xù)調(diào)用overScrollBy()來幫我們計算滾動,然后同理會把計算結(jié)果傳給onOverScrolled()從而完成手指離開后的fling滾動蒜胖。

遇到的問題1

在computeScroll()中不要直接調(diào)用scrollTo(),筆者頁試過台谢,因為這個滑動的時候mScroller.getCurrY()會和mScroller.getFinalY()的值有偏差朋沮,造成ScrolView在fling動作即將完成后總會跳動纠亚。

遇到的問題2

如果不重寫任何方法就調(diào)用awakenScrollBars();是看不到任何滾動條的菜枷。重寫下面幾個方法來控制滾動條啤誊。用一張圖來說明一下


未標題-1.png
  • offset對應(yīng)computeVerticalScrollOffset方法,滑動的時候這里的返回值回根據(jù)offset變化
  • extent對應(yīng)computeVerticalScrollExtent方法牡昆,返回看見區(qū)域的高度丢烘,大白話就是ScrollView的高度。
  • computeVerticalScrollRange方法就是可以滑動的區(qū)域赢乓,這個一般需要計算得到牌芋。我們把黃色的當作LinerLayout的高度躺屁,那個可滑動的區(qū)域就等于 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;
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌轨域,老刑警劉巖干发,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異必峰,居然都是意外死亡吼蚁,警方通過查閱死者的電腦和手機肝匆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來粗仓,“玉大人借浊,你說我怎么就攤上這事蚂斤∈镎簦” “怎么了纽窟?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵森枪,是天一觀的道長县袱。 經(jīng)常有香客問我式散,道長,這世上最難降的妖魔是什么揍移? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮罕邀,結(jié)果婚禮上诉探,老公的妹妹穿的比我還像新娘。我一直安慰自己敬肚,他們只是感情好艳馒,可當我...
    茶點故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布第美。 她就那樣靜靜地躺著,像睡著了一般恶守。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上仔拟,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天,我揣著相機與錄音,去河邊找鬼挠乳。 笑死睡扬,一個胖子當著我的面吹牛卖怜,可吹牛的內(nèi)容都是我干的马靠。 我是一名探鬼主播甩鳄,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼锁孟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了甜熔?” 一聲冷哼從身側(cè)響起突倍,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤腔稀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后羽历,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體焊虏,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年诵闭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片澎嚣。...
    茶點故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡疏尿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出易桃,到底是詐尸還是另有隱情褥琐,我是刑警寧澤,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響浆兰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜驱富,卻給世界環(huán)境...
    茶點故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望匹舞。 院中可真熱鬧褐鸥,春花似錦、人聲如沸赐稽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽姊舵。三九已至晰绎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間括丁,已是汗流浹背荞下。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人尖昏。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓仰税,卻偏偏與公主長得像,于是被迫代替她去往敵國和親抽诉。 傳聞我的和親對象是個殘疾皇子陨簇,可洞房花燭夜當晚...
    茶點故事閱讀 45,851評論 2 361

推薦閱讀更多精彩內(nèi)容