ViewPager2源码详解

今天来聊一聊ViewPager2的原理,相比用开发android的小伙伴对ViewPager都很熟悉,但是ViewPager2估计用到的应该在少数,今天来了解一下ViewPager2的实现原理。

ViewPager2的用法和ViewPage类似,所以才起了一个重复的名字吧,ViewPager2ViewPager最根本的区别就是可以把ViewPager2看作是RecycleView,不错它的内部实现就是RecycleView,现在进入它的源码瞧瞧,眼见为实吗。

public final class ViewPager2 extends ViewGroup

ViewPager2 继承ViewGroup

 public ViewPager2(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initialize(context, attrs);
    }

ViewPager2的构造方法里面有个initialize方法所有的子控件添加都是放在这个方法里面

 private void initialize(Context context, AttributeSet attrs) {
        mAccessibilityProvider = sFeatureEnhancedA11yEnabled
                ? new PageAwareAccessibilityProvider()
                : new BasicAccessibilityProvider();

        mRecyclerView = new RecyclerViewImpl(context);
        mRecyclerView.setId(ViewCompat.generateViewId());
        mRecyclerView.setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);

        mLayoutManager = new LinearLayoutManagerImpl(context);
        mRecyclerView.setLayoutManager(mLayoutManager);
        mRecyclerView.setScrollingTouchSlop(RecyclerView.TOUCH_SLOP_PAGING);
        setOrientation(context, attrs);

        mRecyclerView.setLayoutParams(
                new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        mRecyclerView.addOnChildAttachStateChangeListener(enforceChildFillListener());

        // Create ScrollEventAdapter before attaching PagerSnapHelper to RecyclerView, because the
        // attach process calls PagerSnapHelperImpl.findSnapView, which uses the mScrollEventAdapter
        mScrollEventAdapter = new ScrollEventAdapter(this);
        // Create FakeDrag before attaching PagerSnapHelper, same reason as above
        mFakeDragger = new FakeDrag(this, mScrollEventAdapter, mRecyclerView);
        mPagerSnapHelper = new PagerSnapHelperImpl();
        mPagerSnapHelper.attachToRecyclerView(mRecyclerView);
        // Add mScrollEventAdapter after attaching mPagerSnapHelper to mRecyclerView, because we
        // don't want to respond on the events sent out during the attach process
        mRecyclerView.addOnScrollListener(mScrollEventAdapter);

        mPageChangeEventDispatcher = new CompositeOnPageChangeCallback(3);
        mScrollEventAdapter.setOnPageChangeCallback(mPageChangeEventDispatcher);

        // Callback that updates mCurrentItem after swipes. Also triggered in other cases, but in
        // all those cases mCurrentItem will only be overwritten with the same value.
        final OnPageChangeCallback currentItemUpdater = new OnPageChangeCallback() {
            @Override
            public void onPageSelected(int position) {
                if (mCurrentItem != position) {
                    mCurrentItem = position;
                    mAccessibilityProvider.onSetNewCurrentItem();
                }
            }

            @Override
            public void onPageScrollStateChanged(int newState) {
                if (newState == SCROLL_STATE_IDLE) {
                    updateCurrentItem();
                }
            }
        };

        // Prevents focus from remaining on a no-longer visible page
        final OnPageChangeCallback focusClearer = new OnPageChangeCallback() {
            @Override
            public void onPageSelected(int position) {
                clearFocus();
                if (hasFocus()) { // if clear focus did not succeed
                    mRecyclerView.requestFocus(View.FOCUS_FORWARD);
                }
            }
        };

        // Add currentItemUpdater before mExternalPageChangeCallbacks, because we need to update
        // internal state first
        mPageChangeEventDispatcher.addOnPageChangeCallback(currentItemUpdater);
        mPageChangeEventDispatcher.addOnPageChangeCallback(focusClearer);
        // Allow a11y to register its listeners after currentItemUpdater (so it has the
        // right data). TODO: replace ordering comments with a test.
        mAccessibilityProvider.onInitialize(mPageChangeEventDispatcher, mRecyclerView);
        mPageChangeEventDispatcher.addOnPageChangeCallback(mExternalPageChangeCallbacks);

        // Add mPageTransformerAdapter after mExternalPageChangeCallbacks, because page transform
        // events must be fired after scroll events
        mPageTransformerAdapter = new PageTransformerAdapter(mLayoutManager);
        mPageChangeEventDispatcher.addOnPageChangeCallback(mPageTransformerAdapter);

        attachViewToParent(mRecyclerView, 0, mRecyclerView.getLayoutParams());
    }

可以看到这里面做的一切操作都是初始化RecycleViewRecyclew当作子控件添加的ViewPager2容器里面

protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int width = mRecyclerView.getMeasuredWidth();
        int height = mRecyclerView.getMeasuredHeight();

        // TODO(b/70666626): consider delegating padding handling to the RecyclerView to avoid
        // an unnatural page transition effect: http://shortn/_Vnug3yZpQT
        mTmpContainerRect.left = getPaddingLeft();
        mTmpContainerRect.right = r - l - getPaddingRight();
        mTmpContainerRect.top = getPaddingTop();
        mTmpContainerRect.bottom = b - t - getPaddingBottom();

        Gravity.apply(Gravity.TOP | Gravity.START, width, height, mTmpContainerRect, mTmpChildRect);
        mRecyclerView.layout(mTmpChildRect.left, mTmpChildRect.top, mTmpChildRect.right,
                mTmpChildRect.bottom);

        if (mCurrentItemDirty) {
            updateCurrentItem();
        }
    }

onlayout方法只要负责RecyclewView放在什么位置就好。这么看来好像ViewPager2介绍就完毕了再介绍就是RecyclewView的实现原理了。接下来来看一下ViewPager2的特效扩展的实现原理。

ViewPager2.PageTransformer

如果你想实现ViewPager2切换界面时的反转效果的一些特效比如缩小放大等等,就需要继承上面的类。实现transformPage的方法,在里面依据position的变化确定RecyclewView子条目的效果。

 public interface PageTransformer {

        /**
         * Apply a property transformation to the given page.
         *
         * @param page Apply the transformation to this page
         * @param position Position of page relative to the current front-and-center
         *                 position of the pager. 0 is front and center. 1 is one full
         *                 page position to the right, and -2 is two pages to the left.
         *                 Minimum / maximum observed values depend on how many pages we keep
         *                 attached, which depends on offscreenPageLimit.
         *
         * @see #setOffscreenPageLimit(int)
         */
        void transformPage(@NonNull View page, float position);
    }

如果是咱们自己写RecyclewView话,想要实现子条目在滑动时实现一些效果应该怎么做,当然一般会想到监听滑动的回调方法取出所有显示的子View根据据屏幕中间位置来给每个View添加效果,这么想的话ViewPager2是不是也是这么实现的。来看一下源码:

 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
        mScrollHappened = true;
        updateScrollEventValues();

        if (mDispatchSelected) {
            // Drag started settling, need to calculate target page and dispatch onPageSelected now
            mDispatchSelected = false;
            boolean scrollingForward = dy > 0 || (dy == 0 && dx < 0 == mViewPager.isRtl());

            // "&& values.mOffsetPx != 0": filters special case where we're scrolling forward and
            // the first scroll event after settling already got us at the target
            mTarget = scrollingForward && mScrollValues.mOffsetPx != 0
                    ? mScrollValues.mPosition + 1 : mScrollValues.mPosition;
            if (mDragStartPosition != mTarget) {
                dispatchSelected(mTarget);
            }
        } else if (mAdapterState == STATE_IDLE) {
            // onScrolled while IDLE means RV has just been populated after an adapter has been set.
            // Contract requires us to fire onPageSelected as well.
            int position = mScrollValues.mPosition;
            // Contract forbids us to send position = -1 though
            dispatchSelected(position == NO_POSITION ? 0 : position);
        }

        // If position = -1, there are no items. Contract says to send position = 0 instead.
        dispatchScrolled(mScrollValues.mPosition == NO_POSITION ? 0 : mScrollValues.mPosition,
                mScrollValues.mOffset, mScrollValues.mOffsetPx);

        // Dispatch idle in onScrolled instead of in onScrollStateChanged because RecyclerView
        // doesn't send IDLE event when using setCurrentItem(x, false)
        if ((mScrollValues.mPosition == mTarget || mTarget == NO_POSITION)
                && mScrollValues.mOffsetPx == 0 && !(mScrollState == SCROLL_STATE_DRAGGING)) {
            // When the target page is reached and the user is not dragging anymore, we're settled,
            // so go to idle.
            // Special case and a bit of a hack when mTarget == NO_POSITION: RecyclerView is being
            // initialized and fires a single scroll event. This flags mScrollHappened, so we need
            // to reset our state. However, we don't want to dispatch idle. But that won't happen;
            // because we were already idle.
            dispatchStateChanged(SCROLL_STATE_IDLE);
            resetState();
        }
    }

其中dispatchSelected和dispatchStateChanged,一个对应回调

private void dispatchSelected(int target) {
    if (mCallback != null) {
        mCallback.onPageSelected(target);
    }
}

一个对应回调

private void dispatchScrolled(int position, float offset, int offsetPx) {
    if (mCallback != null) {
        mCallback.onPageScrolled(position, offset, offsetPx);
    }
}
而最终的回调onPageScrolled方法会回调到transformPage。如下所示:
 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        if (mPageTransformer == null) {
            return;
        }

        float transformOffset = -positionOffset;
        for (int i = 0; i < mLayoutManager.getChildCount(); i++) {
            View view = mLayoutManager.getChildAt(i);
            if (view == null) {
                throw new IllegalStateException(String.format(Locale.US,
                        "LayoutManager returned a null child at pos %d/%d while transforming pages",
                        i, mLayoutManager.getChildCount()));
            }
            int currPos = mLayoutManager.getPosition(view);
            float viewOffset = transformOffset + (currPos - position);
            mPageTransformer.transformPage(view, viewOffset);
        }
    }

果然和咱们的猜想一样。接下来再来看一下ViewPager2是正面保持和ViewPager一样缓存固定条目的子View的,RecyclewView大家都知道它是内部源码根据屏幕大小计算显示几个子View的,怎么让RecyclewView固定显示两个呢,如果一屏可以容纳5个子界面的情况下。是不是可以重写LayoutManager在布局子条目的时候加一些逻辑,答案是可以实现的。如下ViewPager2的实现如下:

 private class LinearLayoutManagerImpl extends LinearLayoutManager {
        LinearLayoutManagerImpl(Context context) {
            super(context);
        }

        @Override
        public boolean performAccessibilityAction(@NonNull RecyclerView.Recycler recycler,
                @NonNull RecyclerView.State state, int action, @Nullable Bundle args) {
            if (mAccessibilityProvider.handlesLmPerformAccessibilityAction(action)) {
                return mAccessibilityProvider.onLmPerformAccessibilityAction(action);
            }
            return super.performAccessibilityAction(recycler, state, action, args);
        }

        @Override
        public void onInitializeAccessibilityNodeInfo(@NonNull RecyclerView.Recycler recycler,
                @NonNull RecyclerView.State state, @NonNull AccessibilityNodeInfoCompat info) {
            super.onInitializeAccessibilityNodeInfo(recycler, state, info);
            mAccessibilityProvider.onLmInitializeAccessibilityNodeInfo(info);
        }

        @Override
        protected void calculateExtraLayoutSpace(@NonNull RecyclerView.State state,
                @NonNull int[] extraLayoutSpace) {
            int pageLimit = getOffscreenPageLimit();
            if (pageLimit == OFFSCREEN_PAGE_LIMIT_DEFAULT) {
                // Only do custom prefetching of offscreen pages if requested
                super.calculateExtraLayoutSpace(state, extraLayoutSpace);
                return;
            }
            final int offscreenSpace = getPageSize() * pageLimit;
            extraLayoutSpace[0] = offscreenSpace;
            extraLayoutSpace[1] = offscreenSpace;
        }

        @Override
        public boolean requestChildRectangleOnScreen(@NonNull RecyclerView parent,
                @NonNull View child, @NonNull Rect rect, boolean immediate,
                boolean focusedChildVisible) {
            return false; // users should use setCurrentItem instead
        }
    }

其中calculateExtraLayoutSpace方法就是用来计算剩余空间的,感兴趣的同学可以了解一下RecyclewView的实现原理和细节,其中pageLimit就是咱们想让他最多一下展示几个控件分方setOffscreenPageLimit方法设置的。ViewPager还有一个效果就是在手指离开屏幕时怎么保证当前那个position居中,RecyclewView默认是不带这个功能的,除非你用PagerSnapHelper,ViewPager2里面的方法是实现的了。如下所示:

   mPagerSnapHelper = new PagerSnapHelperImpl();
        mPagerSnapHelper.attachToRecyclerView(mRecyclerView);

ViewPager2ViewPager强大的一点就是可以快速划过好几屏界面,不像ViewPager只能一屏一屏的滑动。

好了,这就是ViewPager2的实现原理了。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: ViewPager和ViewPager2都是Android中的视图控件,用于实现滑动切换不同页面的功能。 ViewPager是Android SDK中的一个类,它可以在同一个Activity中展示多个Fragment,通过左右滑动来切换不同的Fragment。ViewPager可以实现无限循环滑动,但是它的性能不够好,存在一些问题,比如在嵌套使用时会出现滑动冲突等。 ViewPager2ViewPager的升级版,它是在AndroidX库中的一个类,它解决了ViewPager存在的一些问题,比如滑动冲突、性能问题等。ViewPager2支持嵌套滑动,可以实现更加灵活的布局,同时还支持横向和纵向滑动。因此,ViewPager2是更加推荐使用的视图控件。 ### 回答2: ViewPager 和 ViewPager2 都是 Android 平台上的视图容器,它们都用于实现左右滑动切换多个视图的效果。不过,它们也有一些不同的特点。 ViewPager 是 Android 系统自带的视图容器,它主要用于在同一个 Activity 中切换多个 Fragment。ViewPager 会将多个 Fragment 放置在同一个视图中,通过滑动切换 Fragment 来实现左右滑动的效果。ViewPager 比较易用、稳定,使用起来也比较简单,但是在一些功能上有一定的局限性。 ViewPager2ViewPager 的升级版,它是在 AndroidX 中新增加的一个控件。相较于 ViewPager,ViewPager2 有一些更加高级和灵活的功能。首先,ViewPager2 支持 RecyclerView.Adapter,这样用户可以通过 RecyclerView.Adapter 来实现 ViewPager2 中的数据管理,这大大提高了数据操作的灵活性。其次,ViewPager2 支持垂直滑动的效果,这使得用户可以通过上下滑动切换多个视图。此外,ViewPager2 还支持滑块(PageTransformer)和视图预加载(OffscreenPageLimit)等高级功能,让用户可以更加方便地自定义 ViewPager2 的效果和行为。 总的来说,如果只是想要简单实现左右滑动切换多个 Fragment 的效果,可以使用 ViewPager。如果需要更加高级、灵活的功能,或者需要在 ViewPager 中嵌套 RecyclerView 或其他视图控件,则可以选择 ViewPager2。同时,最好在使用 ViewPager2 时,将所有 Fragment 替换为 RecyclerView,这样能够充分利用 ViewPager2 的强大功能。 ### 回答3: ViewPager和ViewPager2Android平台上常用的 View容器 组件。它们最主要的作用是管理多个子view的滑动显示,类似于滑动的页面。 ViewPager从Android API Level 11就被引入,它支持从左往右滑动查看多个子视图,以轻松实现流畅的“屏幕滑动”效果,常见的使用场景包括相册、图库、图片轮播图等。在使用ViewPager时,开发者需要自己实现适配器,根据需要返回子View。且ViewPager中每个页面的宽度是相等的,无法进行自由的布局。 而ViewPager2是新增的一个组件,它是AndroidX中的一部分,于2019年发布。ViewPager2相对于ViewPager的最大改进就在于支持不同宽度的页面。除了滑动方向以外,ViewPager2还支持从RecyclerView中使用适配器,从而不仅仅可以使用View,还可以使用任何RecyclerView的特性和布局(如GridLayoutManager等)。另一个重要的改进是支持了多层嵌套,并且同步了更多的触摸事件,增强了原生的滑动手势支持。 总之,ViewPager2ViewPager的升级版,它具有更多灵活的布局和更好的性能。开发者可以根据自己的需求选择使用ViewPager或ViewPager2,相信在未来的Android开发中,ViewPager2会成为首选。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值