深入了解ViewPager2

ViewPager2从名字就可以看出来它是ViewPager的升级版,既然是升级版那么它相比ViewPager有哪些新变化呢?
添加依赖,目前ViewPager2的最新稳定版本是1.0.0(20210525)。1.1.0-alpha01 不稳定

implementation "androidx.viewpager2:viewpager2:1.0.0"

先来看看目录结构


viewpager2目录结构.jpg

1.adapter

可以看到,熟悉的FragmentStatePagerAdapter被FragmentStateAdapter 替代。

(1) StatefulAdapter
/**
 * {@link ViewPager2} adapters should implement this interface to be called during
 * {@link View#onSaveInstanceState()} and {@link View#onRestoreInstanceState(Parcelable)}
 */
public interface StatefulAdapter {
    /** Saves adapter state */
    @NonNull Parcelable saveState();

    /** Restores adapter state */
    void restoreState(@NonNull Parcelable savedState);
}

注释上很明确,是用来处理 onSaveInstanceState 和 onRestoreInstanceState的。基本上就等于是横竖屏切换时的状态的保存和恢复。暂时可以不管

(2) FragmentViewHolder
public final class FragmentViewHolder extends ViewHolder {
    ......
    @NonNull static FragmentViewHolder create(@NonNull ViewGroup parent) {
        FrameLayout container = new FrameLayout(parent.getContext());
        container.setLayoutParams(
                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT));
        container.setId(ViewCompat.generateViewId());
        container.setSaveEnabled(false);
        return new FragmentViewHolder(container);
    }
    .....
}

很明显,继承自 RecyclerView.ViewHolder,并且 create 一个空的 FrameLayout

(3) FragmentStateAdapter

这是重头戏,代码过长,有些就不贴出来了
首先一个,它继承自 RecyclerView.Adapter,这意味着RecyclerView的优点和功能将会被 FragmentStateAdapter 拥有.看一下 onBindViewHolder

    @Override
    public final void onBindViewHolder(final @NonNull FragmentViewHolder holder, int position) {
        ......
        ensureFragment(position);//***************

        /** Special case when {@link RecyclerView} decides to keep the {@link container}
         * attached to the window, but not to the view hierarchy (i.e. parent is null) */
        final FrameLayout container = holder.getContainer();
        if (ViewCompat.isAttachedToWindow(container)) {
            if (container.getParent() != null) {
                throw new IllegalStateException("Design assumption violated.");
            }
            container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
                @Override
                public void onLayoutChange(View v, int left, int top, int right, int bottom,
                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
                    if (container.getParent() != null) {
                        container.removeOnLayoutChangeListener(this);
                        placeFragmentInViewHolder(holder);//***************
                    }
                }
            });
        }

        gcFragments();
    }

主要是两个地方。一个是 ensureFragment,一个是 placeFragmentInViewHolder。ensureFragment 里面调用了 createFragment,placeFragmentInViewHolder 里面调用了 beginTransaction add 来添加。
整个代码其实不难理解,主要是有个内部类 FragmentMaxLifecycleEnforcer。先来看一下注释

    /**
     * Pauses (STARTED) all Fragments that are attached and not a primary item.
     * Keeps primary item Fragment RESUMED.
     */

翻译过来就是把当前 Fragment 设置为 RESUMED,其他的设置为 STARTED。具体实现在 updateFragmentMaxLifecycle 函数里面。很明显就是控制生命周期的。我们知道,以前使用Fragment时,如果使用show/hide切换Fragment显示,由于Fragment都attach了,当Activity生命周期走到onResume的时候,会触发所有Fragment执行onResume。显然没必要这么做。于是就多了这么一个生命周期控制函数。具体怎么操作呢。先看代码
先定义两个Fragment,如下(FirstFragment SecondFragment 用类似的log)

class FirstFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_first, container, false)
    }

    override fun onHiddenChanged(hidden: Boolean) {
        super.onHiddenChanged(hidden)
        Log.e("Fragment", "FirstFragment - onHiddenChanged")
    }

    override fun onPause() {
        super.onPause()
        Log.e("Fragment", "FirstFragment - onPause")
    }

    override fun onStart() {
        super.onStart()
        Log.e("Fragment", "FirstFragment - onStart")
    }

    override fun onResume() {
        super.onResume()
        Log.e("Fragment", "FirstFragment - onResume")
    }
}

接下来我们用下面这段代码测试

        findViewById(R.id.single).setOnClickListener {
            val beginTransaction = supportFragmentManager.beginTransaction()
            beginTransaction.hide(firstFragment)
            beginTransaction.hide(secondFragment)

            //beginTransaction.setMaxLifecycle(firstFragment, androidx.lifecycle.Lifecycle.State.STARTED)
            beginTransaction.show(firstFragment)
            beginTransaction.commit()
        }

当没有给 firstFragment 设置 STARTED 的时候,进入二级页面会调用 onPause,然后返回时会调用 onResume。当设置以后这两个函数都不会掉用。因此,我们可以简化每个Fragment里面的函数调用。其实 FragmentStateAdapter 内部就是这样实现的,也就是它的注释。当前Fragment执行到onResume,非当前Fragment执行到onStart

2.widget

(1) OnPageChangeCallback

viewpager2中改为了 registerOnPageChangeCallback

(2) PageTransformer

ViewPager2移除了setPageMargin。那么怎么为ViewPager2设置页面间距呢?其实在ViewPager2中为我们提供了MarginPageTransformer,我们可以通过ViewPager2的setPageTransformer方法来设置页面间距。代码如下:

viewPager2.setPageTransformer(MarginPageTransformer(resources.getDimension(R.dimen.dp_20).toInt()))

当然也可以设置多个 PageTransformer。

val compositePageTransformer = CompositePageTransformer()
compositePageTransformer.addTransformer(MarginPageTransformer(resources.getDimension(R.dimen.dp_20).toInt()))
viewPager2.setPageTransformer(compositePageTransformer)
(3) 一屏多页

可以通过为RecyclerView设置Padding来实现。

        viewPager2.apply {
            offscreenPageLimit = 1
            val recyclerView = getChildAt(0) as RecyclerView
            recyclerView.apply {
                val padding = 50
                // setting padding on inner RecyclerView puts overscroll effect in the right place
                setPadding(padding, 0, padding, 0)
                clipToPadding = false
            }
        }
        val compositePageTransformer = CompositePageTransformer()
        compositePageTransformer.addTransformer(MarginPageTransformer(50))
        viewPager2.setPageTransformer(compositePageTransformer)

3.零碎功能

(1) 竖直滑动
viewPager2.orientation = ViewPager2.ORIENTATION_VERTICAL
(2) 禁止用户滑动

我们知道,在使用ViewPager的时候想要禁止用户滑动需要重写ViewPager的onInterceptTouchEvent。而ViewPager2被声明为了final,我们无法再去继承ViewPager2。那么我们应该怎么禁止ViewPager2的滑动呢?其实在ViewPager2中已经为我们提供了这个功能,只需要通过setUserInputEnabled即可实现

viewPager2.isUserInputEnabled = false
(3) 模拟拖拽

ViewPager2新增了一个fakeDragBy的方法。通过这个方法可以来模拟拖拽。在使用fakeDragBy前需要先beginFakeDrag方法来开启模拟拖拽。fakeDragBy会返回一个boolean值,true表示有fake drag正在执行,而返回false表示当前没有fake drag在执行。我们通过代码来尝试下

    fun fakeDragBy(view: View) {
        viewPager2.beginFakeDrag()
        if (viewPager2.fakeDragBy(-310f))
            viewPager2.endFakeDrag()
    }

需要注意到是fakeDragBy接受一个float的参数,当参数值为正数时表示向前一个页面滑动,当值为负数时表示向下一个页面滑动。
具体实现在 FakeDrag 里面

(4) 与TabLayout结合使用
implementation 'com.google.android.material:material:1.4.0-alpha01'

里面有个TabLayoutMediator。用法也简单,这里就不展开了

4 总结

和ViewPager最大的不同就是多了 FragmentMaxLifecycleEnforcer ,可以控制Fragment的生命周期。因此,如果用 FragmentStateAdapter 的话,一定要注意生命周期

参考文献
学不动也要学!深入了解ViewPager2

你可能感兴趣的:(深入了解ViewPager2)