Proper Implementation of Viewpager2 in Android

Proper implementation of ViewPager2 in Android

UPDATE 7

Check : Migrate from ViewPager to ViewPager2

Check : Create swipe views with tabs using ViewPager2

UPDATE 6

Check out my answer if you want to implement Carousel using View Pager2

UPDATE 5

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

TabLayout with ViewPager2

From Docs

ViewPager2

New features

  • Right-to-left (RTL) layout support
  • Vertical orientation support
  • notifyDataSetChanged fully functional

API changes

  • FragmentStateAdapter replaces FragmentStatePagerAdapter
  • RecyclerView.Adapter replaces PagerAdapter
  • registerOnPageChangeCallback replaces addPageChangeListener

SAMPLE CODE

add the latest dependencies for ViewPager2

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

New features

now we need to use ViewPager2.OnPageChangeCallback() to get Swipe event of ViewPager2

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 in RecyclerView.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);
}
});

UPDATE

Try this if you want to use Fragment with ViewPager2

First create a ViewPagerFragmentAdapter class which extends FragmentStateAdapter

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

  • ViewPager2
  • ViewPager2 under the Hood
  • Hands on With ViewPager2

UPDATE 2

Version 1.0.0-alpha02

New features

  • Ability to disable user input (setUserInputEnabled, isUserInputEnabled)

API changes

  • ViewPager2 class final

Bug fixes

  • FragmentStateAdapter stability fixes

SAMPLE 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

UPDATE 3

Version 1.0.0-alpha03

New features

  • Ability to programmatically scroll ViewPager2: fakeDragBy(offsetPx).

API changes

  • FragmentStateAdapter now requires a Lifecycle object. Two utility constructors added to obtain it from the host FragmentActivity or the host Fragment

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

UPDATE 4

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

implementation of viewPager2

yes that link helped . I changed these lines of codes:

 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) {

}
});

viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels);
tabLayout.selectTab(tabLayout.getTabAt(position));
}
});

How to use TabLayout with ViewPager2 in Android

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()

I can't find PageChangeListener in ViewPager2.?

There is registerOnPageChangeCallback method for that:

pager.registerOnPageChangeCallback(object: ViewPager2.OnPageChangeCallback() {
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels)
}

override fun onPageSelected(position: Int) {
super.onPageSelected(position)
}

override fun onPageScrollStateChanged(state: Int) {
super.onPageScrollStateChanged(state)
}
})

Why FragmentStateAdapter not work in ViewPager2?

FragmentStateAdapter in ViewPager2 is a little bit different than in ViewPager as follows:

  • Instead of implementing getCount(), implement getItemCount()

  • Instead of implementing getItem(int position), implement createFragment(int position)

  • Constructor signature is different by adding Lifecycle argument
    which should be included in super as well

So replace your ViewPagerFragmentAdapter with below

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.Lifecycle;
import androidx.viewpager2.adapter.FragmentStateAdapter;

import java.util.ArrayList;


public class ViewPagerFragmentAdapter extends FragmentStateAdapter {

private ArrayList<Fragment> arrayList = new ArrayList<>();

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


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

}

Also make sure to import/extend the right adapter of ViewPager2

import androidx.viewpager2.adapter.FragmentStateAdapter;

UPDATE 2021

This answered the original issue of the question; However creating the FragmentStateAdapter this way is not that right generally speaking; because of:

  1. To have a high performant ViewPager, it should keep only a few instantiated off-screen page fragments.

  2. Keeping all the page fragments into a list is not good in terms of wasting device resources.

  3. createFragment(), as its name implies, is intended to create a new fragment for a particular page and return it; not to return a fragment from a list that has already instantiated fragments.

  4. Above all as per documentation createFragment()::

Provide a new Fragment associated with the specified position.

The adapter will be responsible for the Fragment lifecycle:

  • The Fragment will be used to display an item.
  • The Fragment will be destroyed when it gets too far from the
    viewport, and its state will be saved. When the item is close to the
    viewport again, a new Fragment will be requested, and a previously
    saved state will be used to initialize it.

So, I'd suggest to change this design to include that into consideration:

Assuming your page fragment is PageFragment; then you can have single instance pattern by having static PageFragment newInstance(int position) {} method in the PageFragment, and then the createFragment() should be then:

@NonNull
@Override
public Fragment createFragment(int position) {
// return a brand new fragment that corresponds to this 'position'
return PageFragment.newInstance(position); // Create a new instance of the page fragment
}


Related Topics



Leave a reply



Submit