zoukankan      html  css  js  c++  java
  • ViewPager部分源码分析三:scroll

    手指在屏幕上滑动,触发到onTouchEvent(),执行case MotionEvent.ACTION_MOVE: 。。。

    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);
                        if (pointerIndex == -1) {
                            // A child has consumed some touch events and put us into an inconsistent state.
                            needsInvalidate = resetTouch();
                            break;
                        }
                        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 float marginOffset = (float) mPageMargin / width;
                        final int currentPage = ii.position;
                        final float pageOffset = (((float) scrollX / width) - ii.offset)
                                / (ii.widthFactor + marginOffset);
                        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);
    
                        needsInvalidate = resetTouch();
                    }
                    break;
                case MotionEvent.ACTION_CANCEL:
                    if (mIsBeingDragged) {
                        scrollToItem(mCurItem, true, 0, false);
                        needsInvalidate = resetTouch();
                    }
                    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;
        }
    onTouchEvent(MotionEvent ev)

    --> performDrag()

    private boolean performDrag(float x) {
            boolean needsInvalidate = false;
    
            final float deltaX = mLastMotionX - x;
            mLastMotionX = x;
    
            float oldScrollX = getScrollX();
            float scrollX = oldScrollX + deltaX;
            final int width = getClientWidth();
    
            float leftBound = width * mFirstOffset;
            float rightBound = width * mLastOffset;
            boolean leftAbsolute = true;
            boolean rightAbsolute = true;
    
            final ItemInfo firstItem = mItems.get(0);
            final ItemInfo lastItem = mItems.get(mItems.size() - 1);
            if (firstItem.position != 0) {
                leftAbsolute = false;
                leftBound = firstItem.offset * width;
            }
            if (lastItem.position != mAdapter.getCount() - 1) {
                rightAbsolute = false;
                rightBound = lastItem.offset * width;
            }
    
            if (scrollX < leftBound) {
                if (leftAbsolute) {
                    float over = leftBound - scrollX;
                    needsInvalidate = mLeftEdge.onPull(Math.abs(over) / width);
                }
                scrollX = leftBound;
            } else if (scrollX > rightBound) {
                if (rightAbsolute) {
                    float over = scrollX - rightBound;
                    needsInvalidate = mRightEdge.onPull(Math.abs(over) / width);
                }
                scrollX = rightBound;
            }
            // Don't lose the rounded component
            mLastMotionX += scrollX - (int) scrollX;
            scrollTo((int) scrollX, getScrollY());
            pageScrolled((int) scrollX);
    
            return needsInvalidate;
        }
    performDrag(float x)

    --> pageScrolled()

    private boolean pageScrolled(int xpos) {
            if (mItems.size() == 0) {
                mCalledSuper = false;
                onPageScrolled(0, 0, 0);
                if (!mCalledSuper) {
                    throw new IllegalStateException(
                            "onPageScrolled did not call superclass implementation");
                }
                return false;
            }
            final ItemInfo ii = infoForCurrentScrollPosition();
            final int width = getClientWidth();
            final int widthWithMargin = width + mPageMargin;
            final float marginOffset = (float) mPageMargin / width;
            final int currentPage = ii.position;
            final float pageOffset = (((float) xpos / width) - ii.offset) /
                    (ii.widthFactor + marginOffset);
            final int offsetPixels = (int) (pageOffset * widthWithMargin);
    
            mCalledSuper = false;
            onPageScrolled(currentPage, pageOffset, offsetPixels);
            if (!mCalledSuper) {
                throw new IllegalStateException(
                        "onPageScrolled did not call superclass implementation");
            }
            return true;
        }
    pageScrolled(int xpos)

    --> onPageScrolled()

    protected void onPageScrolled(int position, float offset, int offsetPixels) {
            // Offset any decor views if needed - keep them on-screen at all times.
            if (mDecorChildCount > 0) {
                final int scrollX = getScrollX();
                int paddingLeft = getPaddingLeft();
                int paddingRight = getPaddingRight();
                final int width = getWidth();
                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;
    
                    final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
                    int childLeft = 0;
                    switch (hgrav) {
                        default:
                            childLeft = paddingLeft;
                            break;
                        case Gravity.LEFT:
                            childLeft = paddingLeft;
                            paddingLeft += child.getWidth();
                            break;
                        case Gravity.CENTER_HORIZONTAL:
                            childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
                                    paddingLeft);
                            break;
                        case Gravity.RIGHT:
                            childLeft = width - paddingRight - child.getMeasuredWidth();
                            paddingRight += child.getMeasuredWidth();
                            break;
                    }
                    childLeft += scrollX;
    
                    final int childOffset = childLeft - child.getLeft();
                    if (childOffset != 0) {
                        child.offsetLeftAndRight(childOffset);
                    }
                }
            }
    
            dispatchOnPageScrolled(position, offset, offsetPixels);
    
            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;
                    final float transformPos = (float) (child.getLeft() - scrollX) / getClientWidth();
                    mPageTransformer.transformPage(child, transformPos);
                }
            }
    
            mCalledSuper = true;
        }
    onPageScrolled(int position, float offset, int offsetPixels)

    --> dispatchOnPageScrolled() 处理OnPageChangeListener

    private void dispatchOnPageScrolled(int position, float offset, int offsetPixels) {
            if (mOnPageChangeListener != null) {
                mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
            }
            if (mOnPageChangeListeners != null) {
                for (int i = 0, z = mOnPageChangeListeners.size(); i < z; i++) {
                    OnPageChangeListener listener = mOnPageChangeListeners.get(i);
                    if (listener != null) {
                        listener.onPageScrolled(position, offset, offsetPixels);
                    }
                }
            }
            if (mInternalPageChangeListener != null) {
                mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);
            }
        }
    dispatchOnPageScrolled(int position, float offset, int offsetPixels)

    --> onPageScrolled()中,执行完dispatchOnPageScrolled()之后,对mPageTransformer处理的代码是进行动画处理,因此自定义的动画类(继承PageTransformer,并实现transformPage()方法)在这里得到执行。可以看到,通过for循环对每一个child都进行动画处理。

    在分析这个过程是,对scrollX不太明白,就对原来的一个例子(Android Tab -- 使用ViewPager、Fragment、FragmentPagerAdapter来实现)作了下调整,进行跟踪分析。

    -->

    根据跟踪到的数据对onPageScrolled(int position, float positionOffset, int positionOffsetPixels)进行分析:

    先看看scrollX:

    scrollX = 已经滑过去的标签宽度之和(old scrollX,其实就是对应图中标签1的宽度) + 本次滑动的宽度

    本次滑动的宽度 = 当前左边显示的标签的宽度(child的width) - 当前左边显示的标签剩下的宽度(fragment2滑动后剩下的宽度)

    再来说三个参数:

    position:  当前左边显示的标签位置(从0开始),图片对应的是1。

    positionOffset: 本次滑动的宽度 / 当前左边显示的标签的宽度(child的width)

    positionOffsetSetPixels: 本次滑动的宽度

    如果没有重写PagerAdapter.getPageWidth()方法,就可以简单按以上对三个参数参数的理解。

    然而,但重写了PagerAdapter.getPageWidth()方法后发现不太一样了!!

    public float getPageWidth(int position) {
    if(position == 2)
    return 1.f;
    return 0.4f;
    }

    除了Fragment3是1倍宽度,其他的都是0.4倍宽度。

    -->

    在这种情况下得到的:

    scrollX:同上一种情况,依旧是每次滑动距离之和。

    position:同上一种情况,依旧是当前最左边标签的序号。

    positionOffset:依旧可以简单的理解为:本次滑动的宽度 / 当前左边显示的标签的宽度(child的width),对应图片中:Fragment1滑动的距离 / Fragment1的宽度

    但是,

    positionOffsetSetPixels: 并不是 本次滑动的宽度, 而是positionOffset反映到ViewPager的宽度上,按源码的意思是应该就是 positionOffset * getClientWidth() 。

    当然,源码还有对mPageMargin即page之间的间距的处理,本示例为0。

    因此positionOffsetSetPixels并不是327(494 - 167),而是817(跟踪到的数据,嘿嘿。。。)

  • 相关阅读:
    URL 中使用 Base64 编码
    一个简单实用的C#日志类(第二版)
    C# 对象XML序列化
    VS2010安装项目的系统必备中添加.NET 2.0
    .NET Framework 4 安装程序
    使用ExeConfigurationFileMap读写配置文件
    C# 生成 XML
    Bug管理工具和测试管理工具介绍
    Android学习系列(19)App离线下载
    Android拓展系列(5)CyanogenMod源码下载和编译(Android ROM定制基础篇)
  • 原文地址:https://www.cnblogs.com/yarightok/p/5677563.html
Copyright © 2011-2022 走看看