ViewPager源码分析——滑动切换页面处理过程

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

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

ViewPager  源码位置: android\frameworks\support\v4\java\android\support\v4\view\ViewPager.java

ViewPager其实就是一个重写的ViewGroup,使用ViewPager可以参考SDK中的demo:sdk\extras\android\support\samples

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)触发页面切换。

时间: 2024-10-09 02:10:05

ViewPager源码分析——滑动切换页面处理过程的相关文章

ViewPager 源码分析(一) —— setAdapter() 与 populate()

写在前面 做安卓也有一定时间了,虽然常用控件都已大致掌握,然而随着 Android N 的发布,不自觉的愈发焦虑起来.说来惭愧,Android L 的 Material Design 库里的许多控件都还没用过,照这样下去迟早要被新技术所淘汰.那该怎么办呢,偶然间我看到一篇博文如此说到:"不要觉得 android 里边控件繁杂多样,官方或第三方新控件层出不穷,其实真正的控件就只有两个View和ViewGroup.一旦有了它们的基础,不管来什么新控件,TabLayout也好,CoordinatorL

tair源码分析——leveldb新增的CompactRangeSelfLevel过程

tair是一个分布式KV存储引擎,当新增机器或者有机器down掉的时候,tair的dataserver会根据ConfigServer生成的新的对照表进行数据的迁移和清理.在数据清理的过程中就用到了在tair中新增的Compaction方式——CompactRangeSelfLevel,顾名思义,这个CompactRangeSelfLevel就是对自己所在(指定)的Level进行一定Key范围的Compaction然后将生成的输出文件也写入到自己所在的Level而不是父层(L + 1).下面我们来

ViewPager源码分析

1.问题 由于Android Framework源码很庞大,所以读源码必须带着问题来读!没有问题,创造问题再来读!否则很容易迷失在无数的方法与属性之中,最后无功而返. 那么,关于ViewPager有什么问题呢? 1. setOffsreenPageLimit()方法是如何实现页面缓存的? 2. 在布局文件中,ViewPager布局内部能否添加其他View? 3. 为什么ViewPager初始化时,显示了一个页面却不会触发onPageSelected回调? 问题肯定不止这三个,但是有这三个问题基本

【Spring源码分析】原型Bean实例化过程、byName与byType及FactoryBean获取Bean源码实现

原型Bean加载过程 之前的文章,分析了非懒加载的单例Bean整个加载过程,除了非懒加载的单例Bean之外,Spring中还有一种Bean就是原型(Prototype)的Bean,看一下定义方式: 1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi=&qu

【Android 界面效果45】ViewPager源码分析

ViewPager概述: Layout manager that allows the user to flip left and right through pages of data. You supply an implementation of a PagerAdapter to generate the pages that the view shows. Note this class is currently under early design and development.

Tomcat源码分析--一次HTTP请求过程

前两篇我们分析了Tomcat服务的启动过程和Connector(连接器).现在让我们看看一次Tomcat服务器是怎么提供HTTP服务的. 上文我们说到endpoint是底层处理I/O具体实现类,那么一次HTTP首先也要从这个类中开始.还是以NIOEndPoint实现类为例子.在NIOEndPoint类中有一个名为Acceptor内部类.该内部类负责接收即将到来的TCP/IP连接,并将它们分配给合适的processor处理. HTTP底层是TCP协议,Java实现TCP协议的具体的方式就是Sock

f2fs源码分析(一)mount 过程

许多文章会介绍F2FS,对于入门者来说能够了解个F2FS全貌,但是真正了解这个年轻的文件系统还是要看源码的.网上F2fs源码导读的文章,我到现在还是没看过,所以就用这几篇博客来介绍下f2fs,以期对f2fs有更加深入的认识,甚至对整个IO路径的认知有所启发. 下面 文件系统的包括文件系统在磁盘上的布局,也包括在驻留在内存中的文件系统的“驱动” mount过程主要是新建段管理器(segment manager),节点管理器(node manager).其中,段管理器是为了垃圾回收,因为垃圾回收算法

Qt事件分发机制源码分析之QApplication对象构建过程

我们在新建一个Qt GUI项目时,main函数里会生成类似下面的代码: int main(int argc, char *argv[]) { QApplication application(argc, argv); CQDialog dialog(NULL); dialog.show(); return application.exec(); } 对应的步骤解释如下 1.构建QApplication对象 2.构建CQDialog主界面 3.主界面显示 4.QApplication对象进入事件循

Hadoop源码分析(1):HDFS读写过程解析

一.文件的打开 1.1.客户端 HDFS打开一个文件,需要在客户端调用DistributedFileSystem.open(Path f, int bufferSize),其实现为: public FSDataInputStream open(Path f, int bufferSize) throws IOException { return new DFSClient.DFSDataInputStream( dfs.open(getPathName(f), bufferSize, verif