如何在 Android 中使用 ViewPager2的 TabLayout

我想使用 com.google.android.material.tabs.TabLayout组件与 Android 的新 ViewPager实现 androidx.viewpager2.widget.ViewPager2。然而,TabLayout提供的 setupWithViewPager(..)方法只支持旧的 ViewPager实现。有没有一种方法可以很容易地将 TabLayout绑定到 ViewPager2组件?

99879 次浏览

You have to use this TabLayoutMediator that mimics tabLayout.setupWithViewPager() and sets up the ViewPager2 with Tablayout. Otherwise, you will have to write your own adapter that will combine both parties.

Its code will look like this in Kotlin

TabLayoutMediator(tabLayout, viewPager) { tab, position ->
tab.text = tabTitles[position]
}.attach()

Initialize the TabLayoutMediator object with an object of TabLayout, ViewPager2 , autoRefresh -- boolean type, and an object of OnConfigurationChangeCallback.

TabLayoutMediator tabLayoutMediator = new TabLayoutMediator(tabLayout, viewPager2, true, new TabLayoutMediator.OnConfigureTabCallback() {
@Override
public void onConfigureTab(TabLayout.Tab tab, int position) {
// position of the current tab and that tab
}
});

Finally just call attach() to the TabLayoutMediator object to wire up the tablayout to the viewpager :-

 tabLayoutMediator.attach();

autoRefresh - key if set to true -- ( By default its set to true )

RECREATES all the tabs of the tabLayout if notifyDataSetChanged is called to the viewpager adapter.

Use the contents of TabLayoutMediator.java

UPDATE

check this Create swipe views with tabs using ViewPager2

Here is the Updated answer How to use TabLayout with ViewPager2 in Android

Now we no need to create a class from TabLayoutMediator

Use below dependencies

implementation 'com.google.android.material:material:1.1.0-alpha08'
implementation 'androidx.viewpager2:viewpager2:1.0.0-beta02'

SAMPLE CODE

XMl layout

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">


<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">


<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>


<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</com.google.android.material.appbar.AppBarLayout>


<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewpager"
app:layout_anchor="@id/tabs"
app:layout_anchorGravity="bottom"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>




</androidx.coordinatorlayout.widget.CoordinatorLayout>

Activity

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import com.google.android.material.tabs.TabLayoutMediator


import com.google.android.material.tabs.TabLayout




class MainActivity : AppCompatActivity() {


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)


//        setSupportActionBar(toolbar)
viewpager.adapter = AppViewPagerAdapter(supportFragmentManager, lifecycle)


TabLayoutMediator(tabs, viewpager, object : TabLayoutMediator.OnConfigureTabCallback {
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
// Styling each tab here
tab.text = "Tab $position"
}
}).attach()




}
}

UPDATE

If your using implementation 'com.google.android.material:material:1.1.0-alpha10' then use below code

        TabLayoutMediator(tabs, viewpage,
TabLayoutMediator.TabConfigurationStrategy { tab, position ->
when (position) {
0 -> { tab.text = "TAB ONE"}
1 -> { tab.text = "TAB TWO"}
}
}).attach()

OUTPUT

enter image description here

You can use Kotlin extension function:

fun TabLayout.setupWithViewPager(viewPager: ViewPager2, labels: List<String>) {


if (labels.size != viewPager.adapter?.itemCount)
throw Exception("The size of list and the tab count should be equal!")


TabLayoutMediator(this, viewPager,
TabLayoutMediator.TabConfigurationStrategy { tab, position ->
tab.text = labels[position]
}).attach()
}

And call it:

 tabLayout.setupWithViewPager(viewPager, listOf("Tab A", "Tab B"))

No hacks, no extensions, no TabLayoutMediator

I am on implementation 'com.google.android.material:material:1.2.0-alpha02' and do the following without needing the TabLayoutMediator. Instead, I link the TabLayout with the ViewPager2 using the method described here. I've also added a working example to github here. I think I've minimized the solution to a minimal working example. I'll explain the important bits.

Adding the elements to the template

First we'll need to add the TabLayout and ViewPager2 to the layout. I've placed them inside a LinearLayout and CoordinatorLayout here, but you can do whatever you like of course.

<!-- activity_main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">


<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">


<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">


<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>


</LinearLayout>


</androidx.coordinatorlayout.widget.CoordinatorLayout>


</androidx.constraintlayout.widget.ConstraintLayout>

Connecting an adapter to the viewpager

So the adapter is in charge of supplying the correct fragments to the activity. You'll have to extend FragmentStateAdapter which I've done very simply as below (it's a private class because it's declared within my MainActivity.java here):

    private class ViewStateAdapter extends FragmentStateAdapter {


public ViewStateAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle) {
super(fragmentManager, lifecycle);
}


@NonNull
@Override
public Fragment createFragment(int position) {
// Hardcoded in this order, you'll want to use lists and make sure the titles match
if (position == 0) {
return new BarFragment();
}
return new FooFragment();
}


@Override
public int getItemCount() {
// Hardcoded, use lists
return 2;
}
}

I can then connect my own Adapter to the ViewPager as below:

FragmentManager fm = getSupportFragmentManager();
ViewStateAdapter sa = new ViewStateAdapter(fm, getLifecycle());
final ViewPager2 pa = findViewById(R.id.pager);
pa.setAdapter(sa);

I've added the fragments to my viewpager. (Because I hardcoded the Fragments in my adapter, you should use a list and something like an 'addFragment' method or something)

The TabLayout

Then with

TabLayout tabLayout = findViewById(R.id.tabLayout);
tabLayout.addTab(tabLayout.newTab().setText("Bar"));
tabLayout.addTab(tabLayout.newTab().setText("Foo"));

I add two tabs to my TabLayout, showing the titles but not letting me switch to the fragments yet.

Connecting TabLayout to Adapter

tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
pa.setCurrentItem(tab.getPosition());
}


@Override
public void onTabUnselected(TabLayout.Tab tab) {


}


@Override
public void onTabReselected(TabLayout.Tab tab) {


}
});

This should be pretty straightforward. User clicks on a tab, I get the position in my callback and I simply set the adapter's current item to that position.

Change Tab when swiping

Finally we couple back when the user swipes the fragment to set the correct tab item as selected

pa.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
tabLayout.selectTab(tabLayout.getTabAt(position));
}
});

Im running mine on the implementation 'com.google.android.material:material:1.2.1' and didn't use the tabLayoutMediator either. For starters the https://developer.android.com/jetpack/androidx/migrate/class-mappings have changed for the TabLayout in androidX so be sure to be using com.google.android.material.tabs.TabLayout in your import statement. Heres the rest of my implementation of the solution: The Xml layout declaration of the viewPager and the TabLayout

<?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="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@color/tan_background"
android:orientation="vertical">


<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>


<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewpager"
app:layout_anchor="@id/tabs"
app:layout_anchorGravity="bottom"
android:layout_width="match_parent"
android:layout_height="match_parent"/>


</LinearLayout>

and the activity file

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);


// Set the content of the activity to use the activity_main.xml layout file
setContentView(layout.activity_main);


// Find the view pager that will allow the user to swipe between fragments
ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);


// Create an adapter that knows which fragment should be shown on each page
SimpleFragmentPagerAdapter adapter = new SimpleFragmentPagerAdapter(this,getSupportFragmentManager());


// Set the adapter onto the view pager
viewPager.setAdapter(adapter);


// Find the tab layout that shows the tabs
TabLayout tabLayout = findViewById(R.id.tabs);


// Connect the tab layout with the view pager. This will
//   1. Update the tab layout when the view pager is swiped
//   2. Update the view pager when a tab is selected
//   3. Set the tab layout's tab names with the view pager's adapter's titles
//      by calling onPageTitle()
tabLayout.setupWithViewPager(viewPager);
}
}

I used a fragment adapter setup in a different class as well where I passed the context to the constructor for the different Tabs

If your tab title comes from string.xml.
You can put the fragment with title together in a List then set title by TabLayoutMediator. It helps you easily to reorder, delete or add new fragment

class MyFragment : Fragment() {


override fun onCreateView(...): View? {
...


TabLayoutMediator(tabLayout, viewPager) { tab, position ->
tab.text = pagerAdapter.getTabTitle(position)
}.attach()
}


private inner class PagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
val pages = listOf(
Pair(HomeFragment.newInstance(), R.string.home),
Pair(GraphFragment.newInstance(), R.string.graph),
Pair(SettingFragment.newInstance(), R.string.setting),
)


override fun createFragment(position: Int): Fragment {
return pages[position].first
}


override fun getItemCount(): Int {
return pages.count()
}


fun getTabTitle(position: Int): String {
return getString(pages[position].second)
}
}
}

If you're coding in JAVA. You can use the following adapter for the viewPager2:

public class ViewPagerAdapter extends FragmentStateAdapter {
private static final int CARD_ITEM_SIZE = 10;
public ViewPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
super(fragmentActivity);
}
@NonNull @Override public Fragment createFragment(int position) {
return CardFragment.newInstance(position);
}
@Override public int getItemCount() {
return CARD_ITEM_SIZE;
}
}

And on the mainActivity you need to have something inline with the following:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewPager = findViewById(R.id.view_pager);
tabLayout = findViewById(R.id.tabs);
viewPager.setAdapter(new ViewPagerAdapter(this));
new TabLayoutMediator(tabLayout, viewPager,
new TabLayoutMediator.TabConfigurationStrategy() {
@Override public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) {
tab.setText("Tab " + (position + 1));
}
}).attach();
}
}

Make sure you use TabLayoutMediator after your set adapter to view_pager2

TabLayoutMediator(tab_layout, view_pager2) { tab, position ->
tab.text = fragmentList[position].second  // here u can modify you text..
}.attach()

it is very simple:

java code:

tabLayout=findViewById(R.id.tabLayout);
viewPager=findViewById(R.id.viewPager);
viewPager.setAdapter(new ViewPagerAdapter(this));


new TabLayoutMediator(tabLayout, viewPager, new TabLayoutMediator.TabConfigurationStrategy() {
@Override
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) {
if (position==0){
tab.setText(R.string.photos);
tab.view.setBackground(getResources().getDrawable(R.drawable.ic_rectangle_1345));
}
else {
tab.setText(R.string.videos);
}
}
}).attach();


tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
tab.view.setBackground(getResources().getDrawable(R.drawable.ic_rectangle_1345));
}


@Override
public void onTabUnselected(TabLayout.Tab tab) {
tab.view.setBackgroundColor(Color.TRANSPARENT);
}


@Override
public void onTabReselected(TabLayout.Tab tab) {


}
});

adapter:

public class ViewPagerAdapter extends FragmentStateAdapter {
public ViewPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
super(fragmentActivity);
}


@NonNull
@Override
public Fragment createFragment(int position) {
if (position==0) {
return new PhotosFragment();
}
else {
return new VideosFragment();
}
}


@Override
public int getItemCount() {
return 2;
}}

XML:

<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="@color/tool_bar_bg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/collection_bar" />


<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tabLayout" />

You layouts are ready, fragments are ready and Adapters are ready. All you need now is to setup an event listener to

tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){


@Override
public void onTabSelected(TabLayout.Tab tab) {
viewPager.setCurrentItem(tab.getPosition());
}


@Override
public void onTabUnselected(TabLayout.Tab tab) {


}


@Override
public void onTabReselected(TabLayout.Tab tab) {


}
});

This should bind the Tab layout to your ViewPager2.

You can take inspiration from my sample project to create UIs with Viewpager2. I use TabLayoutMediator on all examples, Simple and very useful. 😉 https://github.com/gabriel-TheCode/OnboardingViewPagerExamples.