ViewPager源碼分析(2):滑動及沖突處理

我的CSDN博客同步發(fā)布:ViewPager源碼分析(2):滑動及沖突處理

轉(zhuǎn)載請注明出處:【huachao1001的簡書:http://www.reibang.com/users/0a7e42698e4b/latest_articles】

上一篇介紹了ViewPageronMeasureonLayout兩個方法,這是自定義View最基本的兩個函數(shù)硝逢。但是我們的ViewPager有個需求就是滑動姨拥,接下來我們一起去學(xué)習(xí)ViewPager在滑動方面做了哪些工作,以及ViewPager如何處理與子View之間的滑動沖突渠鸽。由于ViewPager的子View有Decor View還有普通的子View叫乌,而本篇文章講的主要是普通子View,因此徽缚,不再去刻意區(qū)分憨奸,以下所說的子View不包括DecorView。

1 Scroller典型用法

我們知道猎拨,Android內(nèi)置了Scroller對象膀藐,用于實現(xiàn)漸近式的滑動屠阻。假設(shè)我們自定義一個函數(shù)smoothScrollTo(int destX,int destY),用于讓ViewPager漸近式的滑動到(destX,destY)這個坐標位置额各,那么使用Scroller實現(xiàn)步驟一般如下:

  1. 創(chuàng)建Scroller對象:Scroller scroller=new Scroller(context);
  2. 重寫computeScroll()方法
  3. 最后国觉,在我們的smoothScrollTo方法中調(diào)用startScroll方法

參考如下代碼:

@Override
public void computeScroll(){
    if(scroller.computeScrollOffset()){
        scrollTo(scroller.getCurrX(),scroller.getCurrY());
        postInvalidate();
    }
}  
public void smoothScrollTo(int destX,int destY){
    int scrollX=getScrollX();
    int deltaX=destX-scrollX;
    scroller.startScroll(scrollX,0,deltaX,0,1000);
}

以上的smoothScrollTo實現(xiàn)的是x方向的平滑,其中startScroll函數(shù)的形參分別表示:起始位置的x坐標虾啦、起始位置的y坐標麻诀、x方向要移動的距離、y方向上要移動的距離以及整個滑動過程完成所需的時間傲醉。

2 ViewPager滑動

2.1 ViewPager定義Scroller

參照我們上一節(jié)提到的Scroller典型用法蝇闭,我們進入到ViewPager源碼。我們在ViewPager的initViewPager方法中找到:

void initViewPager() { 
    //····
    final Context context = getContext();
    mScroller = new Scroller(context, sInterpolator);
    //····
}

它跟我們上一節(jié)使用到的Scroller構(gòu)造器不同硬毕,他選擇使用2個形參的構(gòu)造器呻引。其實,第二個形參就是插值器(interpolator)吐咳,對插值器不熟悉的童鞋可以去搜索一下動畫插值器相關(guān)內(nèi)容逻悠。其實這個插值器就是根據(jù)不同的時間控制滑動的速度,就像高中物理中的物體變速運動韭脊。我們繼續(xù)看看ViewPager中自定義的插值器sInterpolator童谒,從變量名稱中以s開頭,就知道sInterpolator是個static屬性:

private static final Interpolator sInterpolator = new Interpolator() {
   public float getInterpolation(float t) {
       t -= 1.0f;
       return t * t * t * t * t + 1.0f;
   }
};

Interpolator是一個接口沪羔,它繼承自TimeInterpolator這個接口饥伊,而Interpolator沒有添加新的抽象方法,TimeInterpolator只有一個抽象方法:float getInterpolation(float input);其中蔫饰,input形參是取值范圍為0到1琅豆,表示當前的動畫時間點,0表示動畫開始死嗦,1表示動畫結(jié)束趋距。返回值表示移動到目標位置的比值粒氧,如果大于1越除,則表示超出了最大位置,小于0表示比最小位置還要小外盯。怎么理解呢摘盆?舉個例子,假設(shè)我們要實現(xiàn)變速動畫饱苟,我們要持續(xù)的時間是[0,1000]孩擂,要滑動的距離是[0,100],那么假設(shè)當前時間是200箱熬,則傳入到getInterpolation的形參就是200/1000=0.2类垦,表示時間過了0.2狈邑,具體的返回值可以根據(jù)你的變速需求計算,假設(shè)你的返回值是0.8蚤认,那么表示當前位置要處于100 * 0.8=80這個位置米苹。如果你的返回值是1.8 ,那么肯定就是超出100了:100*1.8=180。

2.2 ViewPager重寫computeScroll()方法

ViewPager實現(xiàn)的功能已經(jīng)兼容性都是比較健全的砰琢,所有computeScroll()不會像我們所寫的那么簡單蘸嘶,我們一起"膜拜"一下官方代碼吧:

@Override
public void computeScroll() {
//1.mIsScrollStarted標記當前在滑動
mIsScrollStarted = true;
//2.確保mScroller還沒有結(jié)束計算滑動位置
if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
    //3.保存當前所處的位置oldX,oldY
    int oldX = getScrollX();
    int oldY = getScrollY();
    //4.取出由mScroller計算出來的位置
    int x = mScroller.getCurrX();
    int y = mScroller.getCurrY();
    //5.只要x和y方向有一個發(fā)生了變化,就去滾動
    if (oldX != x || oldY != y) {
        //6.滑到mScroller計算出來的新位置
        scrollTo(x, y);
        //7.調(diào)用pageScrolled陪汽,只有當ViewPager里面沒有子View才會返回false
        if (!pageScrolled(x)) {
            //8.結(jié)束動畫训唱,并使得當前位置處于最終的位置
            mScroller.abortAnimation();
            //9.沒有子View,說明x方向無需滑動挚冤,再次確保y方向滑動
            scrollTo(0, y);
        }
    }

    // 10.不斷的postInvalidate,使得不斷重繪况增,達到動畫效果
    ViewCompat.postInvalidateOnAnimation(this);
    return;
}
//11.做一些滑動結(jié)束后的相關(guān)操作
// 注意到,上面的if里面有個return训挡,也就是說巡通,
// 只要是在滑動,就不會執(zhí)行到下面的代碼舍哄,
// 反之宴凉,執(zhí)行到下面代碼就說明已經(jīng)滑動結(jié)束 
completeScroll(true);
}

computeScroll函數(shù)里面大部分代碼比較清晰,只有兩個函數(shù)表悬,需要我們進去深究:pageScrolled以及completeScroll弥锄。

2.2.1 pageScrolled

先看看pageScrolled函數(shù),這個函數(shù)主要的作用是回調(diào)onPageScrolled蟆沫,雖然做了很多計算籽暇,但這些計算的結(jié)果最終是為了作為形參傳給onPageScrolled,看看他的源碼:

private boolean pageScrolled(int xpos) {
//1.mItems是ArrayList類型饭庞,它保存的是每個子View的抽象描述類ItemInfo
//如果沒有子View
if (mItems.size() == 0) {
    //2.先認為沒有調(diào)用父類
    //mCalledSuper作用是:如果子類重寫了onPageScrolled戒悠,
    // 那么子類的實現(xiàn)必須要先調(diào)用父類ViewPager的onPageScrolled
    //為了確保子類的實現(xiàn)中先調(diào)用了父類ViewPager的onPageScrolled,定義了mCalledSuper
    //并且在ViewPager類中的onPageScrolled將mCalledSuper設(shè)置為了true舟山,用于判斷子類有沒有調(diào)用绸狐。
    mCalledSuper = false;
    //3.調(diào)用onPageScrolled,如果子類重寫了該方法累盗,調(diào)用的則是子類的onPageScrolled
    onPageScrolled(0, 0, 0);
    //4.如果沒有執(zhí)行ViewPager的onPageScrolled,拋出異常
    if (!mCalledSuper) {
        throw new IllegalStateException(
                "onPageScrolled did not call superclass implementation");
    }
    //5.如果沒有子View寒矿,返回false
    return false;
}
//6.根據(jù)當前滑動的位置,得到當前顯示的子View的抽象描述類ItemInfo
//只要存在子View若债,得到的ItemInfo對象肯定不為null
final ItemInfo ii = infoForCurrentScrollPosition();
//7.獲取顯示區(qū)域的寬度
final int width = getClientWidth();
//8.加上外邊距后的寬度
final int widthWithMargin = width + mPageMargin;
final float marginOffset = (float) mPageMargin / width;
//保存當前是第幾個頁面(即第幾個子View)
final int currentPage = ii.position;
//計算當前頁面的偏移量符相,取值為[0,1),如果pageOffset不等于0,則下一個頁面可見
final float pageOffset = (((float) xpos / width) - ii.offset) /
        (ii.widthFactor + marginOffset);
//當前頁面移動的像素點個數(shù)
final int offsetPixels = (int) (pageOffset * widthWithMargin);

//以下作用與2蠢琳、3啊终、4類似
mCalledSuper = false;
onPageScrolled(currentPage, pageOffset, offsetPixels);
if (!mCalledSuper) {
    throw new IllegalStateException(
            "onPageScrolled did not call superclass implementation");
}
return true;
}

我們定位到第6個注釋镜豹,我提到infoForCurrentScrollPosition函數(shù)是據(jù)當前滑動的位置,得到當前顯示的子View的抽象描述類ItemInfo蓝牲,如果當前滑動位置顯示的恰好是一個完整的頁面逛艰,這個頁面的前一個頁面和后一個頁面都沒有顯示,那么很容易理解搞旭,返回的就是這個頁面散怖。可是如果當前顯示區(qū)域是同時顯示2個頁面(兩個頁面都顯示一部分出現(xiàn)在顯示區(qū)域)肄渗,那這個函數(shù)應(yīng)該返回哪一個頁面呢镇眷?從infoForCurrentScrollPosition源碼看出每次是返回左邊的頁面,如下圖所示:

根據(jù)滑動位置返回當前的Page

換句話說翎嫡,只會是存在當前頁面與下一個頁面同時出現(xiàn)在顯示區(qū)域欠动,不可能是當前頁面與上一個頁面同時出現(xiàn)。關(guān)于infoForCurrentScrollPosition的具體實現(xiàn)惑申,我們不要去關(guān)心具伍,我們只要知道它幫我們實現(xiàn)了什么功能,如果對其感興趣可以去看源碼圈驼。

2.2.2 onPageScrolled

上面我們知道人芽,pageScrolled函數(shù)是為了調(diào)用onPageScrolled做前期計算,并將計算結(jié)果作為onPageScrolled的形參绩脆,最終是為了回調(diào)onPageScrolled函數(shù)萤厅,那么我們看看onPageScrolled函數(shù)到底是干了啥~,從函數(shù)名看的出來靴迫,它是一個回調(diào)函數(shù)惕味,那么是什么情況下回調(diào)呢?其實玉锌,在我們手指滑動或者是通過代碼直接滑動到指定位置過程中名挥,會使得一些頁面滑動,如果我們想要在每個頁面在顯示區(qū)域滑動過程中實現(xiàn)某些效果主守,可以重寫這個函數(shù)禀倔,當然了,我們前面分析pageScrolled函數(shù)時就提到丸逸,重寫onPageScrolled時蹋艺,必須先調(diào)用super.onPageScrolled(position, offset, offsetPixels)剃袍,我們的ViewPager在滑動過程中黄刚,會不斷回調(diào)onPageScrolled函數(shù),這個“不斷”是從這里體現(xiàn):computeScroll—>onPageScrolled->onPageScrolled民效°疚滑動過程不斷調(diào)用computeScroll涛救,而computeScroll調(diào)用onPageScrolledonPageScrolled又調(diào)用onPageScrolled业扒。好了检吆,我們?nèi)タ纯?code>onPageScrolled吧~首先看看三個參數(shù):

  1. int position,表示當前是第幾個頁面
  2. float offset表示當前頁面移動的距離程储,其實就是個相對實際寬度比例值蹭沛,取值為[0,1)。0表示整個頁面在顯示區(qū)域章鲤,1表示整個頁面已經(jīng)完全左移出顯示區(qū)域摊灭。
  3. int offsetPixels , 表示當前頁面左移的像素個數(shù)。

我們已經(jīng)了解形參的含義败徊,接下來看看源碼:

@CallSuper
protected void onPageScrolled(int position, float offset, int offsetPixels) {
    // Offset any decor views if needed - keep them on-screen at all times.
    //1.如果有Decor View帚呼,則需要使得它們時刻顯示在屏幕中,不移出屏幕
    if (mDecorChildCount > 0) {
        //根據(jù)Gravity將Decor View擺放到指定位置皱蹦,注釋略煤杀,可以參考上一篇文章
        //代碼略···
    }
    //2.分發(fā)頁面滾動事件
    dispatchOnPageScrolled(position, offset, offsetPixels);
    //3.如果mPageTransformer不為null,則不斷去調(diào)用mPageTransformer的transformPage函數(shù)
    if (mPageTransformer != null) {
        final int scrollX = getScrollX();
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            //只針對頁面進行處理
            if (lp.isDecor) continue;
            //計算child位置
            final float transformPos = (float) (child.getLeft() - scrollX) / getClientWidth();
            //調(diào)用transformPage
            mPageTransformer.transformPage(child, transformPos);
        }
    }
    //標記ViewPager的onPageScrolled函數(shù)執(zhí)行過
    mCalledSuper = true;
}

從源碼上我們知道沪哺,onPageScrolled做了3件事沈自,首先把Decor View固定在顯示區(qū)域,其次辜妓,將滾動事件進行分發(fā)酥泛,即dispatchOnPageScrolled函數(shù),dispatchOnPageScrolled函數(shù)內(nèi)部就是調(diào)用OnPageChangeListeneronPageScrolled函數(shù)嫌拣,我們添加的監(jiān)聽器就是此時被回調(diào)onPageScrolled函數(shù)柔袁,dispatchOnPageScrolled函數(shù)代碼比較簡單,不去追究异逐。最后捶索,就是判斷是否設(shè)置了mPageTransformer,如果設(shè)置了灰瞻,就去回調(diào)mPageTransformertransformPage函數(shù)腥例,我們知道,我們可以通過自定義PageTransformer來實現(xiàn)每個頁面的“出場動畫”和“離場動畫”酝润,就是這里回調(diào)transformPage來實現(xiàn)的燎竖。

2.2.3 completeScroll

把目光回到computeScroll函數(shù),我們前面說道要销,在computeScroll函數(shù)最后調(diào)用了completeScroll函數(shù)构回,這個函數(shù)是做滑動結(jié)束后的清理復(fù)位等工作。比如:確保滾動已經(jīng)到最終位置,如果沒有到最終位置纤掸,則滾動到最終位置脐供。還有就是將每個頁面對應(yīng)的ItemInfo對象的scrolling設(shè)為false等等。

2.3 ViewPager 定義smoothScrollTo函數(shù)

根據(jù)第1節(jié)借跪,我們知道政己,重寫了computeScroll函數(shù)后,需要自定義一種平滑到指定位置的函數(shù)掏愁,一般命名為smoothScrollTo歇由,當然咯,你也可以取其他名字果港,你開心就好~印蓖。但是在這個函數(shù)里面需要調(diào)用startScroll函數(shù)。我們來看看ViewPagersmoothScrollTo函數(shù)源碼京腥,其中x,y表示要移動到的位置赦肃,velocity表示手指移動速度,如果不是用戶的手指觸發(fā)的平滑操作公浪,則velocity設(shè)為0即可:

void smoothScrollTo(int x, int y, int velocity) {
    if (getChildCount() == 0) {
        // 如果沒有頁面他宛,啥也不干
        setScrollingCacheEnabled(false);
        return;
    }
    //定義x軸起始位置
    int sx;
    //判斷在此之前mScroller是否還在計算滾動
    boolean wasScrolling = (mScroller != null) && !mScroller.isFinished();
    //如果當前在滾動
    if (wasScrolling) {
        //根據(jù)在此之前是否還在滾動來決定如何獲取當前的x位置
        sx = mIsScrollStarted ? mScroller.getCurrX() : mScroller.getStartX();
        // 如果mScroller在此之前還在計算滾動,則將其停止計算欠气,并直接滑動到最終位置厅各,
        // 這個最終位置即為此刻smoothScrollTo的起始位置
        mScroller.abortAnimation();
        //不啟用緩存
        setScrollingCacheEnabled(false);
    } else {//如果當前滾動結(jié)束
        sx = getScrollX();
    }
    //獲取y軸起始位置
    int sy = getScrollY();
    //計算要移動的x和y方向的距離
    int dx = x - sx;
    int dy = y - sy;
    //如果x和y方向的移動距離都是0,說明無需移動预柒,結(jié)束并返回
    if (dx == 0 && dy == 0) {
        //做一些清理和還原工作
        completeScroll(false);
        //已經(jīng)確定好新的頁面队塘,將mCurItem設(shè)置為新的頁面以及其他的相關(guān)處理
        populate();
        //設(shè)置當前的滾動狀態(tài)
        setScrollState(SCROLL_STATE_IDLE);
        return;
    }
    //啟用緩存,即對每個子View調(diào)用setDrawingCacheEnabled(true)
    setScrollingCacheEnabled(true);
    //設(shè)置當前的滾動狀態(tài)
    setScrollState(SCROLL_STATE_SETTLING);
    //獲取寬度及一半寬度
    final int width = getClientWidth();
    final int halfWidth = width / 2;
    //要移動的距離占寬度的比例宜鸯,這個比例必須得小于等于1
    final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);
    //smoothScrollTo并沒有使用勻速滑動憔古,而是通過distanceInfluenceForSnapDuration函數(shù)
    //來實現(xiàn)變速,這里與Scroller里面的插值器之間并無影響
    final float distance = halfWidth + halfWidth *
            distanceInfluenceForSnapDuration(distanceRatio);

    int duration;
    velocity = Math.abs(velocity);
    //如果手指滑動速度不為0
    if (velocity > 0) {
        //如果是手指滑動淋袖,則需要根據(jù)手指滑動速度計算滑動持續(xù)時間
        duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
    } else {
        //如果手指滑動速度為0鸿市,即,是通過代碼的方式滑動到指定位置即碗,則使用另一種方式計算滑動持續(xù)時間
        final float pageWidth = width * mAdapter.getPageWidth(mCurItem);
        final float pageDelta = (float) Math.abs(dx) / (pageWidth + mPageMargin);
        duration = (int) ((pageDelta + 1) * 100);
    }
    //確保整個滑動時間不超出最大的時間
    duration = Math.min(duration, MAX_SETTLE_DURATION);

    //將mIsScrollStarted標記重置為false焰情,表示沒有開始滾動,
    //這個標記會在computeScrollOffset函數(shù)中重置為true剥懒,
    //所以不用擔心會影響到其他地方的判斷
    mIsScrollStarted = false;
    //開始平滑
    mScroller.startScroll(sx, sy, dx, dy, duration);
    ViewCompat.postInvalidateOnAnimation(this);
}

從上面可以看到内舟,ViewPagersmoothScrollTo的實現(xiàn)還是挺復(fù)雜的,代碼實現(xiàn)出來的效果體驗非常好以及所考慮的功能很全面初橘。感覺非常值得去學(xué)習(xí)验游!另外充岛,ViewPager提供了只有x,y兩個參數(shù)的smoothScrollTo,其內(nèi)部也是調(diào)用上面這個smoothScrollTo批狱,只是將velocity參數(shù)設(shè)置為0裸准。

3 滑動沖突

現(xiàn)在為止展东,ViewPager的滑動部分已經(jīng)分析完畢赔硫,但是用過ViewPager都知道,ViewPager幫我們處理了滑動沖突盐肃。我們知道爪膊,ViewPager只關(guān)注水平方向的手指滑動,根據(jù)水平方向的手指滑動來切換頁面砸王。在垂直方向上推盛,ViewPager并不關(guān)心,因此谦铃,ViewPager很有必要解決一下滑動沖突耘成,把豎直方向的滑動傳遞給子View來處理。

我們知道驹闰,ViewGroup是在onInterceptTouchEvent函數(shù)中決定是否攔截觸摸事件瘪菌,那么我們就去學(xué)習(xí)一下ViewPageronInterceptTouchEvent函數(shù)。

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {

    //1. 觸摸動作
    final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;

    //2. 時刻要注意觸摸是否已經(jīng)結(jié)束
    if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
        //3. Release the drag.
        if (DEBUG) Log.v(TAG, "Intercept done!");
        //4. 重置一些跟判斷是否攔截觸摸相關(guān)變量
        resetTouch();
        //5. 觸摸結(jié)束嘹朗,無需攔截
        return false;
    }

    //6. 如果當前不是按下事件师妙,我們就判斷一下,是否是在拖拽切換頁面
    if (action != MotionEvent.ACTION_DOWN) {
        //7. 如果當前是正在拽切換頁面屹培,直接攔截掉事件默穴,后面無需再做攔截判斷
        if (mIsBeingDragged) {
            if (DEBUG) Log.v(TAG, "Intercept returning true!");
            return true;
        }
        //8. 如果標記為不允許拖拽切換頁面,我們就"放過"一切觸摸事件
        if (mIsUnableToDrag) {
            if (DEBUG) Log.v(TAG, "Intercept returning false!");
            return false;
        }
    }
    //9. 根據(jù)不同的動作進行處理
    switch (action) {
        //10. 如果是手指移動操作
        case MotionEvent.ACTION_MOVE: {

            //11. 代碼能執(zhí)行到這里褪秀,就說明mIsBeingDragged==false蓄诽,否則的話,在第7個注釋處就已經(jīng)執(zhí)行結(jié)束了

            //12.使用觸摸點Id媒吗,主要是為了處理多點觸摸
            final int activePointerId = mActivePointerId;
            if (activePointerId == INVALID_POINTER) {
                //13.如果當前的觸摸點id不是一個有效的Id若专,無需再做處理
                break;
            }
            //14.根據(jù)觸摸點的id來區(qū)分不同的手指,我們只需關(guān)注一個手指就好
            final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
            //15.根據(jù)這個手指的序號蝴猪,來獲取這個手指對應(yīng)的x坐標
            final float x = MotionEventCompat.getX(ev, pointerIndex);
            //16.在x軸方向上移動的距離
            final float dx = x - mLastMotionX;
            //17.x軸方向的移動距離絕對值
            final float xDiff = Math.abs(dx);
            //18.同理调衰,參照16、17條注釋
            final float y = MotionEventCompat.getY(ev, pointerIndex);
            final float yDiff = Math.abs(y - mInitialMotionY);
            if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);

            //19.判斷當前顯示的頁面是否可以滑動自阱,如果可以滑動嚎莉,則將該事件丟給當前顯示的頁面處理
            //isGutterDrag是判斷是否在兩個頁面之間的縫隙內(nèi)移動
            //canScroll是判斷頁面是否可以滑動
            if (dx != 0 && !isGutterDrag(mLastMotionX, dx) &&
                    canScroll(this, false, (int) dx, (int) x, (int) y)) {
                mLastMotionX = x;
                mLastMotionY = y;
                //20.標記ViewPager不去攔截事件
                mIsUnableToDrag = true;
                return false;
            }
            //21.如果x移動距離大于最小距離募书,并且斜率小于0.5照激,表示在水平方向上的拖動
            if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
                if (DEBUG) Log.v(TAG, "Starting drag!");
                //22.水平方向的移動,需要ViewPager去攔截
                mIsBeingDragged = true;
                //23.如果ViewPager還有父View铆隘,則還要向父View申請將觸摸事件傳遞給ViewPager
                requestParentDisallowInterceptTouchEvent(true);
                //24.設(shè)置滾動狀態(tài)
                setScrollState(SCROLL_STATE_DRAGGING);
                //25.保存當前位置
                mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop :
                        mInitialMotionX - mTouchSlop;
                mLastMotionY = y;
                //26.啟用緩存
                setScrollingCacheEnabled(true);
            } else if (yDiff > mTouchSlop) {//27.否則的話,表示是豎直方向上的移動
                if (DEBUG) Log.v(TAG, "Starting unable to drag!");
                //28.豎直方向上的移動則不去攔截觸摸事件
                mIsUnableToDrag = true;
            }
            if (mIsBeingDragged) {
                // 29.跟隨手指一起滑動
                if (performDrag(x)) {
                    ViewCompat.postInvalidateOnAnimation(this);
                }
            }
            break;
        }
        //30.如果手指是按下操作
        case MotionEvent.ACTION_DOWN: {
             
            //31.記錄按下的點位置
            mLastMotionX = mInitialMotionX = ev.getX();
            mLastMotionY = mInitialMotionY = ev.getY();
            //32.第一個ACTION_DOWN事件對應(yīng)的手指序號為0
            mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
            //33.重置允許拖拽切換頁面
            mIsUnableToDrag = false;
            //34.標記開始滾動
            mIsScrollStarted = true;
            //35.手動調(diào)用計算滑動的偏移量
            mScroller.computeScrollOffset();
            //36.如果當前滾動狀態(tài)為正在將頁面放置到最終位置叫确,
            //且當前位置距離最終位置足夠遠
            if (mScrollState == SCROLL_STATE_SETTLING &&
                    Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) {
                //37. 如果此時用戶手指按下跳芳,則立馬暫停滑動
                mScroller.abortAnimation();
                mPopulatePending = false;
                populate();
                mIsBeingDragged = true;
                //38.如果ViewPager還有父View竹勉,則還要向父View申請將觸摸事件傳遞給ViewPager
                requestParentDisallowInterceptTouchEvent(true);
                //39.設(shè)置當前狀態(tài)為正在拖拽
                setScrollState(SCROLL_STATE_DRAGGING);
            } else {
                //40.結(jié)束滾動
                completeScroll(false);
                mIsBeingDragged = false;
            }

            if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
                    + " mIsBeingDragged=" + mIsBeingDragged
                    + "mIsUnableToDrag=" + mIsUnableToDrag);
            break;
        }

        case MotionEventCompat.ACTION_POINTER_UP:
            onSecondaryPointerUp(ev);
            break;
    }

    //41.添加速度追蹤
    if (mVelocityTracker == null) {
        mVelocityTracker = VelocityTracker.obtain();
    }
    mVelocityTracker.addMovement(ev);

     
    //42.只有在當前是拖拽切換頁面時我們才會去攔截事件
    return mIsBeingDragged;
}

我們看看ViewPager是如何決定是攔截還是不攔截飞盆,從源碼上面看出,但斜率小于0.5時次乓,則要攔截吓歇,否則不攔截,斜率是什么情況呢票腰?高中數(shù)學(xué)可知城看,在第一象限中,越靠近y軸的直線杏慰,斜率越大测柠,越靠近x軸直線斜率越小,先看簡單圖示:

第一象限斜率

也就是說缘滥,手指滑動的傾斜度比0.5小轰胁,就去攔截事件,由ViewPager來響應(yīng)切換頁面完域。

好啦软吐,今天的學(xué)習(xí)就先到處為止啦,明天繼續(xù)研究其他部分

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吟税,一起剝皮案震驚了整個濱河市凹耙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌肠仪,老刑警劉巖肖抱,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異异旧,居然都是意外死亡意述,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門吮蛹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來荤崇,“玉大人,你說我怎么就攤上這事潮针∈趸纾” “怎么了?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵每篷,是天一觀的道長瓣戚。 經(jīng)常有香客問我端圈,道長,這世上最難降的妖魔是什么子库? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任舱权,我火速辦了婚禮,結(jié)果婚禮上仑嗅,老公的妹妹穿的比我還像新娘宴倍。我一直安慰自己,他們只是感情好无畔,可當我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布啊楚。 她就那樣靜靜地躺著吠冤,像睡著了一般浑彰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拯辙,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天郭变,我揣著相機與錄音,去河邊找鬼涯保。 笑死诉濒,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的夕春。 我是一名探鬼主播未荒,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼及志!你這毒婦竟也來了片排?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤速侈,失蹤者是張志新(化名)和其女友劉穎率寡,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體倚搬,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡冶共,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了每界。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捅僵。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖眨层,靈堂內(nèi)的尸體忽然破棺而出庙楚,到底是詐尸還是另有隱情,我是刑警寧澤谐岁,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布醋奠,位于F島的核電站榛臼,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏窜司。R本人自食惡果不足惜沛善,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望塞祈。 院中可真熱鬧金刁,春花似錦、人聲如沸议薪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽斯议。三九已至产捞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間哼御,已是汗流浹背坯临。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留恋昼,地道東北人看靠。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像液肌,于是被迫代替她去往敵國和親挟炬。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,527評論 2 349

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

  • 什么是View View 是 Android 中所有控件的基類嗦哆。 View的位置參數(shù) View 的位置由它的四個頂...
    acc8226閱讀 1,154評論 0 7
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,787評論 25 707
  • 路在腳下延伸谤祖! 兩旁落滿灰塵的柳樹依舊靜靜的站著,偶爾也會隨著寒風搖動起來吝秕,向我示意問好泊脐。 看著柳樹...
    貓步先生閱讀 380評論 0 0
  • 孩子放學(xué)回家容客,你問他/她的第一句話是什么? 我有個朋友约郁,他曾經(jīng)給我講過他跟兒子相處時的一個小細節(jié)缩挑。我不得不佩服...
    孩子怎么教閱讀 335評論 0 0
  • 隨著暢銷書《異類》的走紅,相信大多數(shù)人對10000小時理論都不會陌生鬓梅,即任何人如果想要在某個領(lǐng)域達到頂級專家供置,必須...
    毛文鑫閱讀 795評論 8 14