zoukankan      html  css  js  c++  java
  • ViewPager源码分析——滑动切换页面处理过程

    上周客户反馈Contacts快速滑动界面切换tab有明显卡顿,让优化。

    自己验证又没发现卡顿现象,但总得给客户一个技术性的回复,于是看了一下ViewPager源码中处理滑动切换tab的过程。

    ViewPager  源码位置: androidframeworkssupportv4javaandroidsupportv4viewViewPager.java

    ViewPager其实就是一个重写的ViewGroup,使用ViewPager可以参考SDK中的demo:sdkextrasandroidsupportsamples

    ViewPager.java开头的注释中有推荐一个demo,使用了supportv13

    * <p>Here is a more complicated example of ViewPager, using it in conjuction
    * with {@link android.app.ActionBar} tabs. You can find other examples of using
    * ViewPager in the API 4+ Support Demos and API 13+ Support Demos sample code.
    *
    * {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java
    * complete}

    ViewPager滑动是处理Touch事件,所以有必要了解Touch事件的分发过程。可以参考这篇 http://blog.csdn.net/guolin_blog/article/details/9153747

    public class ViewPager extends ViewGroup{
           
             ......
             
             @Override
             public boolean onInterceptTouchEvent(MotionEvent ev) {
                 /*
                  * This method JUST determines whether we want to intercept the motion.
                  * If we return true, onMotionEvent will be called and we do the actual
                  * scrolling there.
                  */
                         
                 final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
                         
                 // Always take care of the touch gesture being complete.
                 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
                     // Release the drag.
                     if (DEBUG) Log.v(TAG, "Intercept done!");
                     mIsBeingDragged = false;
                     mIsUnableToDrag = false;
                     mActivePointerId = INVALID_POINTER;
                     if (mVelocityTracker != null) {
                         mVelocityTracker.recycle();
                         mVelocityTracker = null;
                     }
                     return false;
                 }
    
                 // Nothing more to do here if we have decided whether or not we
                 // are dragging.
                 if (action != MotionEvent.ACTION_DOWN) {
                     if (mIsBeingDragged) {
                         if (DEBUG) Log.v(TAG, "Intercept returning true!");
                         return true;
                     }
                     if (mIsUnableToDrag) {
                         if (DEBUG) Log.v(TAG, "Intercept returning false!");
                         return false;
                     }
                 }
    
                 switch (action) {
                     case MotionEvent.ACTION_MOVE: {
                         /*
                          * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
                          * whether the user has moved far enough from his original down touch.
                          */
    
                         /*
                         * Locally do absolute value. mLastMotionY is set to the y value
                         * of the down event.
                         */
                         final int activePointerId = mActivePointerId;
                         
                         if (activePointerId == INVALID_POINTER) {
                             // If we don't have a valid id, the touch down wasn't on content.
                             break;
                         }
    
                         final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
                         final float x = MotionEventCompat.getX(ev, pointerIndex);
                         final float dx = x - mLastMotionX;
                         final float xDiff = Math.abs(dx);
                         final float y = MotionEventCompat.getY(ev, pointerIndex);
                         final float yDiff = Math.abs(y - mInitialMotionY);
                                         
                          boolean isGutterDrag = isGutterDrag(mLastMotionX, dx);
                          boolean canScroll  = canScroll(this, false, (int) dx, (int) x, (int) y);
                                         
                         if (dx != 0 && !isGutterDrag && canScroll) {
                             // Nested view has scrollable area under this point. Let it be handled there.
                             mLastMotionX = x;
                             mLastMotionY = y;
                             mIsUnableToDrag = true;
                             return false;
                         }
                         if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
                             if (DEBUG) Log.v(TAG, "Starting drag!");
                             mIsBeingDragged = true;
                             requestParentDisallowInterceptTouchEvent(true);
                             setScrollState(SCROLL_STATE_DRAGGING);
                             mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop :
                                     mInitialMotionX - mTouchSlop;
                             mLastMotionY = y;
                             setScrollingCacheEnabled(true);
                         } else if (yDiff > mTouchSlop) {
                             // The finger has moved enough in the vertical
                             // direction to be counted as a drag...  abort
                             // any attempt to drag horizontally, to work correctly
                             // with children that have scrolling containers.
                             if (DEBUG) Log.v(TAG, "Starting unable to drag!");
                             mIsUnableToDrag = true;
                         }
                         if (mIsBeingDragged) {
                             // Scroll to follow the motion event
                             if (performDrag(x)) {
                                 ViewCompat.postInvalidateOnAnimation(this);
                             }
                         }
                         break;
                     }
    
                     case MotionEvent.ACTION_DOWN: {
                         /*
                          * Remember location of down touch.
                          * ACTION_DOWN always refers to pointer index 0.
                          */
                         mLastMotionX = mInitialMotionX = ev.getX();
                         mLastMotionY = mInitialMotionY = ev.getY();
                         mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
                         mIsUnableToDrag = false;
                         
    
                         mScroller.computeScrollOffset();
                         if (mScrollState == SCROLL_STATE_SETTLING &&
                                 Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) {
                             // Let the user 'catch' the pager as it animates.
                             mScroller.abortAnimation();
                             mPopulatePending = false;
                             populate();
                             mIsBeingDragged = true;
                             requestParentDisallowInterceptTouchEvent(true);
                             setScrollState(SCROLL_STATE_DRAGGING);
                         } else {
                             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;
                 }
    
                 if (mVelocityTracker == null) {
                     mVelocityTracker = VelocityTracker.obtain();
                 }
                 mVelocityTracker.addMovement(ev);
    
                 /*
                  * The only time we want to intercept motion events is if we are in the
                  * drag mode.
                  */
                 return mIsBeingDragged;
             }
    
             @Override
             public boolean onTouchEvent(MotionEvent ev) {
                 if (mFakeDragging) {
                     // A fake drag is in progress already, ignore this real one
                     // but still eat the touch events.
                     // (It is likely that the user is multi-touching the screen.)
                     return true;
                 }
    
                 if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
                     // Don't handle edge touches immediately -- they may actually belong to one of our
                     // descendants.
                     return false;
                 }
    
                 if (mAdapter == null || mAdapter.getCount() == 0) {
                     // Nothing to present or scroll; nothing to touch.
                     return false;
                 }
    
                 if (mVelocityTracker == null) {
                     mVelocityTracker = VelocityTracker.obtain();
                 }
                 mVelocityTracker.addMovement(ev);
    
                 final int action = ev.getAction();
                 boolean needsInvalidate = false;
                         
                 switch (action & MotionEventCompat.ACTION_MASK) {
                     case MotionEvent.ACTION_DOWN: {
                         mScroller.abortAnimation();
                         mPopulatePending = false;
                         populate();
    
                         // Remember where the motion event started
                         mLastMotionX = mInitialMotionX = ev.getX();
                         mLastMotionY = mInitialMotionY = ev.getY();
                         mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
                         break;
                     }
                     case MotionEvent.ACTION_MOVE:
                         if (!mIsBeingDragged) {
                             final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
                             final float x = MotionEventCompat.getX(ev, pointerIndex);
                             final float xDiff = Math.abs(x - mLastMotionX);
                             final float y = MotionEventCompat.getY(ev, pointerIndex);
                             final float yDiff = Math.abs(y - mLastMotionY);
                             if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
                             if (xDiff > mTouchSlop && xDiff > yDiff) {
                                 if (DEBUG) Log.v(TAG, "Starting drag!");
                                 mIsBeingDragged = true;
                                 requestParentDisallowInterceptTouchEvent(true);
                                 mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop :
                                         mInitialMotionX - mTouchSlop;
                                 mLastMotionY = y;
                                 setScrollState(SCROLL_STATE_DRAGGING);
                                 setScrollingCacheEnabled(true);
    
                                 // Disallow Parent Intercept, just in case
                                 ViewParent parent = getParent();
                                 if (parent != null) {
                                     parent.requestDisallowInterceptTouchEvent(true);
                                 }
                             }
                         }
                         // Not else! Note that mIsBeingDragged can be set above.
                         if (mIsBeingDragged) {
                             // Scroll to follow the motion event
                             final int activePointerIndex = MotionEventCompat.findPointerIndex(
                                     ev, mActivePointerId);
                             final float x = MotionEventCompat.getX(ev, activePointerIndex);
                             needsInvalidate |= performDrag(x);
                         }
                         break;
                     case MotionEvent.ACTION_UP:
                         if (mIsBeingDragged) {
                             final VelocityTracker velocityTracker = mVelocityTracker;
                             velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                             int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
                                     velocityTracker, mActivePointerId);
                             mPopulatePending = true;
                             final int width = getClientWidth();
                             final int scrollX = getScrollX();
                             final ItemInfo ii = infoForCurrentScrollPosition();
                             final int currentPage = ii.position;
                             final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
                             final int activePointerIndex =
                                     MotionEventCompat.findPointerIndex(ev, mActivePointerId);
                             final float x = MotionEventCompat.getX(ev, activePointerIndex);
                             final int totalDelta = (int) (x - mInitialMotionX);
                             int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
                                     totalDelta);
                             setCurrentItemInternal(nextPage, true, true, initialVelocity);
    
                             mActivePointerId = INVALID_POINTER;
                             endDrag();
                             needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
                         }
                         break;
                     case MotionEvent.ACTION_CANCEL:
                         if (mIsBeingDragged) {
                             scrollToItem(mCurItem, true, 0, false);
                             mActivePointerId = INVALID_POINTER;
                             endDrag();
                             needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
                         }
                         break;
                     case MotionEventCompat.ACTION_POINTER_DOWN: {
                         final int index = MotionEventCompat.getActionIndex(ev);
                         final float x = MotionEventCompat.getX(ev, index);
                         mLastMotionX = x;
                         mActivePointerId = MotionEventCompat.getPointerId(ev, index);
                         break;
                     }
                     case MotionEventCompat.ACTION_POINTER_UP:
                         onSecondaryPointerUp(ev);
                         mLastMotionX = MotionEventCompat.getX(ev,
                                 MotionEventCompat.findPointerIndex(ev, mActivePointerId));
                         break;
                 }
                 if (needsInvalidate) {
                     ViewCompat.postInvalidateOnAnimation(this);
                 }
                 return true;
             }
             
             ......
         }

    按照ACTION_DOWN, ACTION_MOVE, ACTION_UP顺序分析滑动页面

    1,ACTION_DOWN 

      第一次按下时onInterceptTouchEvent先处理

      第一次按下时mIsBeingDragged = false;所以ACTION_DOWN传给ViewPager当前页面子View处理——如:联系人列表,直至ACTION_DOWN处理完

    2,ACTION_MOVE

      如果处理ACTION_DOWN时没执行requestParentDisallowInterceptTouchEvent(true);则 onInterceptTouchEvent处理ACTION_MOVE

      

            //判断水平移动,一般canScroll都为false,所以不会进入这里。如果进入,则onInterceptTouchEvent返回false,ACTION_MOVE传递给当前页面中的View处理。
                  if (dx != 0 && !isGutterDrag && canScroll) {
                       //isGutterDrag——是否从屏幕边缘滑动, canScroll——ViewPager当前页面中的子View是否支持水平滑动
                       //Contacts中canScroll始终未false, 所以不会进入这里。
                        // Nested view has scrollable area under this point. Let it be handled there.
                        mLastMotionX = x;
                        mLastMotionY = y;
                        mIsUnableToDrag = true;
                        return false;
                  }
          
                  //判断水平,竖直位移           
                  if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
                      //mTouchSlop在ViewPager初始化时获得为16dp, 也就是(水平位移>16dp && 水平位移/2>竖直位移)
                        if (DEBUG) Log.v(TAG, "Starting drag!");
                        mIsBeingDragged = true;  //mIsBeingDragged是onInterceptTouchEvent返回的返回值,true表示onTouchEvent要处理ACTION_MOVE,不再往下传递
                        requestParentDisallowInterceptTouchEvent(true);//disallowIntercept设为true,即将发生的ACTION_UP不会进入onInterceptTouchEvent
                        setScrollState(SCROLL_STATE_DRAGGING);
                        mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop :
                                mInitialMotionX - mTouchSlop;
                        mLastMotionY = y;
                        setScrollingCacheEnabled(true);
                 } else if (yDiff > mTouchSlop) {
                     //如果不满足(水平位移>16dp && 水平位移/2>竖直位移),但——竖直位移>16dp, onInterceptTouchEvent返回false,ACTION_MOVE传递给当前页面中的View处理
                        // The finger has moved enough in the vertical
                        // direction to be counted as a drag...  abort
                        // any attempt to drag horizontally, to work correctly
                        // with children that have scrolling containers.
                        if (DEBUG) Log.v(TAG, "Starting unable to drag!");
                        mIsUnableToDrag = true;
                 }
                 if (mIsBeingDragged) {
                        // Scroll to follow the motion event
                        if (performDrag(x)) {
                            ViewCompat.postInvalidateOnAnimation(this);//页面滑动
                        }
                 }
                   
                 
                 //满足(水平位移>16dp && 水平位移/2>竖直位移)onTouchEvent处理ACTION_MOVE
                  if (mIsBeingDragged) {
                        // Scroll to follow the motion event
                        final int activePointerIndex = MotionEventCompat.findPointerIndex(
                                ev, mActivePointerId);
                        final float x = MotionEventCompat.getX(ev, activePointerIndex);
                        needsInvalidate |= performDrag(x);
                  }
                  
                ACTION_MOVE事件是滑动页面时执行最多的,

    3,ACTION_UP
      如果第2步执行requestParentDisallowInterceptTouchEvent(true)并且return rue, 则由onTouchEvent直接处理。

      

    case MotionEvent.ACTION_UP:
                    if (mIsBeingDragged) {
                        ......//获得要切换的页面
                        int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
                                totalDelta);
                        //这里实现页面切换        
                        setCurrentItemInternal(nextPage, true, true, initialVelocity);
                ......
                    }

      setCurrentItemInternal(....)方法实现页面切换,切换到哪个页面时由determineTargetPage(....)返回的值决定。

          private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) {
                int targetPage;
                Log.i("antoon", TAG+", determineTargetPage, Math.abs(deltaX) = "+Math.abs(deltaX)+", mFlingDistance = "+mFlingDistance);
                Log.i("antoon", TAG+", determineTargetPage, Math.abs(velocity) = "+Math.abs(velocity)+", mMinimumVelocity = "+mMinimumVelocity);
                if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
                    targetPage = velocity > 0 ? currentPage : currentPage + 1;
                } else {            
                    final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
                    targetPage = (int) (currentPage + pageOffset + truncator);               
                }
    
                if (mItems.size() > 0) {
                    final ItemInfo firstItem = mItems.get(0);
                    final ItemInfo lastItem = mItems.get(mItems.size() - 1);
    
                    // Only let the user target pages we have items for
                    targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));
                }
    
                return targetPage;
            }

      经Log输出,mFlingDistance=75, mMinimumVelocity=1200, 所以对于快速滑动要满足 (水平滑动距离>75px && 滑动速率>1200px/s)才会切换页面

          else {//这是对缓慢滑动的处理。  pageOffset决定切换哪个页面 。
                    final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
                    targetPage = (int) (currentPage + pageOffset + truncator);
               }

    综上ViewPager快速滑动切换页面需要满足条件: 1,(水平位移>16dp && 水平位移/2>竖直位移)触发页面滑动,
                           2,(水平滑动距离>75px && 滑动速率>1200px/s)触发页面切换。

  • 相关阅读:
    linux安装redis 完整步骤
    java获取音频文件播放时长
    jar包部署在linux上后浏览器访问不到的问题
    FileRead方法
    FileWrite方法
    用Calendar方法知道月份的天数
    Calendar的用法
    两个时间相减(java简单用法)
    单列体现(Runtime)
    Random方法
  • 原文地址:https://www.cnblogs.com/antoon/p/4256630.html
Copyright © 2011-2022 走看看