implementation "androidx.viewpager2:viewpager2:1.0.0"
}
2.ViewPager2布局文件:
<androidx.viewpager2.widget.ViewPager2br/>android:id=”@+id/view_pager”
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
3.ViewPager2的Adapter
因为ViewPager2内部封装的是RecyclerView,因此它的Adapter也就是RecyclerView的Adapter。
class MyAdapter : RecyclerView.Adapter<MyAdapter.PagerViewHolder>() {
private var mList: List<Int> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PagerViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_page, parent, false)
return PagerViewHolder(itemView)
}
override fun onBindViewHolder(holder: PagerViewHolder, position: Int) {
holder.bindData(mList[position])
}
fun setList(list: List<Int>) {
mList = list
}
override fun getItemCount(): Int {
return mList.size
}
// ViewHolder需要继承RecycleView.ViewHolder
class PagerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val mTextView: TextView = itemView.findViewById(R.id.tv_text)
private var colors = arrayOf("#CCFF99","#41F1E5","#8D41F1","#FF99CC")
fun bindData(i: Int) {
mTextView.text = i.toString()
mTextView.setBackgroundColor(Color.parseColor(colors[i]))
}
}
}
item_page中代码如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
4.在Activity中为ViewPager设置Adapter:
val viewPager2 = findViewById<ViewPager2>(R.id.view_pager)
val myAdapter = MyAdapter()
myAdapter.setList(data)
viewPager2.adapter = myAdapter
很简单就完成了一个ViewPager的功能,来看下效果怎么样:
5.ViewPager2竖直滑动
接下来我们通过一行代码为其设置竖直滑动
viewPager2.orientation = ViewPager2.ORIENTATION_VERTICAL
竖直滑动用ViewPager是很难实现的,而通过ViewPager2只需要设置一个参数即可。来看下效果:
6.页面滑动事件监听
上文已经提到过了,我们为ViewPager设置页面滑动的监听事件需要重写三个方法,而为ViewPager2设置监听事件只需要重写需要的方法即可,因为ViewPager2中OnPageChangeCallback是一个抽象类。
viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
Toast.makeText(this@MainActivity, "page selected $position", Toast.LENGTH_SHORT).show()
}
})
7.setUserInputEnabled与fakeDragBy
我们知道,在使用ViewPager的时候想要禁止用户滑动需要重写ViewPager的onInterceptTouchEvent。而ViewPager2被声明为了final,我们无法再去继承ViewPager2。那么我们应该怎么禁止ViewPager2的滑动呢?其实在ViewPager2中已经为我们提供了这个功能,只需要通过setUserInputEnabled即可实现。
viewPager2.isUserInputEnabled = false
同时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的参数,当参数值为正数时表示向前一个页面滑动,当值为负数时表示向下一个页面滑动。 下面来看下效果图: 演示图中禁止了用户输入,通过按钮点击可以模拟用户滑动。
8.ViewPager2的offScreenPageLimit
offScreenPageLimit在ViewPager中就已经存在,这个参数用来控制ViewPager左右两端预加载页面的个数。为了保证ViewPager的流畅性,offScreenPageLimit被强制规定为大于0的数,即使我们将其设置为0,ViewPager内部也会将其改为1。因此ViewPager就被强制左右两边至少加载一个页面。这也是一直被广大开发者所诟病的一个问题。而在ViewPager2中针对这一问题做了优化。我们点开ViewPager2的源码来看下:
VewPager2
private @OffscreenPageLimit int mOffscreenPageLimit = OFFSCREEN_PAGE_LIMIT_DEFAULT;
/**
- Value to indicate that the default caching mechanism of RecyclerView should be used instead
- of explicitly prefetch and retain pages to either side of the current page.
- @see #setOffscreenPageLimit(int)
*/
public static final int OFFSCREEN_PAGE_LIMIT_DEFAULT = -1;
/* @hide /br/>@SuppressWarnings(“WeakerAccess”)
@RestrictTo(LIBRARY_GROUP_PREFIX)br/>@Retention(SOURCE)
@IntDef({OFFSCREEN_PAGE_LIMIT_DEFAULT})
@IntRange(from = 1)
public @interface OffscreenPageLimit {
}
可以看到在ViewPager2中offScreenPageLimit的默认值被设置为了-1,而且offScreenPageLimit这个成员变量被一个名为@OffscreenPageLimit的注解所修饰,而在这个注解强制要求int的范围是大于等于1的。什么?ViewPager2的预加载页面难道也必须大于等于1?那这相比ViewPager有什么区别呢?先别着急,其实最大的区别就在这个OFFSCREEN_PAGE_LIMIT_DEFAULT上,这个值被设置为-1,那么它代表什么意思呢?我们可以从ViewPager2源码的注释中找出一些端倪
/**
- <p>Set the number of pages that should be retained to either side of the currently visible
- page(s). Pages beyond this limit will be recreated from the adapter when needed. Set this to
- {@link #OFFSCREEN_PAGE_LIMIT_DEFAULT} to use RecyclerView’s caching strategy. The given value
- must either be larger than 0, or {@code #OFFSCREEN_PAGE_LIMIT_DEFAULT}.</p>
- <p>Pages within {@code limit} pages away from the current page are created and added to the
- view hierarchy, even though they are not visible on the screen. Pages outside this limit will
- be removed from the view hierarchy, but the {@code ViewHolder}s will be recycled as usual by
- {@link RecyclerView}.</p>
- <p>This is offered as an optimization. If you know in advance the number of pages you will
- need to support or have lazy-loading mechanisms in place on your pages, tweaking this setting
- can have benefits in perceived smoothness of paging animations and interaction. If you have a
- small number of pages (3-4) that you can keep active all at once, less time will be spent in
- layout for newly created view subtrees as the user pages back and forth.</p>
- <p>You should keep this limit low, especially if your pages have complex layouts. By default
- it is set to {@code OFFSCREEN_PAGE_LIMIT_DEFAULT}.</p>
- @param limit How many pages will be kept offscreen on either side. Valid values are all
- values {@code >= 1} and {@link #OFFSCREEN_PAGE_LIMIT_DEFAULT}
- @throws IllegalArgumentException If the given limit is invalid
- @see #getOffscreenPageLimit()
*/
public void setOffscreenPageLimit(@OffscreenPageLimit int limit) {
if (limit < 1 && limit != OFFSCREEN_PAGE_LIMIT_DEFAULT) {
throw new IllegalArgumentException(
"Offscreen page limit must be OFFSCREEN_PAGE_LIMIT_DEFAULT or a number > 0");
}
mOffscreenPageLimit = limit;
// Trigger layout so prefetch happens through getExtraLayoutSize()
mRecyclerView.requestLayout();
}
从这段对setOffscreenPageLimit(int)方法的注释中我们可以看到,当setOffscreenPageLimit被设置为OFFSCREEN_PAGE_LIMIT_DEFAULT时候会使用RecyclerView的缓存机制。那么我们就来在ViewPager2中尝试下加载Fragment是一种怎样的效果吧。 首先我们在ViewPager中添加多个Fragment,并且setOffscreenPageLimit使用默认值,然后再Fragment声明周期中打印出日志,代码不再贴出,直接看日志打印的内容: 从日志中可以看出来初始化的只有第一个Fragment。当我们滑动到第二个页面的时候,打印日志如下:
可以看到第二个页面在滑动的时候才被初始化,由此我们可以看出在ViewPager2中默认的OffscreenPageLimit是不会进行页面预加载的。 接下来我们将offscreenPageLimit值改为1,再来看下输出日志:
此时可以看到offscreenPageLimit设置为1后会预加载进来一个页面,和ViewPager几乎是一样的效果。总之,ViewPager2对于ViewPager的预加载机制做了优化,使得体验上变得更好。关于ViewPager2的offScreenLimit在本篇文章中不再深究,我会在下篇文章中深入探讨。
三、ViewPager2的PageTransformer
相比ViewPager,ViewPager2的Transformer功能有了很大的扩展。ViewPager2不仅可以通过PageTransformer用来设置页面动画,还可以用PageTransformer设置页面间距以及同时添加多个PageTransformer。接下来我们就来认识下ViewPager2的PageTransformer吧!
1.setPageMargin
在第一章中我们提到了ViewPager2移除了setPageMargin方法,那么怎么为ViewPager2设置页面间距呢?其实在ViewPager2中为我们提供了MarginPageTransformer,我们可以通过ViewPager2的setPageTransformer方法来设置页面间距。代码如下:
viewPager2.setPageTransformer(MarginPageTransformer(resources.getDimension(R.dimen.dp_10).toInt()))
上述代码我们为ViewPager2设置了10dp的页面间距。效果如下:
2.认识CompositePageTransformer
这个时候我们应该有个疑问,为ViewPager2设置了页面间距后如果还想设置页面动画的Transformer怎么办呢?这时候就该CompositePageTransformer出场了。从名字上也可以看出来它是一个组合的PageTransformer。没错,CompositePageTransformer实现了PageTransformer接口,同时在其内部维护了一个List集合,我们可以将多个PageTransformer添加到CompositePageTransformer中。
val compositePageTransformer = CompositePageTransformer()
compositePageTransformer.addTransformer(ScaleInTransformer())
compositePageTransformer.addTransformer(MarginPageTransformer(resources.getDimension(R.dimen.dp_10).toInt()))
viewPager2.setPageTransformer(compositePageTransformer)
上述代码中我们通过CompositePageTransformer为ViewPager设置了MarginPageTransformer和一个页面缩放的ScaleInTransformer。来看下效果:
3.ViewPager2中的PageTransformer
PageTransformer是一个位于ViewPager2中的接口,因此ViewPager2的PageTransformer是独立于ViewPager的,它与ViewPager的PageTransformer没有任何关系。虽然如此,却不必担心。因为ViewPager2的PageTransformer和ViewPager的PageTransformer实现方式一模一样。我们看下上一小节中用到的ScaleInTransformer:
class ScaleInTransformer : ViewPager2.PageTransformer {
private val mMinScale = DEFAULT_MIN_SCALE
override fun transformPage(view: View, position: Float) {
view.elevation = -abs(position)
val pageWidth = view.width
val pageHeight = view.height
view.pivotY = (pageHeight / 2).toFloat()
view.pivotX = (pageWidth / 2).toFloat()
if (position < -1) {
view.scaleX = mMinScale
view.scaleY = mMinScale
view.pivotX = pageWidth.toFloat()
} else if (position <= 1) {
if (position < 0) {
val scaleFactor = (1 + position) (1 – mMinScale) + mMinScale
view.scaleX = scaleFactor
view.scaleY = scaleFactor
view.pivotX = pageWidth (DEFAULT_CENTER + DEFAULT_CENTER -position)
} else {
val scaleFactor = (1 – position) (1 – mMinScale) + mMinScale
view.scaleX = scaleFactor
view.scaleY = scaleFactor
view.pivotX = pageWidth ((1 – position) DEFAULT_CENTER)
}
} else {
view.pivotX = 0f
view.scaleX = mMinScale
view.scaleY = mMinScale
最后
如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。
最后针对Android程序员,我这边给大家整理了一些资料,包括不限于高级UI、性能优化、移动架构师、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!
需要资料的朋友可以点击我的GitHub免费领取
原创文章,作者:kirin,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/219612.html