深入了解ViewPager2的使用
我赌一包辣条 人气:0一、ViewPager2的新特性
ViewPager2从名字就可以看出来它是ViewPager的升级版,既然是升级版那么它相比ViewPager有哪些新功能和哪些API变化呢?我们接着往下看。
1.ViewPager2新特性
- 基于RecyclerView实现。这意味着RecyclerView的优点将会被ViewPager2所继承。
- 支持竖直滑动。只需要一个参数就可以改变滑动方向。
- 支持关闭用户输入。通过setUserInputEnabled来设置是否禁止用户滑动页面。
- 支持通过编程方式滚动。通过fakeDragBy(offsetPx)代码模拟用户滑动页面。
- CompositePageTransformer 支持同时添加多个PageTransformer。
- 支持DiffUtil ,可以添加数据集合改变的item动画。
- 支持RTL (right-to-left)布局。我觉得这个功能对国内开发者来说可能用处不大..
2.相比ViewPager变化的API
ViewPager2相比ViewPager做了哪些改变呢?研究了一番之后我大概列出以下几点:
- ViewPager2与ViewPager同是继承自ViewGrop,但是ViewPager2被声明成了final。意味着我们不可能再像ViewPager一样通过继承来修改ViewPager2的代码。
- FragmentStatePagerAdapter被FragmentStateAdapter 替代
- PagerAdapter被RecyclerView.Adapter替代
- addPageChangeListener被registerOnPageChangeCallback。我们知道ViewPager的addPageChangeListener接收的是一个OnPageChangeListener的接口,而这个接口中有三个方法,当想要监听页面变化时需要重写这三个方法。而ViewPager2的registerOnPageChangeCallback方法接收的是一个叫OnPageChangeCallback的抽象类,因此我们可以选择性的重写需要的方法即可。
- 移除了setPargeMargin方法。
以上所罗列的新特性和API可能并不完整,如有疏漏可以留言补充。
二、开启ViewPager2之旅
ViewPager2位于androidx包下,也就是它不像ViewPager一样被内置在系统源码中。因此,使用ViewPager2需要额外的添加依赖库。另外,android support中不包含ViewPager,也就是要使用ViewPager2必须迁移到androidx才可以。
1.添加依赖,目前ViewPager2的最新版本是1.0.0:
dependencies { implementation "androidx.viewpager2:viewpager2:1.0.0" }
2.ViewPager2布局文件:
<androidx.viewpager2.widget.ViewPager2 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"> <TextView android:id="@+id/tv_text" android:background="@color/colorPrimaryDark" android:layout_width="match_parent" android:layout_height="280dp" android:gravity="center" android:textColor="#ffffff" android:textSize="22sp" /> </LinearLayout>
4.在Activity中为ViewPager设置Adapter
很简单就完成了一个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的参数,当参数值为正数时表示向前一个页面滑动,当值为负数时表示向下一个页面滑动。
下面来看下效果图:
演示图中禁止了用户输入,通过按钮点击可以模拟用户滑动。
三、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 } } companion object { const val DEFAULT_MIN_SCALE = 0.85f const val DEFAULT_CENTER = 0.5f } }
4.ViewPager2的一屏多页效果
在ViewPager2的官方Sample上看到了ViewPager2的一屏多页可以通过为RecyclerView设置Padding来实现。代码如下:
viewPager2.apply { offscreenPageLimit=1 val recyclerView= getChildAt(0) as RecyclerView recyclerView.apply { val padding = resources.getDimensionPixelOffset(R.dimen.dp_10) + resources.getDimensionPixelOffset(R.dimen.dp_10) // setting padding on inner RecyclerView puts overscroll effect in the right place setPadding(padding, 0, padding, 0) clipToPadding = false } } val compositePageTransformer = CompositePageTransformer() compositePageTransformer.addTransformer(ScaleInTransformer()) compositePageTransformer.addTransformer(MarginPageTransformer(resources.getDimension(R.dimen.dp_10).toInt())) viewPager2.setPageTransformer(compositePageTransformer)
最后,我们来看下效果
四、ViewPager2与Fragment
我们前面也已经提到了ViewPager2中新增的FragmentStateAdapter 替代了ViewPager的FragmentStatePagerAdapter。那么来我们就用ViewPager2来实现一个Activity中嵌套Fragment的实例。
1.Activity的layout中添加ViewPager2
<androidx.viewpager2.widget.ViewPager2 android:id="@+id/vp_fragment" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_above="@id/rg_tab" />
2.实现FragmentStateAdapter
class AdapterFragmentPager(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) { private val fragments: SparseArray<BaseFragment> = SparseArray() init { fragments.put(PAGE_HOME, HomeFragment.getInstance()) fragments.put(PAGE_FIND, PageFragment.getInstance()) fragments.put(PAGE_INDICATOR, IndicatorFragment.getInstance()) fragments.put(PAGE_OTHERS, OthersFragment.getInstance()) } override fun createFragment(position: Int): Fragment { var fragment: Fragment when (position) { PAGE_HOME -> { if (fragments.get(PAGE_HOME) == null) { fragment = HomeFragment.getInstance(); fragments.put(PAGE_HOME, fragment) } else { fragment = fragments.get(PAGE_HOME) } } PAGE_FIND -> { if (fragments.get(PAGE_FIND) == null) { fragment = PageFragment.getInstance(); fragments.put(PAGE_FIND, fragment) } else { fragment = fragments.get(PAGE_FIND) } } PAGE_INDICATOR -> { if (fragments.get(PAGE_INDICATOR) == null) { fragment = IndicatorFragment.getInstance(); fragments.put(PAGE_INDICATOR, fragment) } else { fragment = fragments.get(PAGE_INDICATOR) } } PAGE_OTHERS -> { if (fragments.get(PAGE_OTHERS) == null) { fragment = OthersFragment.getInstance(); fragments.put(PAGE_OTHERS, fragment) } else { fragment = fragments.get(PAGE_OTHERS) } } else -> { if (fragments.get(PAGE_HOME) == null) { fragment = HomeFragment.getInstance(); fragments.put(PAGE_HOME, fragment) } else { fragment = fragments.get(PAGE_HOME) } } } return fragment } override fun getItemCount(): Int { return fragments.size() } companion object { const val PAGE_HOME = 0 const val PAGE_FIND = 1 const val PAGE_INDICATOR = 2 const val PAGE_OTHERS = 3 } }
3.在Activity中为ViewPager2设置FragmentStateAdapter
vp_fragment.adapter = AdapterFragmentPager(this) vp_fragment.offscreenPageLimit = 3 vp_fragment.isUserInputEnabled=false
五、ViewPager2与TabLayout
TabLayout也是项目中经常用到的一个控件,它通常会与ViewPager一起出现。那么对于ViewPager2应该怎么使用Tablayout呢?这需要我们认识一个新类TabLayoutMediator,这个类是在material-1.2.0中新增的一个类,目前material包的最新版本是1.2.0-alpha03,因此需要我们单独引入这个包,依赖如下:
implementation 'com.google.android.material:material:1.2.0-alpha03'
TabLayoutMediator的构造方法接收三个参数,第一个参数为TabLayout;第二个参数为ViewPager2;第三个参数是TabConfigurationStrategy,这是一个接口,该接口中有一个方法onConfigureTab(@NonNull TabLayout.Tab tab, int position),第一个参数是当前Tab,第二个当前position,源码如下:
public interface TabConfigurationStrategy { /** * Called to configure the tab for the page at the specified position. Typically calls {@link * TabLayout.Tab#setText(CharSequence)}, but any form of styling can be applied. * * @param tab The Tab which should be configured to represent the title of the item at the given * position in the data set. * @param position The position of the item within the adapter's data set. */ void onConfigureTab(@NonNull TabLayout.Tab tab, int position); }
接下来我们便可以通过TabLayoutMediator将TabLayout与ViewPager2关联起来了:
TabLayoutMediator(tab_layout, view_pager) { tab, position -> // 为Tab设置Text tab.text = Card.DECK[position].toString() }.attach()
使用起来非常简单,实现效果如下图所示:
六、小结
本篇文章我们认识了ViewPager2的新特性以及其用法。总得来说ViewPager2相比ViewPager不管在性能上还是在功能上都有了很大的提升。因此,我相信在不久的未来ViewPager2必定会取代ViewPager。那么,你是否已经考虑将ViewPager2用到你的项目中了呢?
最后再来给大家推荐一下BannerViewPager。这是一个基于ViewPager实现的具有强大功能的无限轮播库。在未来,我会在BannerViewPager 3.0版本中用ViewPager2来重构代码。欢迎大家到GitHub关注BannerViewPager 。
第四节中ViewPager2与Fragment的代码见:
加载全部内容