Proper implementation of ViewPager2 in Android
Asked Answered
L

13

152

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.

Lownecked answered 12/2, 2019 at 5:29 Comment(6)
Here is explanation and example check it : michaelevans.org/blog/2019/02/07/hands-on-with-viewpager2Helvetian
Whoever given close votes for 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
There is an official sample: github.com/googlesamples/android-viewpager2Tamikotamil
appreciated question, look like Blog :DOnyx
@NikunjParadva Answer of Nilesh Rathod is much appreciated and also looks like blog post. The question is just simple.Lownecked
Here's an article of implementation t.ly/3qgjBice
I
294

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

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;
    }
}
Involucrum answered 12/2, 2019 at 6:11 Comment(41)
It seems like I have to make blog post :DLownecked
Awesome, I didn't get time for blogpost but I will do for sure.Lownecked
@PratikButani we have new updates for ViewPager2 using Version 1.0.0-alpha02Involucrum
Every time when I read you comment, again refreshing me to write blog :DLownecked
Early Introduction of ViewPager2 by @BirjuVachhani , check this, it might be usefulBarboza
Nice detailed description for ViewPager2, But please make a edit for, this version of ViewPager2 is released for Android X Only. so you need to add Android X dependecy to compile the project successfully.Seersucker
Is it possible to use PagerTitleStrip or PagerTabStrip with ViewPager2 or maybe there is an alternative?Sampler
Hi @NileshRathod. In update 4, is there a way to use ArrayList<Fragment> object to create fragments dynamically in ViewPagerFragmentAdapter?Celloidin
@Celloidin yes that is possible please contact me i will send u sample codeInvolucrum
how to slide automatically with indicator ?Traweek
@MahmoudAyman check this post https://mcmap.net/q/57489/-how-to-change-the-image-automatically-in-view-pager-when-it-reaches-the-last-image-it-should-come-to-the-first-image-automaticallyInvolucrum
How to destroy views like PagerAdapter's destroyItem function in the custom RecyclerView.Adapter?Collenecollet
I tried overriding onViewRecycled as override fun onViewRecycled(holder: ViewHolder) { (holder.itemView as ViewGroup).removeAllViews() super.onViewRecycled(holder) } it workedCollenecollet
@PhaniRithvij no need to destroyItem inside RecyclerView.Adapter why you want to destroy item inside it ?Involucrum
Because I'm getting multiple views laid on top of each other. As I'm doing a holder.view.addView(view_c) in onBindViewHolderCollenecollet
Wouldn't the list of fragments you have in ViewPagerFragmentAdapter prevent recycling?Stpeter
How to wrap height of Android ViewPager2 to height of current item?Precis
I think you are still using the old adapter as the new adapter is FragmentStatePagerAdapterHackamore
@AmirDora nop, I haven't updated my answer after Nov 25 '2019Involucrum
Please update it, because the new lifecycle feature BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT can be used only with FragmentStatePagerAdapterHackamore
why getItem to return Fragment no exist anymore?Spadiceous
Hi @NileshRathod, it is possible to create ViewPager2 inside a Fragment that can host other Fragments instead of using Activity?Mouth
@Ali yes that's possibleInvolucrum
I have trouble injecting viewPage2 in my fragment which is basically homeFrag, lemme debug my code and figure what I'm missing. Thanks @NileshRathodMouth
@Ali I am using viewpager with TabLayout in fragment, I am not able to swipe the screen. it's stuck on 1st fragment, how can I solve this issueColous
@kdblue have you tred to set the viewpager.isUserInputEnabled = trueMouth
@Ali I solved, it was happening because of swipe refresh layout.Colous
@Colous i am also facing the same issue, but i wanna keep the swipe refresh layout, do u have any idea how to fix it?Xerarch
@Ali viewpager.isUserInoutEnabled isn't working for meXerarch
@Nilesh Rathod I also have a viewpager2 and tablyout inside a fragment but it isn't swiping.Xerarch
@Xerarch can you please share your code let me check your codeInvolucrum
@Xerarch which artifact version you're using in your app build.gradle dependency?Mouth
@Nilesh Rathod here is the link to my question. I have the code there as well . #62810629Xerarch
@Ali implementation "androidx.viewpager2:viewpager2:1.0.0"Xerarch
@NileshRathod, you have created an arraylist of fragments but you're not calling them anywhere?Saarinen
nice and detailed post but can u add how to handle lifecycles in viewpager2 as I am not able to find any solution.. ?Yam
All answers are here, thanks a lot,I wish the google documentation team can pick a leaf of how an API migration manual ought to look likeMalleolus
That's a lot of knowledge for such a thing, I'm missing a simple TabLayout constructor where you can simply pass the array of fragments with titles and be done.Journalese
@Involucrum how can I give you more point sirAnnabal
In the last example ViewPagerFragmentAdapter has an arraylist but it's not used. Can we do return arraylist[position] in createFragment() ?Descendant
@MuhammadHanzilah Yes you canInvolucrum
A
14

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:

Armenia answered 24/6, 2019 at 15:10 Comment(2)
How can we use this type of implementation with ViewBinding?Nonunionism
@MaulikDodia nothing special is needed use as usual, please see the docs : developer.android.com/topic/libraries/view-bindingArmenia
T
7

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:

enter image description here

Toxicosis answered 12/2, 2019 at 5:50 Comment(6)
Can not we use ViewPager2 with fragments?Joliejoliet
@Joliejoliet yes you can use Fragment with ViewPager2 please check my above answer i have added sample code for How use Fragment with ViewPager2Involucrum
Thanks, but the repository is deleted.Fascine
@Fascine Which repository is deleted?Toxicosis
github.com/sushildlh/ViewPager2. See also medium.com/swlh/android-viewpager2-tablayout-3099aae2f396 for new info. :)Fascine
Here is the repo -> github.com/android/views-widgets-samples/tree/master/ViewPager2Balderas
E
7

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'
Existentialism answered 24/2, 2020 at 10:38 Comment(1)
How did you solve switching between tabs? For example when I chose 1 then 1 and 2 is loaded. Somehow skips from 1 to 2...Multiplicate
Y
2

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

  1. 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)
  1. If you are using TabLayout when ViewPager2 is inside a fragment do not forget to detach TabLayout and set adapter of ViewPager2 as

detach 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

ViewPager2 inside a fragment leaks after replacing the fragment it's in by Navigation Component navigate

TabLayoutMediator(tabLayout, viewPager2, tabConfigurationStrategy).detach()     
viewPager2.adapter = null

inside onDestroyView method of Fragment

  1. 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.

Yelp answered 23/9, 2020 at 17:54 Comment(0)
H
2

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.

Halvaard answered 11/12, 2021 at 22:52 Comment(0)
M
1

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

Miquelmiquela answered 20/3, 2021 at 7:46 Comment(0)
K
1

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'
}
Keynes answered 22/10, 2021 at 23:38 Comment(2)
That's quite a lot of code, with no explanation! Some comments and/or supporting text would be useful especially to help people understand, and derive their own solutionsScopp
@andrew I added more comments to the code for more clarityKeynes
E
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);
Earful answered 23/10, 2019 at 16:33 Comment(6)
I have used it, with recyclerview of images inside FragmentPageAdapter and its too slow as a pig running with horses.Histaminase
This is not correct. 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
@BartekLipinski. So how i "can dynamically modify the fragment collection at runtime, and ViewPager2 will correctly display the modified collection." as documentation say?Scalar
@BartekLipinski, I created a list of 4 fragments and then in 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
Just the fact that it "works as expected", doesn't mean that it "works as it should". 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 am doin the same but I noticed some duplication of fragment on orientation change if I use viewpager.setCurrentItem(savedIndex) in onCreate() . Did anyone face this problem?Descendant
B
0

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

Bracknell answered 24/12, 2020 at 14:13 Comment(0)
T
0

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)

Theona answered 11/2, 2021 at 14:59 Comment(1)
can u help me with lifecycle implementation as I am not able to find any resource regarding handling lifecycles.....Yam
R
0

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"
                />
Reverent answered 23/2, 2023 at 13:10 Comment(0)
U
-2

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" />
Underbody answered 1/5, 2022 at 15:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.