I came to know about ViewPager2 and tried to implement it, but didn't find any proper example.
Can anyone tell me how can I use it.
I am looking for proper usage, not an example.
I came to know about ViewPager2 and tried to implement it, but didn't find any proper example.
Can anyone tell me how can I use it.
I am looking for proper usage, not an example.
Check : Migrate from ViewPager to ViewPager2
Check : Create swipe views with tabs using ViewPager2
Check out my answer if you want to implement Carousel using View Pager2
How to use TabLayout with ViewPager2
SAMPLE CODE
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()
}
}
OUTPUT
From Docs
New features
API changes
FragmentStateAdapter
replaces FragmentStatePagerAdapter
RecyclerView.Adapter
replaces PagerAdapter
registerOnPageChangeCallback
replaces addPageChangeListener
SAMPLE CODE
add the latest
dependencies
forViewPager2
implementation 'androidx.viewpager2:viewpager2:1.0.0-alpha01'
layout
<?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"
android:orientation="vertical"
tools:context=".MainActivity">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
activity
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager2.widget.ViewPager2;
import java.util.ArrayList;
public class MyActivity extends AppCompatActivity {
ViewPager2 myViewPager2;
MyAdapter MyAdapter;
private ArrayList<String> arrayList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
myViewPager2 = findViewById(R.id.view_pager);
arrayList.add("Item 1");
arrayList.add("Item 2");
arrayList.add("Item 3");
arrayList.add("Item 4");
arrayList.add("Item 5");
MyAdapter = new MyAdapter(this, arrayList);
myViewPager2.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
myViewPager2.setAdapter(MyAdapter);
}
}
MyAdapter
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private Context context;
private ArrayList<String> arrayList = new ArrayList<>();
public MyAdapter(Context context, ArrayList<String> arrayList) {
this.context = context;
this.arrayList = arrayList;
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
holder.tvName.setText(arrayList.get(position));
}
@Override
public int getItemCount() {
return arrayList.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder {
TextView tvName;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
tvName = itemView.findViewById(R.id.tvName);
}
}
}
now we need to use
ViewPager2.OnPageChangeCallback()
to get Swipe event ofViewPager2
SAMPLE CODE
myViewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels);
}
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
Log.e("Selected_Page", String.valueOf(position));
}
@Override
public void onPageScrollStateChanged(int state) {
super.onPageScrollStateChanged(state);
}
});
we can set Orientation using
myViewPager2.setOrientation()
SAMPLE CODE
For HORIZONTAL Orientation
use
myViewPager2.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
For VERTICAL Orientation
use
myViewPager2.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
We can use
notifyDataSetChanged
same as we are using inRecyclerView.Adapter
SAMPLE CODE to add new item
btnAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
arrayList.add("New ITEM ADDED");
MyAdapter.notifyDataSetChanged();
}
});
SAMPLE CODE to remove new item
btnRemove.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
arrayList.remove(3);
MyAdapter.notifyItemRemoved(3);
}
});
Fragment
with ViewPager2
First create a
ViewPagerFragmentAdapter
class which extendsFragmentStateAdapter
import java.util.ArrayList;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.viewpager2.adapter.FragmentStateAdapter;
public class ViewPagerFragmentAdapter extends FragmentStateAdapter {
private ArrayList<Fragment> arrayList = new ArrayList<>();
public ViewPagerFragmentAdapter(@NonNull FragmentManager fragmentManager) {
super(fragmentManager);
}
@NonNull
@Override
public Fragment getItem(int position) {
return arrayList.get(position);
}
public void addFragment(Fragment fragment) {
arrayList.add(fragment);
}
@Override
public int getItemCount() {
return arrayList.size();
}
}
Now use like this in your activity
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager2.widget.ViewPager2;
import neel.com.bottomappbar.R;
public class MainActivity extends AppCompatActivity {
ViewPager2 myViewPager2;
ViewPagerFragmentAdapter myAdapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myViewPager2 = findViewById(R.id.view_pager);
myAdapter = new ViewPagerFragmentAdapter(getSupportFragmentManager());
// add Fragments in your ViewPagerFragmentAdapter class
myAdapter.addFragment(new FragmentOne());
myAdapter.addFragment(new Fragmenttwo());
myAdapter.addFragment(new FragmentThree());
// set Orientation in your ViewPager2
myViewPager2.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
myViewPager2.setAdapter(myAdapter);
}
}
for more information check this
New features
setUserInputEnabled
, isUserInputEnabled
)API changes
ViewPager2
class finalBug fixes
FragmentStateAdapter
stability fixesSAMPLE CODE to disable swiping in viewpager2
myViewPager2.setUserInputEnabled(false);// SAMPLE CODE to disable swiping in viewpager2
myViewPager2.setUserInputEnabled(true);//SAMPLE CODE to enable swiping in viewpager2
New features
API changes
FragmentStateAdapter
now requires a Lifecycle
object. Two utility constructors added to obtain it from the host FragmentActivity
or the host FragmentSAMPLE CODE
ViewPagerFragmentAdapter
import java.util.ArrayList;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.Lifecycle;
import androidx.viewpager2.adapter.FragmentStateAdapter;
public class ViewPagerFragmentAdapter extends FragmentStateAdapter {
private ArrayList<Fragment> arrayList = new ArrayList<>();
public ViewPagerFragmentAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle) {
super(fragmentManager, lifecycle);
}
@NonNull
@Override
public Fragment getItem(int position) {
return arrayList.get(position);
}
public void addFragment(Fragment fragment) {
arrayList.add(fragment);
}
@Override
public int getItemCount() {
return arrayList.size();
}
}
MainActivity code
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager2.widget.ViewPager2;
import neel.com.bottomappbar.R;
public class MainActivity extends AppCompatActivity {
ViewPager2 myViewPager2;
ViewPagerFragmentAdapter myAdapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myViewPager2=findViewById(R.id.view_pager);
myAdapter = new ViewPagerFragmentAdapter(getSupportFragmentManager(), getLifecycle());
// add Fragments in your ViewPagerFragmentAdapter class
myAdapter.addFragment(new FragmentOne());
myAdapter.addFragment(new Fragmenttwo());
myAdapter.addFragment(new FragmentThree());
myViewPager2.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
myViewPager2.setAdapter(myAdapter);
}
}
Version 1.0.0-alpha05
New features
ItemDecorator
introduced with a behaviour consistent with RecyclerView
.MarginPageTransformer
introduced to provide an ability to create space between pages (outside of page inset).CompositePageTransformer
introduced to provide an ability to combine multiple PageTransformers
API changes
FragmentStateAdapter#getItem
method renamed to FragmentStateAdapter#createFragment
- previous method name has proven to be a source of bugs in the past.OFFSCREEN_PAGE_LIMIT_DEFAULT
value changed from 0 to -1. No need for a client code change if the OFFSCREEN_PAGE_LIMIT_DEFAULTconstant
used.SAMPLE CODE
Activity code
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.widget.MarginPageTransformer;
import androidx.viewpager2.widget.ViewPager2;
import neel.com.bottomappbar.R;
public class MainActivity extends AppCompatActivity {
ViewPager2 myViewPager2;
ViewPagerFragmentAdapter myAdapter;
private ArrayList<Fragment> arrayList = new ArrayList<>();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myViewPager2 = findViewById(R.id.myViewPager2);
// add Fragments in your ViewPagerFragmentAdapter class
arrayList.add(new FragmentOne());
arrayList.add(new Fragmenttwo());
arrayList.add(new FragmentThree());
myAdapter = new ViewPagerFragmentAdapter(getSupportFragmentManager(), getLifecycle());
// set Orientation in your ViewPager2
myViewPager2.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
myViewPager2.setAdapter(myAdapter);
myViewPager2.setPageTransformer(new MarginPageTransformer(1500));
}
}
ViewPagerFragmentAdapter
import java.util.ArrayList;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.Lifecycle;
import androidx.viewpager2.adapter.FragmentStateAdapter;
public class ViewPagerFragmentAdapter extends FragmentStateAdapter {
private ArrayList<Fragment> arrayList = new ArrayList<>();
public ViewPagerFragmentAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle) {
super(fragmentManager, lifecycle);
}
@NonNull
@Override
public Fragment createFragment(int position) {
switch (position) {
case 0:
return new FragmentOne();
case 1:
return new Fragmenttwo();
case 2:
return new FragmentThree();
}
return null;
}
@Override
public int getItemCount() {
return 3;
}
}
ViewPager2
using Version 1.0.0-alpha02
–
Involucrum PagerTitleStrip
or PagerTabStrip
with ViewPager2 or maybe there is an alternative? –
Sampler ArrayList<Fragment>
object to create fragments dynamically in ViewPagerFragmentAdapter
? –
Celloidin PagerAdapter
's destroyItem
function in the custom RecyclerView.Adapter
? –
Collenecollet onViewRecycled
as override fun onViewRecycled(holder: ViewHolder) { (holder.itemView as ViewGroup).removeAllViews() super.onViewRecycled(holder) }
it worked –
Collenecollet RecyclerView.Adapter
why you want to destroy item inside it ? –
Involucrum holder.view.addView(view_c)
in onBindViewHolder
–
Collenecollet ViewPagerFragmentAdapter
prevent recycling? –
Stpeter Actually now there is an official samples repo for ViewPager2 (linked below)
Repo contains following samples (Quoting from the repo readme below)
Samples
- ViewPager2 with Views - shows how to set up a ViewPager2 with Views as pages
- ViewPager2 with Fragments - shows how to set up a ViewPager2 with Fragments as pages
- ViewPager2 with a Mutable Collection (Views) - demonstrates usage of ViewPager2 with Views as pages and mutations in a page adapter
- ViewPager2 with a Mutable Collection (Fragments) - demonstrates usage of ViewPager2 with Fragments as pages, and mutations in a page adapter
- ViewPager2 with a TabLayout (Views) - shows how to set up a ViewPager2 with Views as pages, and link it to a TabLayout
Simple example of ViewPager2 with Fragments in Kotlin
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="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".MainActivity">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager2_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
</LinearLayout>
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val viewPager2 = findViewById<ViewPager2>(R.id.pager2_container)
val fragmentList = arrayListOf(
FirstFragment.newInstance(),
SecondFragment.newInstance(),
ThirdFragment.newInstance()
)
viewPager2.adapter = ViewPagerAdapter(this, fragmentList)
}
}
FirstFragment.kt (SecondFragment.kt
and ThirdFragment.kt
looks similar to FirstFragment.kt
)
class FirstFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_first, container, false)
}
companion object{
fun newInstance() = FirstFragment()
}
}
ViewPagerAdapter.kt
class ViewPagerAdapter(fa:FragmentActivity, private val fragments:ArrayList<Fragment>): FragmentStateAdapter(fa) {
override fun getItemCount(): Int = fragments.size
override fun createFragment(position: Int): Fragment = fragments[position]
}
Some other useful resources:
Training: https://developer.android.com/training/animation/screen-slide-2
Release notes: https://developer.android.com/jetpack/androidx/releases/viewpager2
Medium article by a GDE : Exploring the ViewPager2
Use of ViewPager2 in Android
As mentioned on Developer Site
API changes
FragmentStateAdapter replaces FragmentStatePagerAdapter
RecyclerView.Adapter replaces PagerAdapter
registerOnPageChangeCallback replaces addPageChangeListener
In Simple Words they make it View Pager adapter work like Recycle View Adapter.
Note:- We don't need to use fragment in View Pager 2. It is fully depend on RecyclerView.Adapter inflate layout.
Here is sample gitHub repo Link
Example:-
MainActivity.class
public class MainActivity extends AppCompatActivity {
private ViewPager2 mPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportActionBar().setTitle("View Pager 2");
mPager = findViewById(R.id.pager);
mPager.setAdapter(new MyViewPagerAdapter(this));
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (R.id.change == item.getItemId()) {
mPager.setOrientation(mPager.getOrientation() != ViewPager2.ORIENTATION_VERTICAL ? ViewPager2.ORIENTATION_VERTICAL : ViewPager2.ORIENTATION_HORIZONTAL);
}
return super.onOptionsItemSelected(item);
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.constraint.ConstraintLayout>
MyViewPagerAdapter.class
public class MyViewPagerAdapter extends RecyclerView.Adapter<MyHolder> {
private Context context;
public MyViewPagerAdapter(Context context) {
this.context=context;
}
@NonNull
@Override
public MyHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new MyHolder(LayoutInflater.from(context).inflate(R.layout.cell_item, parent, false));
}
@Override
public void onBindViewHolder(@NonNull MyHolder holder, int position) {
holder.mText.setText("Page "+(position+1));
}
@Override
public int getItemCount() {
return 10;
}
}
cell_item.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="Page 1"
android:textSize="20sp" />
</android.support.constraint.ConstraintLayout>
MyHolder.class
class MyHolder extends RecyclerView.ViewHolder {
public TextView mText;
public MyHolder(@NonNull View itemView) {
super(itemView);
mText = itemView.findViewById(R.id.text);
}
}
output:
Fragment
with ViewPager2
please check my above answer i have added sample code for How use Fragment with ViewPager2
–
Involucrum Here is what I did to implemet ViewPager2 with TabLayout with 3 Fragment full Examble:
Layout contains ViewPager2
with TabLayout
:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/include3">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:background="@color/colorPrimary"
app:tabTextColor="@color/tab_dismiss_color"
app:tabSelectedTextColor="@color/green"
android:layout_height="wrap_content" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_gravity="bottom"
android:background="#e4e4e4" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
init ViewPager2 and set Tabs Name:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_report);
ButterKnife.bind(this);
actionBarTitleId.setText(R.string.reports);
viewPager.setAdapter(new ViewPagerAdapter(this));
new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> {
switch (position) {
case 0:
tab.setText(R.string.financial_duty_str);
break;
case 1:
tab.setText(R.string.financial_unpaid_str);
break;
case 2:
tab.setText(R.string.financial_paid_str);
break;
}
}).attach();
}
ViewPager2
adapter :
public class ViewPagerAdapter extends FragmentStateAdapter {
public ViewPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
super(fragmentActivity);
}
@NonNull
@Override
public Fragment createFragment(int position) {
switch (position) {
case 0:
return new FinancialFragment();
case 1:
return new FinancialUnPaidFragment();
case 2:
return new FinancialPaidFragment();
default:
return null;
}
}
@Override
public int getItemCount() {
return 3;
}
dependency used:
implementation "androidx.viewpager2:viewpager2:1.0.0"
implementation 'com.google.android.material:material:1.1.0-alpha10'
How to use it explained very clearly. Let me give small but very crucial tips and details about ViewPager2 especially if it's inside a fragment about how to prevent memory leaks
Don't use the constructor that takes fragment especially if you are using TabLayout
.
public FragmentStateAdapter(@NonNull Fragment fragment) {
this(fragment.getChildFragmentManager(), fragment.getLifecycle());
}
because it has risk of memory leak as described here
instead use the one takes FragmentManager and LifeCycle. And don't send lifeCycle
of fragment as argument, use viewLifeCycleOwner
's lifecycle because viewLifeCycleOwner represents life cycle of fragment's view
.
class NavigableFragmentStateAdapter(
fragmentManager: FragmentManager,
lifecycle: Lifecycle
) : FragmentStateAdapter(fragmentManager, lifecycle) {
}
and set adapter with in onCreateView
viewPager.adapter =
NavigableFragmentStateAdapter(childFragmentManager, viewLifecycleOwner.lifecycle)
TabLayout
when ViewPager2 is inside a fragment do not forget to detach TabLayout and set adapter of ViewPager2 asdetach TabLayoutMediator since it causing memory leaks when it's in a fragment
https://mcmap.net/q/57492/-leak-canary-detects-memory-leaks-for-tablayout-with-viewpager2
TabLayoutMediator(tabLayout, viewPager2, tabConfigurationStrategy).detach()
viewPager2.adapter = null
inside onDestroyView
method of Fragment
If you wish to use pages of ViewPager as navHostFragment
with navigation components to navigate back to child fragments register FragmentTransactionCallback in FragmentStateAdapter
as
private val fragmentTransactionCallback =
object : FragmentStateAdapter.FragmentTransactionCallback() {
override fun onFragmentMaxLifecyclePreUpdated(
fragment: Fragment,
maxLifecycleState: Lifecycle.State
) = if (maxLifecycleState == Lifecycle.State.RESUMED) {
// This fragment is becoming the active Fragment - set it to
// the primary navigation fragment in the OnPostEventListener
OnPostEventListener {
fragment.parentFragmentManager.commitNow {
setPrimaryNavigationFragment(fragment)
}
}
} else {
super.onFragmentMaxLifecyclePreUpdated(fragment, maxLifecycleState)
}
}
init {
// Add a FragmentTransactionCallback to handle changing
// the primary navigation fragment
registerFragmentTransactionCallback(fragmentTransactionCallback)
}
Also if you wish to see some samples how to use ViewPager2 with navigation components, dynamic feature modules and combining with BottomNavigationView you can check out the tutorials in this github repo.
Just in case one may want to listen for ViewPager2.OnPageChangeCallback
events:
private final ViewPager2.OnPageChangeCallback onPageChangeListener = new ViewPager2.OnPageChangeCallback() {
/**
* This method will be invoked when the current page is scrolled, either as part
* of a programmatically initiated smooth scroll or a user initiated touch scroll.
* @param position Position index of the first page currently being displayed.
* Page position+1 will be visible if positionOffset is nonzero.
* @param positionOffset Value from [0, 1) indicating the offset from the page at position.
* @param positionOffsetPixels Value in pixels indicating the offset from position.
*/
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels);
}
/**
* This method will be invoked when a new page becomes selected.
* Animation is not necessarily complete.
* @param position Position index of the first page currently being displayed.
* Page position+1 will be visible if positionOffset is nonzero.
*/
@Override
public void onPageSelected (int position) {
super.onPageSelected(position);
if (position == 2) { // SomeFragment
Log.d(LOG_TAG, "ViewPager2.onPageSelected( " + position + " )");
SomePagerAdapter adapter = (SomePagerAdapter) viewpager.getAdapter();
if (adapter != null) {
SomeFragment fragment = (SomeFragment) adapter.getItem(position);
fragment.onLateInit();
}
}
}
};
To be applied with ViewPager2.registerOnPageChangeCallback()
:
viewpager.registerOnPageChangeCallback(this.onPageChangeListener);
The FragmentStateAdapter
needs to hold an ArrayList<Fragment> mItems
, so that one can access the instances, which are being constructed ahead of time. SomeFragment
exposes method public void onLateInit()
, because @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
can possibly not be used (depends on the Fragment
).
public Fragment getItem(int position) {
return this.mItems.get(position);
}
Alike this one one work around the issue, what the Fragment
is being constructed long before one might be able to properly initialize it's view, eg. with data entered in the previous one Fragment
. It might not be the optimum for a large amount of Fragment
(...), but for a few it works pretty well.
here is kotlin version implementation of @sushildlh answer in Kotlin.
in this code I am implementing viewpager2 with recyclerview to view images.
also i am using viewbinding
food_details.xml
<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=".presentation.recipeitem.RecipeDetailsFragment">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager2"
android:layout_width="0dp"
android:layout_height="300dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/avatars" />
...
the fragment which inflates this xml
@AndroidEntryPoint
class RecipeDetailsFragment : Fragment(R.layout.fragment_recipe_details) {
private val viewModel: RecipeItemViewModel by viewModels()
private val viewBinding: FragmentRecipeDetailsBinding by viewBinding()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
bindRecipeData(//object of data that is to be desplayed)
}
private fun bindRecipeData(recipeDetailedInfo: RecipeDetailedInfo?) {
recipeDetailedInfo?.let {
with(viewBinding) {
viewPager2.adapter = ViewPagerAdapter(it.images)
viewPager2.setPageTransformer(ZoomOutPageTransformer())
lifecycleScope.launchWhenCreated {
delay(500)
viewPager2.setCurrentItem(if (it.images.size >= 2) 1 else 0, true)
}
}
}
}....
in the fragment i am creating object of the adapter and sending directly list of strings that contains the images URLs
here is the viewpager adapter which is basically a normal recyclerview adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.blogspot.soyamr.recipes2.databinding.ImageviewBinding
import com.squareup.picasso.Picasso
class ViewPagerAdapter(private val images: List<String>) :
RecyclerView.Adapter<ViewPagerAdapter.ImageViewHolder>() {
class ImageViewHolder(private val imageViewBinding: ImageviewBinding) :
RecyclerView.ViewHolder(imageViewBinding.root) {
fun bind(imageLink: String) {
Picasso.get().load(imageLink).into(imageViewBinding.root)
}
companion object {
fun from(parent: ViewGroup): ImageViewHolder {
val itemBinding =
ImageviewBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ImageViewHolder(itemBinding)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder {
return ImageViewHolder.from(parent)
}
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
holder.bind(images[position])
}
override fun getItemCount(): Int = images.size
}
in the recyclerview i am inflating this imageview.xml
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.imageview.ShapeableImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent"
android:scaleType="centerCrop"
android:theme="@style/roundedImageView"
/>
you use your complex xml view if needed
Simple example with two differente fragments, usin tabs. Main layout:
<?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:id="@+id/mainConstraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/mainViewPager"
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/mainTabLayout">
</androidx.viewpager2.widget.ViewPager2>
<com.google.android.material.tabs.TabLayout
android:id="@+id/mainTabLayout"
android:layout_width="match_parent"
android:layout_height="@dimen/toolbar_height"
android:background="@color/brown_normal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tabIndicatorColor="@color/yellow_light"
app:tabIndicatorHeight="3dp"
app:tabSelectedTextColor="@color/yellow_light"
app:tabTextColor="@color/yellow_normal"
tools:ignore="SpeakableTextPresentCheck" />
</androidx.constraintlayout.widget.ConstraintLayout>
Adapter:
public class MainToolsAdapter extends FragmentStateAdapter
{
// The quantity of tab is fixed
private static final int FRAGMENT_COUNT = 2;
// Titles for each tab
private final String[] titles = new String[FRAGMENT_COUNT];
public MainToolsAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle, Context context)
{
super(fragmentManager, lifecycle);
// Load titles for tab from resourses
titles[0] = context.getResources().getString(R.string.tab_1);
titles[1] = context.getResources().getString(R.string.tab_2);
}
@NonNull
@Override
public Fragment createFragment(int position)
{
// Create fragments according to position
if(position == 0)
{
return new FragmentTabOne();
}
return new FragmentTabTwo();
}
@Override
public int getItemCount()
{
return FRAGMENT_COUNT;
}
public String getItemTitle(int position)
{
if(position == 0)
{
return titles[0];
}
return titles[1];
}
}
Main activity OnCreate:
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Create adapter for ViewPager
mainToolsAdapter = new MainToolsAdapter(getSupportFragmentManager(), getLifecycle(), this);
// Set adapter to the ViewPager
viewPager = findViewById(R.id.mainViewPager);
viewPager.setAdapter(mainToolsAdapter);
tabLayout = findViewById(R.id.mainTabLayout);
// Create the TabLayoutMediator to asociate ViewPager2 to TabLayout
TabLayoutMediator tabLayoutMediator = new TabLayoutMediator(tabLayout, viewPager, true, new TabLayoutMediator.TabConfigurationStrategy()
{
@Override
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position)
{
// When a tab is created this is called, then we can set tab properties, in this case the text
tab.setText(mainToolsAdapter.getItemTitle(position));
}
});
// After configure we need to realice attach then Tab and ViewPager2 are asociated
tabLayoutMediator.attach();
}
gradle:
plugins {
id 'com.android.application'
}
android {
compileSdk 30
defaultConfig {
applicationId "com.xxxx.yyyy"
minSdk 19
targetSdk 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies
{
implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
Here my solution (Android Studio 3.6):
In app/build.gradle:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation('com.crashlytics.sdk.android:crashlytics:2.10.1@aar') { transitive = true; }
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.1.0-beta01'
implementation 'org.altbeacon:android-beacon-library:2.16.3'
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
implementation 'androidx.viewpager2:viewpager2:1.0.0-beta05'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation "androidx.core:core-ktx:+"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
Here my activity:
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.viewpager2.widget.ViewPager2;
import java.util.ArrayList;
import java.util.List;
import com.myproject.android.R;
import com.myproject.android.adapter.CustomFragmentStateAdapter;
import com.myproject.android.ui.fragment.BluetoothPageFragment;
import com.myproject.android.ui.fragment.QrPageFragment;
public class QRBluetoothSwipeActivity extends AppCompatActivity {
private ViewPager2 myViewPager2;
private CustomFragmentStateAdapter myAdapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
// Make sure this is before calling super.onCreate
setTheme(R.style.AppTheme); // show splash screen
super.onCreate(savedInstanceState);
setContentView(R.layout.qr_bluetooth_swipe_activity);
init();
}
private void init() {
List<Fragment> fragmentList = new ArrayList<Fragment>();
QrPageFragment m1 = new QrPageFragment();
BluetoothPageFragment m2 = new BluetoothPageFragment();
myViewPager2 = findViewById(R.id.viewPager2);
fragmentList.add(m2);
fragmentList.add(m1);
myAdapter = new CustomFragmentStateAdapter(this, fragmentList);
myViewPager2.setAdapter(myAdapter);
}
}
Here layout:
<?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=".ui.actviity.SplashDelayActivity">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager2"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Here my CustomFragmentStateAdapter
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class CustomFragmentStateAdapter extends FragmentStateAdapter {
private List<Fragment> listFragment = new ArrayList<>();
public CustomFragmentStateAdapter(FragmentActivity fa, List<Fragment> list) {
super(fa);
listFragment = list;
}
@NotNull
@Override
public Fragment createFragment(int position) {
return listFragment.get(position);
}
@Override
public int getItemCount() {
return 2;
}
}
And here my fragments:
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.myproject.android.R;
public class BluetoothPageFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.bluetooth_page_fragment, container, false);
}
}
and second fragment:
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.myproject.android.R;
public class QrPageFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.qr_page_fragment, container, false);
}
}
And as result now I use androidx.viewpager2.widget.ViewPager2
with my custom fragments.
And it's work!!!
Nice.
P.S. Another implement:
public class CustomFragmentStateAdapter extends FragmentStateAdapter {
private ArrayList<Fragment> arrayList = new ArrayList<>();
public CustomFragmentStateAdapter (FragmentActivity fa) {
super(fa);
}
public void addFragment(Fragment fragment) {
arrayList.add(fragment);
}
@Override
public int getItemCount() {
return arrayList.size();
}
@NonNull
@Override
public Fragment createFragment(int position) {
// return your fragment that corresponds to this 'position'
return arrayList.get(position);
}
}
And use like this (in activity):
myViewPager2 = findViewById(R.id.viewPager2);
myAdapter = new CustomFragmentStateAdapter (this);
myAdapter.addFragment(new QrPageFragment());
myAdapter.addFragment(new BluetoothPageFragment());
myViewPager2.setAdapter(myAdapter);
createFragment
callback of the FragmentStateAdapter
should (as the method says) create
a Fragment
. And not give a one that has already been created and is lying around. By using your approach you're messing up with FragmentManager's
operation. –
Lucialucian createFragment
returned an item by position. Everything works as expected, no need to recreate a fragment each time. But the adapter is called this way: val adapter = object : FragmentStateAdapter(childFragmentManager, viewLifecycleOwner.lifecycle)
. –
Fascine FragmentManager
is quite a complex piece of machinery (~4k lines of code) and by not following its API, you might experience some edge-cases and uncertain behavior. –
Lucialucian I first created a view pager, then migrated to view pager 2 using the instructions given in: https://developer.android.com/training/animation/vp2-migration
This is the correct implementation!
typealias FragmentBuilder = () -> Fragment
class MyAdapter(
fragmentManager: FragmentManager,
lifecycle: Lifecycle
) : FragmentStateAdapter(fragmentManager, lifecycle) {
private val fragmentBuilders = mutableListOf<FragmentBuilder>()
fun add(fragmentBuilder: FragmentBuilder) {
fragmentBuilders.add(fragmentBuilder)
}
/**
* Dynamic replacement of fragments
*/
fun set(position: Int, fragmentBuilder: FragmentBuilder) {
fragmentBuilders[position] = fragmentBuilder
}
override fun getItemCount() = fragmentBuilders.size
override fun createFragment(position: Int) = fragmentBuilders[position].invoke()
}
Don't even thank)
In Kotlin FragmentStateAdapter with ViewPager2 The primary reason to migrate is that ViewPager2 is receiving active development support and ViewPager is not. However, ViewPager2 also offers several other specific advantages. FragmentStateAdapter in ViewPager2 is a little bit different than in ViewPager as follows: 1] Instead of implementing getCount(), implement getItemCount() 2] Instead of implementing getItem(int position), implement createFragment(int position) 3] Constructor signature is different by adding Lifecycle argument which should be included in super as well So replace your ViewPagerFragmentAdapter with below
Kotlin PagerAdapter
class SurveyPagerAdapter(
private val fragmentList: MutableList< Fragment>,
fragment: FragmentManager,
lifecycle: Lifecycle) : FragmentStateAdapter(fragment,lifecycle){
override fun getItemCount(): Int = fragmentList.size
override fun createFragment(position: Int): Fragment {
return fragmentList[position]
}
fun getFragmentName(position: Int) = fragmentList[position]
fun addFragment(fragment: Fragment) {
fragmentList.add(fragment)
notifyDataSetChanged()
}}
Kotlin Activity *Just put on method in onCreate() *
private fun setUpViewPager(){
val fragmentList : MutableList< Fragment> = mutableListOf()
fragmentList.add(SurveyPageOneFragment.newInstance(true))
fragmentList.add(SurveyPageTwoFragment.newInstance(true))
fragmentList.add(SurveyPageThreeFragment.newInstance(true))
fragmentList.add(SurveyPageFourFragment.newInstance(true))
fragmentList.add(SurveyPageFiveFragment.newInstance(true))
surveyPagerAdapter = SurveyPagerAdapter(fragmentList,supportFragmentManager,lifecycle)
binding.viewPager.adapter = surveyPagerAdapter
binding.dotsIndicator.attachTo(binding.viewPager)
}
Kotlin Fragment
class SurveyPageOneFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_survey_page_one, container, false)
}
companion object {
@JvmStatic
fun newInstance(isMyBoolean: Boolean) = SurveyPageOneFragment().apply {
val fragment = SurveyPageOneFragment()
arguments = Bundle().apply {
putBoolean("REPLACE WITH A STRING CONSTANT", isMyBoolean)
}
fragment.arguments
return fragment
}
}}
xml code in viewPager2
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layoutDirection="ltr"/>
Dot indicator
lib//kotlin dot indicator implementation 'com.tbuonomo:dotsindicator:4.3'
<com.tbuonomo.viewpagerdotsindicator.DotsIndicator
android:id="@+id/dots_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:dotsColor="@color/colorSurveyEditGreenLight"
app:dotsCornerRadius="8dp"
app:dotsSize="16dp"
app:dotsSpacing="4dp"
app:dotsWidthFactor="2.5"
app:selectedDotColor="@color/white"
app:progressMode="true"
/>
I personally used ViewPager2 inside of a Fragment. This is how I do it. Example code on GitHub [https://github.com/codebyjames/Example-Using-ViewPager2-Slide-Page-Adapter]
First in onCreate
// pager adapter
val pagerAdapter = ScreenSlidePageAdapter(this@ManagerFragment)
viewPager.adapter = pagerAdapter
viewPager.setPageTransformer(ZoomOutPageTransformer())
Adapter for the ViewPager
class ScreenSlidePageAdapter(val fragment: Fragment): FragmentStateAdapter(fragment) {
val fragments = listOf(WalkThroughFragment(), PermissionsFragment(), DatastoreFragment())
override fun getItemCount(): Int {
return fragments.size
}
override fun createFragment(position: Int): Fragment {
return fragments[position]
}
}
Transformation for ViewPager2 - optional
class ZoomOutPageTransformer : ViewPager2.PageTransformer {
override fun transformPage(view: View, position: Float) {
view.apply {
val pageWidth = width
val pageHeight = height
when {
position < -1 -> { // [-Infinity,-1)
// This page is way off-screen to the left.
alpha = 0f
}
position <= 1 -> { // [-1,1]
// Modify the default slide transition to shrink the page as well
val scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position))
val vertMargin = pageHeight * (1 - scaleFactor) / 2
val horzMargin = pageWidth * (1 - scaleFactor) / 2
translationX = if (position < 0) {
horzMargin - vertMargin / 2
} else {
horzMargin + vertMargin / 2
}
// Scale the page down (between MIN_SCALE and 1)
scaleX = scaleFactor
scaleY = scaleFactor
// Fade the page relative to its size.
alpha = (MIN_ALPHA +
(((scaleFactor - MIN_SCALE) / (1 - MIN_SCALE)) * (1 - MIN_ALPHA)))
}
else -> { // (1,+Infinity]
// This page is way off-screen to the right.
alpha = 0f
}
}
}
}
}
My ManagerFragment layout (which contains the ViewPager2)
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
© 2022 - 2024 — McMap. All rights reserved.
recommend or find a book, tool, software library, tutorial or other off-site resource
Please read carefully that I am not looking for example. – Lownecked