In-depth understanding of the use of ViewPager2

Time:2022-8-18

First, the new features of ViewPager2

ViewPager2 can be seen from the name that it is an upgraded version of ViewPager. Since it is an upgraded version, what new features and API changes does it have compared to ViewPager? Let's look down.

1. ViewPager2 new features

  • Based on RecyclerView implementation. This means that the advantages of RecyclerView will be inherited by ViewPager2.
  • Support vertical sliding. Only one parameter is required to change the sliding direction.
  • Support for turning off user input. Use setUserInputEnabled to set whether to prohibit users from sliding the page.
  • Programmatic scrolling is supported. Simulate the user sliding the page through the fakeDragBy(offsetPx) code.
  • CompositePageTransformer supports adding multiple PageTransformers at the same time.
  • Support DiffUtil, you can add item animation of data collection changes.
  • Supports RTL (right-to-left) layout. I think this feature may not be very useful for domestic developers..

2. API changes compared to ViewPager

What changes did ViewPager2 make compared to ViewPager? After doing some research, I've come up with the following:

  • ViewPager2 and ViewPager both inherit from ViewGrop, but ViewPager2 is declared final. It means that we can no longer modify the code of ViewPager2 through inheritance like ViewPager.
  • FragmentStatePagerAdapter is replaced by FragmentStateAdapter
  • PagerAdapter is replaced by RecyclerView.Adapter
  • addPageChangeListener is registeredOnPageChangeCallback. We know that ViewPager's addPageChangeListener receives an OnPageChangeListener interface, and there are three methods in this interface. When you want to monitor page changes, you need to override these three methods. The registerOnPageChangeCallback method of ViewPager2 receives an abstract class called OnPageChangeCallback, so we can selectively override the required methods.
  • Removed setPargeMargin method.

The new features and APIs listed above may not be complete. If there are any omissions, you can leave a message to add.

Second, start the journey of ViewPager2

ViewPager2 is located under the androidx package, that is, it is not built into the system source code like ViewPager. Therefore, using ViewPager2 requires additional dependencies to be added. In addition, ViewPager is not included in android support, that is, to use ViewPager2, it must be migrated to androidx.

1. Add dependencies, the latest version of ViewPager2 is 1.0.0:


dependencies {
  implementation "androidx.viewpager2:viewpager2:1.0.0"
}

2.ViewPager2 layout file:


<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. Adapter of ViewPager2

Because ViewPager2 encapsulates RecyclerView inside, its Adapter is also the Adapter of RecyclerView.

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 needs to inherit 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]))
    }
  }
}

The code in item_page is as follows:


<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. Set Adapter for ViewPager in Activity

It's very simple to complete the function of a ViewPager, let's see how the effect looks like:

5.ViewPager2 slides vertically

Next, we set the vertical sliding for it through a line of code


viewPager2.orientation = ViewPager2.ORIENTATION_VERTICAL

It is difficult to use ViewPager for vertical sliding, and only one parameter needs to be set through ViewPager2. Take a look at the effect:

6. Listener for page sliding events

As mentioned above, we need to override three methods to set the listener event of page sliding for ViewPager, and only need to override the required method to set the listener event for ViewPager2, because OnPageChangeCallback in ViewPager2 is an abstract class.


viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
      override fun onPageSelected(position: Int) {
        super.onPageSelected(position)
        Toast.makeText([email protected], "page selected $position", Toast.LENGTH_SHORT).show()
      }
    })

7.setUserInputEnabled与fakeDragBy

We know that when using ViewPager, if you want to prohibit the user from sliding, you need to rewrite ViewPager's onInterceptTouchEvent. And ViewPager2 is declared as final, we can no longer inherit ViewPager2. So how should we prohibit the sliding of ViewPager2? In fact, this function has been provided for us in ViewPager2, which can be achieved only by setUserInputEnabled.


viewPager2.isUserInputEnabled = false

At the same time, ViewPager2 adds a fakeDragBy method. This method can simulate drag and drop. Before using fakeDragBy, you need to startFakeDrag method to enable simulated dragging. fakeDragBy will return a boolean value, true indicates that there is a fake drag being executed, and returning false indicates that there is no fake drag currently being executed. Let's try the code:


fun fakeDragBy(view: View) {
    viewPager2.beginFakeDrag()
    if (viewPager2.fakeDragBy(-310f))
      viewPager2.endFakeDrag()
  }

It should be noted that fakeDragBy accepts a float parameter. When the parameter value is positive, it means sliding to the previous page, and when the value is negative, it means sliding to the next page.
Let's take a look at the effect diagram:

User input is disabled in the demo, and user swiping can be simulated by button clicks.

3. PageTransformer of ViewPager2

Compared with ViewPager, the Transformer function of ViewPager2 has been greatly expanded. ViewPager2 can not only use PageTransformer to set page animation, but also use PageTransformer to set page spacing and add multiple PageTransformers at the same time. Next, let's get to know the PageTransformer of ViewPager2!

1.setPageMargin

In the first chapter, we mentioned that ViewPager2 removed the setPageMargin method, so how to set page spacing for ViewPager2? In fact, MarginPageTransformer is provided for us in ViewPager2, and we can set the page spacing through the setPageTransformer method of ViewPager2. code show as below:


viewPager2.setPageTransformer(MarginPageTransformer(resources.getDimension(R.dimen.dp_10).toInt()))

In the above code we set a page spacing of 10dp for ViewPager2. The effect is as follows:

2. Meet CompositePageTransformer

At this time, we should have a question. After setting the page spacing for ViewPager2, what if we want to set the Transformer for page animation? This is where the CompositePageTransformer comes in. It can also be seen from the name that it is a combined PageTransformer. That's right, CompositePageTransformer implements the PageTransformer interface and maintains a List collection inside it. We can add multiple PageTransformers to CompositePageTransformer.


val compositePageTransformer = CompositePageTransformer()
    compositePageTransformer.addTransformer(ScaleInTransformer())
    compositePageTransformer.addTransformer(MarginPageTransformer(resources.getDimension(R.dimen.dp_10).toInt()))
    viewPager2.setPageTransformer(compositePageTransformer)

In the above code, we set MarginPageTransformer and a ScaleInTransformer for page scaling for ViewPager through CompositePageTransformer. Take a look at the effect:

3. PageTransformer in ViewPager2

PageTransformer is an interface located in ViewPager2, so ViewPager2's PageTransformer is independent of ViewPager, and it has nothing to do with ViewPager's PageTransformer. Even so, don't worry. Because the PageTransformer of ViewPager2 is implemented in exactly the same way as the PageTransformer of ViewPager. Let's look at the ScaleInTransformer used in the previous section:


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. One-screen multi-page effect of ViewPager2

On the official Sample of ViewPager2, I saw that ViewPager2's one-screen multi-page can be achieved by setting Padding for RecyclerView. code show as below:


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)

Finally, let's look at the effect

Fourth, ViewPager2 and Fragment

We have also mentioned earlier that the new FragmentStateAdapter in ViewPager2 replaces the FragmentStatePagerAdapter of ViewPager. Then let's use ViewPager2 to implement an instance of nested Fragment in an Activity.

1. Add ViewPager2 to the layout of the Activity


<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. Implement 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. Set FragmentStateAdapter for ViewPager2 in Activity


vp_fragment.adapter = AdapterFragmentPager(this)
    vp_fragment.offscreenPageLimit = 3
    vp_fragment.isUserInputEnabled=false

Five, ViewPager2 and TabLayout

TabLayout is also a control that is often used in projects, and it usually appears together with ViewPager. So how should Tablayout be used for ViewPager2? This requires us to know a new class, TabLayoutMediator, which is a new class in material-1.2.0. The latest version of the material package is 1.2.0-alpha03, so we need to introduce this package separately. The dependencies are as follows:


implementation 'com.google.android.material:material:1.2.0-alpha03'

The construction method of TabLayoutMediator receives three parameters, the first parameter is TabLayout; the second parameter is ViewPager2; the third parameter is TabConfigurationStrategy, which is an interface, which has a method onConfigureTab(@NonNull TabLayout.Tab tab, int position), the first parameter is the current Tab, the second is the current position, the source code is as follows:


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);
}

Next, we can associate TabLayout with ViewPager2 through TabLayoutMediator:

TabLayoutMediator(tab_layout, view_pager) { tab, position ->
      // Set Text for Tab
      tab.text = Card.DECK[position].toString()
    }.attach()

It is very simple to use, and the effect is shown in the following figure:

6. Summary

In this article, we learned about the new features of ViewPager2 and its usage. In general, ViewPager2 has a great improvement in performance and function compared to ViewPager. Therefore, I believe that ViewPager2 will definitely replace ViewPager in the near future. So, have you considered using ViewPager2 in your project?

Finally, I will recommend BannerViewPager to everyone. This is an infinite carousel library with powerful functions based on ViewPager. In the future, I will refactor the code with ViewPager2 in BannerViewPager 3.0. You are welcome to follow BannerViewPager on GitHub.

This article involves source code download

See the code of ViewPager2 and Fragment in Section 4:

BannerViewPager

The above is the detailed content of in-depth understanding of the use of ViewPager2. For more information about the use of ViewPager2, please pay attention to other related articles on developpaer!