ViewPager has been around for a number of years now, and was added in support library V22.1.0. In February 2019 the first alpha of ViewPager2
was released. In this post we’ll take a look at how ViewPager2
differs from ViewPager
, and how to implement it.
The main difference between ViewPager2
and the original ViewPager
is that ViewPager2
is implemented around RecyclerView
which leverages much of the behaviour implicit in RecyclerView
which already contains mechanisms to destroy and recycle items when the move outside the visible area of the RecyclerView
and these same mechanisms are used to recycle the pages in ViewPager2
. Moreover, the behaviour RecyclerView
is highly extensible and configurable, and we are therefore able to customise the appearance and behaviour of ViewPager2
as a result.
I’m assuming that the reader is already familiar with the basics of RecyclerView
as it is such an important and useful component in the Android UI Framework.
The first thing that we need to do is add the ViewPager2
dependency:
. . . dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" implementation "androidx.appcompat:appcompat:1.1.0-beta01" implementation "androidx.core:core-ktx:1.2.0-alpha02" implementation "com.google.android.material:material:1.1.0-alpha07" implementation "androidx.constraintlayout:constraintlayout:2.0.0-beta2" implementation "androidx.lifecycle:lifecycle-extensions:2.0.0" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0" implementation "androidx.viewpager2:viewpager2:1.0.0-alpha05" } . . .
The next thing we’ll do is add a simple Fragment
which takes an argument indicating which page it represents:
class PageFragment : Fragment() { companion object { private const val ARG_SECTION_NUMBER = "section_number" @JvmStatic fun newInstance(sectionNumber: Int): PageFragment { return PageFragment().apply { arguments = Bundle().apply { putInt(ARG_SECTION_NUMBER, sectionNumber) } } } } private lateinit var viewModel: PageViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel = ViewModelProviders.of(this).get(PageViewModel::class.java).apply { setIndex(arguments?.getInt(ARG_SECTION_NUMBER) ?: 1) } } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val root = inflater.inflate(R.layout.page_fragment, container, false) val textView: TextView = root.findViewById(R.id.section_label) viewModel.text.observe(this, Observer{ textView.text = it }) return root } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) viewModel = ViewModelProviders.of(this).get(PageViewModel::class.java).apply { setIndex(arguments?.getInt(ARG_SECTION_NUMBER) ?: 1) } } }
This gets backed by a ViewModel
that will provide the string to populate the TextView
in the Fragment
. This Fragment
/ ViewModel
is very similar to those that get generated if you create a new Android project with the “Tabbed Activity” template from the “Create New Project” wizard in Android Studio. Hopefully it’s all pretty self-explanatory, so let’s move on and get the the are of interest: wiring this up to ViewPager2
.
We discussed previously how ViewPager2
is built around RecyclerView
. Where the original ViewPager
used its own PagerAdapter
and subclasses, ViewPager2
uses RecyclerView.Adapter
instead, but provides its own subclasses to make things easier for us.
We’ll create a subclass of FragmentStateAdapter
which will manage our Fragment
state in the case of configuration changes (i.e. device rotation):
class SectionsPagerAdapter( private val activity: FragmentActivity ) : FragmentStateAdapter(activity) { override fun createFragment(position: Int): Fragment = PageFragment.newInstance(position) fun getPageTitle(position: Int): CharSequence = activity.resources.getString(TAB_TITLES[position]) override fun getItemCount(): Int = TAB_TITLES.size companion object { private val TAB_TITLES = arrayOf( R.string.tab_text_1, R.string.tab_text_2 ) } }
This overrides the two required methods createFragment()
, which creates Fragment
instances, and getItemCount()
which returns the number of pages in the adpater.
There’s an additional method here named getPageTitle()
which will return a title for each page. Those that are familiar with the original ViewPager
‘s PagerAdapter
may know of its getPageTitle()
method which can be useful when hooking ViewPager
up to a TabLayout
. There is no equivalent method in FragmentStateAdapter
but we’ll define the method anyway so that we can see how easy it is to hook up to TabLayout
.
All that remains is our Activity
which performs all of the wiring:
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val sectionsPagerAdapter = SectionsPagerAdapter(this) val viewPager = findViewById(R.id.view_pager).apply { adapter = sectionsPagerAdapter orientation = ViewPager2.ORIENTATION_HORIZONTAL } val tabs: TabLayout = findViewById(R.id.tabs) TabLayoutMediator(tabs, viewPager, true) { tab, position -> tab.text = sectionsPagerAdapter.getPageTitle(position) }.attach() } }
First we create an instance of our SectionsPagerAdapter
, and then we obtain ViewPager2
instance from the layout. We then configure the ViewPager2
instance by setting the adapter to our SectionsPagerAdapter
instance, and then setting the orientation to horizontal.
This is pretty standard RecyclerView
setup and orientation works in the same way. Here we see another nice advantage of having ViewPager2
built around RecyclerView
– we can have different orientations of ViewPager2
which was not possible with the original ViewPager
. One thing that the more observant reader may have spotted is that we haven’t set a layout manager for the RecyclerView
. ViewPager2
automatically sets a LinearLayoutManager
for us because it’s going to be by far the most common use case. However, it is possible to set a different one if required, so it’s possible to get some really funky ViewPager
behaviours by using different layout managers.
The final thing that we need to set up is wiring up the ViewPager2
to our TabLayout
. While TabLayout
has a couple of setupWithViewPager()
methods, it is completely agnostic of ViewPager2
so we cannot use those methods. Instead the setup is done by a TabLayoutMediator
instance. TabLayoutMediator
is part of the ViewPager2
library, and does all of the necessary work for us. The arguments are the TabLayout
instance, then the ViewPager2
instance, a boolean which controls whether the the tabs will be automatically updated when the ViewPager2
‘s adapter is updated, and finally an OnConfiguarionCallback
instance which is implemented here as a lambda.
OnConfigurationCallback
is called to configure the tab representing each page within the ViewPager2
. Within this we set the text of the tab using the getPageTitle()
method that we added to the SectionsPagerAdapter
.
And we’re done. If we run this we get the behaviour that we and, more importantly, our users are familiar with. We can both swipe between pages, and also tap the tabs to change pages:
Although we’ve just mimicked the behaviour of the original ViewPager
here, it will now be much easier to modify that behaviour. An obvious thing here would be how easy it would to convert this to a vertical ViewPager2
– just a single line change to alter the orientation. That certainly would have required significantly more effort in the original ViewPager
. But we could also utilise RecyclerView
‘s ItemDecoration
to add dividers and suchlike between the pages. Using RecyclerView
allows us plenty of scope for customisation!
We’ve only covered the basics of what is possible here but those that are experienced with RecyclerView
should already have an idea of how powerful this new approach to ViewPager
really is.
That it for this introduction to ViewPager2
. However the next article will cover a very different topic, yet will build upon the code that we’ve used here to get a really nice visual effect.
The source code for this article is available here.
© 2019, Mark Allison. All rights reserved.
Copyright © 2019 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.
Thanks for article!
But It’s look strange the that the PageFragment knows about his position in Viewpager, not isn’t?
But it doesn’t. There is no logic based around the value of sectionNumber. It uses whatever value is provided to it during construction to create the string that is displayed, but it is the ViewModel which contains the logic for creating that string. The value of sectionNumber does not affect the PageFagment at all. In fact PageFragment does not directly access sectionNumber, it just passes whatever value is in the ARG_SECTION_NUMBER argument to the ViewModel.
We could probably bypass the use of an argument by injecting the ViewModel already initialised, but this code is to demonstrate how to implement ViewPager2, and I didn’t want to confuse things by adding unnecessary complexity which wasn’t related to the subject of the article.
Afaik TabLayoutMediator is part of the Material Components.
Thanks for the Article(s)!