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

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

轉(zhuǎn)載請注明出處:【huachao1001的簡書:http://www.lxweimin.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對象,用于實(shí)現(xiàn)漸近式的滑動。假設(shè)我們自定義一個函數(shù)smoothScrollTo(int destX,int destY),用于讓ViewPager漸近式的滑動到(destX,destY)這個坐標(biāo)位置,那么使用Scroller實(shí)現(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實(shí)現(xiàn)的是x方向的平滑,其中startScroll函數(shù)的形參分別表示:起始位置的x坐標(biāo)、起始位置的y坐標(biāo)、x方向要移動的距離、y方向上要移動的距離以及整個滑動過程完成所需的時間。

2 ViewPager滑動

2.1 ViewPager定義Scroller

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

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

它跟我們上一節(jié)使用到的Scroller構(gòu)造器不同,他選擇使用2個形參的構(gòu)造器。其實(shí),第二個形參就是插值器(interpolator),對插值器不熟悉的童鞋可以去搜索一下動畫插值器相關(guān)內(nèi)容。其實(shí)這個插值器就是根據(jù)不同的時間控制滑動的速度,就像高中物理中的物體變速運(yùn)動。我們繼續(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,表示當(dāng)前的動畫時間點(diǎn),0表示動畫開始,1表示動畫結(jié)束。返回值表示移動到目標(biāo)位置的比值,如果大于1,則表示超出了最大位置,小于0表示比最小位置還要小。怎么理解呢?舉個例子,假設(shè)我們要實(shí)現(xiàn)變速動畫,我們要持續(xù)的時間是[0,1000],要滑動的距離是[0,100],那么假設(shè)當(dāng)前時間是200,則傳入到getInterpolation的形參就是200/1000=0.2,表示時間過了0.2,具體的返回值可以根據(jù)你的變速需求計(jì)算,假設(shè)你的返回值是0.8,那么表示當(dāng)前位置要處于100 * 0.8=80這個位置。如果你的返回值是1.8 ,那么肯定就是超出100了:100*1.8=180。

2.2 ViewPager重寫computeScroll()方法

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

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

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

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

2.2.1 pageScrolled

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

private boolean pageScrolled(int xpos) {
//1.mItems是ArrayList類型,它保存的是每個子View的抽象描述類ItemInfo
//如果沒有子View
if (mItems.size() == 0) {
    //2.先認(rèn)為沒有調(diào)用父類
    //mCalledSuper作用是:如果子類重寫了onPageScrolled,
    // 那么子類的實(shí)現(xiàn)必須要先調(diào)用父類ViewPager的onPageScrolled
    //為了確保子類的實(shí)現(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ù)當(dāng)前滑動的位置,得到當(dāng)前顯示的子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;
//保存當(dāng)前是第幾個頁面(即第幾個子View)
final int currentPage = ii.position;
//計(jì)算當(dāng)前頁面的偏移量,取值為[0,1),如果pageOffset不等于0,則下一個頁面可見
final float pageOffset = (((float) xpos / width) - ii.offset) /
        (ii.widthFactor + marginOffset);
//當(dāng)前頁面移動的像素點(diǎn)個數(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ù)當(dāng)前滑動的位置,得到當(dāng)前顯示的子View的抽象描述類ItemInfo,如果當(dāng)前滑動位置顯示的恰好是一個完整的頁面,這個頁面的前一個頁面和后一個頁面都沒有顯示,那么很容易理解,返回的就是這個頁面。可是如果當(dāng)前顯示區(qū)域是同時顯示2個頁面(兩個頁面都顯示一部分出現(xiàn)在顯示區(qū)域),那這個函數(shù)應(yīng)該返回哪一個頁面呢?從infoForCurrentScrollPosition源碼看出每次是返回左邊的頁面,如下圖所示:

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

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

2.2.2 onPageScrolled

上面我們知道,pageScrolled函數(shù)是為了調(diào)用onPageScrolled做前期計(jì)算,并將計(jì)算結(jié)果作為onPageScrolled的形參,最終是為了回調(diào)onPageScrolled函數(shù),那么我們看看onPageScrolled函數(shù)到底是干了啥~,從函數(shù)名看的出來,它是一個回調(diào)函數(shù),那么是什么情況下回調(diào)呢?其實(shí),在我們手指滑動或者是通過代碼直接滑動到指定位置過程中,會使得一些頁面滑動,如果我們想要在每個頁面在顯示區(qū)域滑動過程中實(shí)現(xiàn)某些效果,可以重寫這個函數(shù),當(dāng)然了,我們前面分析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,表示當(dāng)前是第幾個頁面
  2. float offset表示當(dāng)前頁面移動的距離,其實(shí)就是個相對實(shí)際寬度比例值,取值為[0,1)。0表示整個頁面在顯示區(qū)域,1表示整個頁面已經(jīng)完全左移出顯示區(qū)域。
  3. int offsetPixels , 表示當(dāng)前頁面左移的像素個數(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();
            //只針對頁面進(jìn)行處理
            if (lp.isDecor) continue;
            //計(jì)算child位置
            final float transformPos = (float) (child.getLeft() - scrollX) / getClientWidth();
            //調(diào)用transformPage
            mPageTransformer.transformPage(child, transformPos);
        }
    }
    //標(biāo)記ViewPager的onPageScrolled函數(shù)執(zhí)行過
    mCalledSuper = true;
}

從源碼上我們知道,onPageScrolled做了3件事,首先把Decor View固定在顯示區(qū)域,其次,將滾動事件進(jìn)行分發(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來實(shí)現(xiàn)每個頁面的“出場動畫”和“離場動畫”,就是這里回調(diào)transformPage來實(shí)現(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,當(dāng)然咯,你也可以取其他名字,你開心就好~。但是在這個函數(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是否還在計(jì)算滾動
    boolean wasScrolling = (mScroller != null) && !mScroller.isFinished();
    //如果當(dāng)前在滾動
    if (wasScrolling) {
        //根據(jù)在此之前是否還在滾動來決定如何獲取當(dāng)前的x位置
        sx = mIsScrollStarted ? mScroller.getCurrX() : mScroller.getStartX();
        // 如果mScroller在此之前還在計(jì)算滾動,則將其停止計(jì)算,并直接滑動到最終位置,
        // 這個最終位置即為此刻smoothScrollTo的起始位置
        mScroller.abortAnimation();
        //不啟用緩存
        setScrollingCacheEnabled(false);
    } else {//如果當(dāng)前滾動結(jié)束
        sx = getScrollX();
    }
    //獲取y軸起始位置
    int sy = getScrollY();
    //計(jì)算要移動的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è)置當(dāng)前的滾動狀態(tài)
        setScrollState(SCROLL_STATE_IDLE);
        return;
    }
    //啟用緩存,即對每個子View調(diào)用setDrawingCacheEnabled(true)
    setScrollingCacheEnabled(true);
    //設(shè)置當(dāng)前的滾動狀態(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ù)
    //來實(shí)現(xiàn)變速,這里與Scroller里面的插值器之間并無影響
    final float distance = halfWidth + halfWidth *
            distanceInfluenceForSnapDuration(distanceRatio);

    int duration;
    velocity = Math.abs(velocity);
    //如果手指滑動速度不為0
    if (velocity > 0) {
        //如果是手指滑動,則需要根據(jù)手指滑動速度計(jì)算滑動持續(xù)時間
        duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
    } else {
        //如果手指滑動速度為0,即,是通過代碼的方式滑動到指定位置,則使用另一種方式計(jì)算滑動持續(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標(biāo)記重置為false,表示沒有開始滾動,
    //這個標(biāo)記會在computeScrollOffset函數(shù)中重置為true,
    //所以不用擔(dān)心會影響到其他地方的判斷
    mIsScrollStarted = false;
    //開始平滑
    mScroller.startScroll(sx, sy, dx, dy, duration);
    ViewCompat.postInvalidateOnAnimation(this);
}

從上面可以看到,ViewPagersmoothScrollTo的實(shí)現(xiàn)還是挺復(fù)雜的,代碼實(shí)現(xiàn)出來的效果體驗(yà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. 如果當(dāng)前不是按下事件,我們就判斷一下,是否是在拖拽切換頁面
    if (action != MotionEvent.ACTION_DOWN) {
        //7. 如果當(dāng)前是正在拽切換頁面,直接攔截掉事件,后面無需再做攔截判斷
        if (mIsBeingDragged) {
            if (DEBUG) Log.v(TAG, "Intercept returning true!");
            return true;
        }
        //8. 如果標(biāo)記為不允許拖拽切換頁面,我們就"放過"一切觸摸事件
        if (mIsUnableToDrag) {
            if (DEBUG) Log.v(TAG, "Intercept returning false!");
            return false;
        }
    }
    //9. 根據(jù)不同的動作進(jìn)行處理
    switch (action) {
        //10. 如果是手指移動操作
        case MotionEvent.ACTION_MOVE: {

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

            //12.使用觸摸點(diǎn)Id,主要是為了處理多點(diǎn)觸摸
            final int activePointerId = mActivePointerId;
            if (activePointerId == INVALID_POINTER) {
                //13.如果當(dāng)前的觸摸點(diǎn)id不是一個有效的Id,無需再做處理
                break;
            }
            //14.根據(jù)觸摸點(diǎn)的id來區(qū)分不同的手指,我們只需關(guān)注一個手指就好
            final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
            //15.根據(jù)這個手指的序號,來獲取這個手指對應(yīng)的x坐標(biāo)
            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.判斷當(dāng)前顯示的頁面是否可以滑動,如果可以滑動,則將該事件丟給當(dāng)前顯示的頁面處理
            //isGutterDrag是判斷是否在兩個頁面之間的縫隙內(nèi)移動
            //canScroll是判斷頁面是否可以滑動
            if (dx != 0 && !isGutterDrag(mLastMotionX, dx) &&
                    canScroll(this, false, (int) dx, (int) x, (int) y)) {
                mLastMotionX = x;
                mLastMotionY = y;
                //20.標(biāo)記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.保存當(dāng)前位置
                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.記錄按下的點(diǎn)位置
            mLastMotionX = mInitialMotionX = ev.getX();
            mLastMotionY = mInitialMotionY = ev.getY();
            //32.第一個ACTION_DOWN事件對應(yīng)的手指序號為0
            mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
            //33.重置允許拖拽切換頁面
            mIsUnableToDrag = false;
            //34.標(biāo)記開始滾動
            mIsScrollStarted = true;
            //35.手動調(diào)用計(jì)算滑動的偏移量
            mScroller.computeScrollOffset();
            //36.如果當(dāng)前滾動狀態(tài)為正在將頁面放置到最終位置,
            //且當(dāng)前位置距離最終位置足夠遠(yuǎn)
            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è)置當(dāng)前狀態(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.只有在當(dāng)前是拖拽切換頁面時我們才會去攔截事件
    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)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,635評論 2 380

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

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