深入了解ViewPager2的使用

一、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的代码见:

BannerViewPager

以上就是深入了解ViewPager2的使用的详细内容,更多关于ViewPager2 使用教程的资料请关注我们其它相关文章!

(0)

相关推荐

  • android使用viewpager计算偏移量实现选项卡功能

    本文实例为大家分享了android实现选项卡功能,通过计算偏移量,设置tetxview和imageView的对应值,一些color的值读者自己去补充 实现效果图: (1)简单写一个主界面的布局activity_main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/andr

  • android使用ViewPager实现图片自动切换

    本文实现viewpager图片轮播的功能.左右滑动的时候能够流畅的切换图片.并且没有边界限制 1.activity_main.xml布局 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent&q

  • Android自定义引导玩转ViewPager的方法详解

    ViewPager简介: ViewPager(android.support.v4.view.ViewPager)是android扩展包v4包中的类,这个类可以让用户左右切换当前的view,实现滑动切换的效果. 注意: ViewPager类直接继承了ViewGroup类,也就是说它和我们经常打交道的LinearLayout一样,都是一个容器,需要在里面添加我们想要显示的内容. ViewPager类需要一个PagerAdapter适配器类给它提供数据,这个和ListView类似. ViewPage

  • Android自定义ViewPager实现纵向滑动翻页效果

    抖音几乎已经成为了我们日常生活中使用比较频繁的App,无聊之时或工作之后可以刷一刷短视频来供我们娱乐与放松.看到抖音的视屏切换效果,觉得用ViewPager可以做出一样的效果.想一想之前用的ViewPager都是横向切换的,虽然很经常用,但是从来没实现过竖向的切换效果,说做就做吧. 我们先看一波效果图: 那么,要想实现这样的效果,当然是自定义ViewPager啦.问了一下度娘,看到有这样一种思路: 首先,把Touch事件的x,y坐标做一下交换,从原先的x坐标差值转变成y坐标的差值,正符合了我们手

  • Android使用viewpager实现画廊式效果

    本文实例为大家分享了Android使用viewpager实现画廊式效果的具体代码,供大家参考,具体内容如下 先看一下效果 1.创建一个自定义类 ZoomOutPageTransformer public class ZoomOutPageTransformer implements ViewPager.PageTransformer { //自由控制缩放比例 private static final float MAX_SCALE = 1f; private static final float

  • Android 两个ViewPager的联动效果的实现

    前言 以前做的项目,导航栏基本上是在顶部或者是在底部,但是最近开发的一款app,刚开始拿到设计图也是很懵逼的,导航栏居然是在中间,what fuck!设计图如下: 导航栏在中间就会涉及到两个viewpager之间的联动,viewpager的高度适应等问题,现在来纪录一下是怎么解决问题的?希望给有同样需求的提供一定的帮助. (一)Viewpager 高度自适应 系统自动viewpager 不能设置wrap_content: 自定义viewpager,注意高度的设置否则底部空白的问题 网上也会有很多

  • Android ViewPager实现滑动指示条功能

    布局 activity_main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="

  • Android Studio使用ViewPager+Fragment实现滑动菜单Tab效果

    本文为大家分享了Android Studio实现滑动菜单Tab效果的具体代码,供大家参考,具体内容如下 描述: 之前有做过一个记账本APP,拿来练手的,做的很简单,是用Eclipse开发的: 最近想把这个APP重新完善一下,添加了一些新的功能,并选用Android Studio来开发: APP已经完善了一部分,现在就想把已经做好的功能整理一下,记录下来. 效果图: 可以手动滑动菜单 也可以通过点击头部菜单进行切换 具体实现的代码: 前台代码(activity_main.xml): <?xml v

  • Android使用ViewPager完成app引导页

    本文实例为大家分享了Android使用ViewPager完成app引导页的具体代码,供大家参考,具体内容如下 public class MainActivity extends AppCompatActivity { // int[] resourceId = { // R.layout.first, // R.layout.second, // R.layout.third // }; List<View> mListView; ViewPager viewPager; ViewGroup

  • Android使用ViewPager快速切换Fragment时卡顿的优化方案

    当ViewPager切换到当前的Fragment时,Fragment会加载布局并显示内容,如果用户这时快速切换ViewPager,即Fragment需要加载UI内容,而又频繁地切换Fragment,就容易产生卡顿现象(类似在ListView快速滑动的同时加载图片容易卡顿). 优化方案: 1.Fragment轻量化 如果ViewPager加载的Fragment都比较轻量,适当精简Fragment的布局,可提高Fragment加载的速度,从而减缓卡顿现象. 2.防止Fragment被销毁 ViewP

  • Android利用ViewPager实现带小圆球的图片滑动

    在上文实现的带小圆球的图片滑动的通用性较好,但是较复杂. 现在也是利用 ViewPager ,但是却没有利用 ShapeDrawable 来实现带小圆球的图片滑动.如有些播放器一样,在开始安装 app 时,都会出现引导界面,然后才进入主界面,但是在重新启动 app 时却不会再出现该引导界面. 下面实现的就是该类似的功能,只是把引导界面都做成了图片显示,这样更能够体现不同的做法(和上一篇博客). 本例主要主要:在小圆点的绘制和 viewpager 相关联起来. 如下效果: MyPagerAdapt

随机推荐