前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入了解ViewPager2

深入了解ViewPager2

作者头像
提莫队长
发布2021-06-29 10:15:33
1.5K0
发布2021-06-29 10:15:33
举报
文章被收录于专栏:刘晓杰刘晓杰

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

代码语言:javascript
复制
implementation "androidx.viewpager2:viewpager2:1.0.0"

先来看看目录结构

viewpager2目录结构.jpg

1.adapter

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

(1) StatefulAdapter
代码语言:javascript
复制
/**
 * {@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
代码语言:javascript
复制
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

代码语言:javascript
复制
    @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。先来看一下注释

代码语言:javascript
复制
    /**
     * 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)

代码语言:javascript
复制
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")
    }
}

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

代码语言:javascript
复制
        findViewById<TextView>(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方法来设置页面间距。代码如下:

代码语言:javascript
复制
viewPager2.setPageTransformer(MarginPageTransformer(resources.getDimension(R.dimen.dp_20).toInt()))

当然也可以设置多个 PageTransformer。

代码语言:javascript
复制
val compositePageTransformer = CompositePageTransformer()
compositePageTransformer.addTransformer(MarginPageTransformer(resources.getDimension(R.dimen.dp_20).toInt()))
viewPager2.setPageTransformer(compositePageTransformer)
(3) 一屏多页

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

代码语言:javascript
复制
        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) 竖直滑动
代码语言:javascript
复制
viewPager2.orientation = ViewPager2.ORIENTATION_VERTICAL
(2) 禁止用户滑动

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

代码语言:javascript
复制
viewPager2.isUserInputEnabled = false
(3) 模拟拖拽

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

代码语言:javascript
复制
    fun fakeDragBy(view: View) {
        viewPager2.beginFakeDrag()
        if (viewPager2.fakeDragBy(-310f))
            viewPager2.endFakeDrag()
    }

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

(4) 与TabLayout结合使用
代码语言:javascript
复制
implementation 'com.google.android.material:material:1.4.0-alpha01'

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

4 总结

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

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.adapter
  • 2.widget
  • 3.零碎功能
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档