ViewPager2从名字就可以看出来它是ViewPager的升级版,既然是升级版那么它相比ViewPager有哪些新变化呢?
添加依赖,目前ViewPager2的最新稳定版本是1.0.0(20210525)。1.1.0-alpha01 不稳定
implementation "androidx.viewpager2:viewpager2:1.0.0"
先来看看目录结构
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