ViewPager2相较于ViewPager作出了很多改进,采用了RecyclerView作为内部实现,提高了应用的灵活性,使得横向翻页效果可以使用它来实现。最终整体效果如下:
下面先对其滑动原理进行介绍:
假设我们在ViewPager的Adapter中添加了4个页面的内容:
当前可见的页面是页面id 0,它对应的position是0,注意这里的position与官方文档中所描述的the position index of xxx一致,position与页面的id号(显示在页面上的红色数字)并非是一一对应的。官方文档对于position的描述如下:
当我们进行了一次向左滑动后,页面变为:
可以看到,对于position来说,当前可见的页面(即id为1的页面)所对应的position始终为0,在这页之前的页的position小于0,在这页之后的大于0。
如果不作任何改动,原生的显示效果如下:
显然,这样的效果达不到我们的要求,我们需要将左边的页面叠放到当前页面之下,并随屏幕滑动的动作依次移开。下面的动图展示了这一过程:
图中,立方体为页面的堆叠区域,其顶层(z坐标为0)为当前可见的页面。下方为当前状态viewpager内部的position变化情况。
幸运的是,viewpager2为我们提供了相应的接口来实现这两个目标:
ViewPager2.PageTransformer
abstract void transformPage(@NonNull View page, float position)
这里的参数 page为在adapter中设置的viewholder所对应的itemview;参数 position为上文所提到的页面的索引,但这里的position不是整数,而是随着屏幕的滑动而逐渐变化。
接口transformPage会在初次create的时候以及每次页面被滑动时调用(滑动时将调用多次),这就同时满足了我们既要对初始状态进行改变又要在移动中进行变动的要求。
在下图中,我给出了这种改动的规律:
如图所示,页面可以被分为两类,一类是 pos<=0(即 i<=0) 的页面,这些页面都处于表面(蓝色立方体左侧的平面上,符合viewpager原生的运动规律),第二类是 pos>0(即i>0) 的页面,这些页面处于堆叠状态(蓝色立方体内部,需要将x轴向的运动变为z轴方向的运动)。
这里以页面3为例:
需要将其向左移动其宽度w的距离,使其叠放在页面2之下,故有:
Δ X 3 ΔX_3 ΔX3= − w ∗ 1 -w*1 −w∗1
若还存在页面3右侧的页面4,则:
Δ X 4 ΔX_4 ΔX4= − w ∗ 2 -w*2 −w∗2
以此类推,可得:
观察上面的公式,可知当i=0时,将取消x轴向的运动,于是只需要在此基础上添加ΔZ即可,通过上面的原理分析易得:
这样我们就得到了适用与普遍情形的堆叠移动的变化公式。
前面讲解了viewpager移动和堆叠的原理,要具体实现这样的效果,必须清晰理解接口transformPage是如何调用的。
我在viewholder中添加了textview page_id以便区分不同的页面,实验所用到的关键代码如下,代码将当前页面的id和position打印到logcat中:
@Override
public void transformPage(@NonNull View page, float position) {
TextView viewId = page.findViewById(R.id.page_id);
String s_id = viewId.getText().toString();
Log.d("transformPage", s_id + " | pos = " + position);
}
上面的讲解已经比较详细了,我们得到下面的代码就很自然了:
pageView.setPageTransformer(new ViewPager2.PageTransformer() {
@Override
public void transformPage(@NonNull View page, float position) {
TextView viewId = page.findViewById(R.id.page_id);
String s_id = viewId.getText().toString();
if (position <= 0.0f) {
//被滑动的那页及之前全部的已被划走的页
//Log.d("transformPage [surface]",s_id+ " | pos = "+position);
page.setTranslationX(0.0f);
page.setTranslationZ(0.0f);
} else {
//在被滑动页下方的页
//Log.d("transformPage [under]",s_id+ " | pos = "+position);
//设置每一页相对于【其自身左侧】的偏移
page.setTranslationX((-page.getWidth() * position));
page.setTranslationZ(-position);
}
}
});
此处的 setTranslationX 和 setTranslationZ 即对页面施加一个ΔX、ΔZ的运动,需要注意的是,这里的坐标系是每个页面独立且相对于其左边缘的。
在页面添加阴影会使得堆叠滑动效果更加明显,这里采用 androidx.cardview.widget.CardView 作为布局,通过设置属性cardBackgroundColor 添加阴影:
app:cardBackgroundColor="#CCE8CF"
这么做之后你会发现阴影似乎并没有显示出来,这是因为阴影在边缘区域被隐藏了,还需要在CardView下添加如下属性,;
android:layout_marginEnd="2dp"
至此,简单的堆叠滑动效果就实现了,这只是横向翻页效果实现的第一步。下面第二章我将会讲解如何将一个大的章节文本按照View的大小分为多个子页。