简体   繁体   中英

Start child fragment in ViewPager2

I'm using ViewPager2 with FragmentStateAdapter . Basic 3 fragments (ABC). It means, you can scroll from A to B, B to C

What I want to do?

From fragment B start child fragment D

(ABC) | D (ABC) | D From fragment B start fragment D, when the user click "back arrow" (top bar), fragment D will be destroyed and the user will back to B. (I don't want to use activity)

What I tried?

val ft: FragmentTransaction = requireActivity().supportFragmentManager.beginTransaction()
ft.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out)
ft.replace(android.R.id.content, DetailsFragment())
ft.addToBackStack(null)
ft.commit();

With the code above I have two issues.

  1. Two fragments (B and created D) overlap (I see B and D at the same time)
  2. When clicking back button (on top bar), neither fragment B or D won't close

Edit: Passing fragment manager to ViewPager2 , in main_activity in onCreate() method

pagerAdapter = PagerAdapter(supportFragmentManager, lifecycle)
binding.mainViewPager.adapter = pagerAdapter

PagerAdapter is a simple class: class PagerAdapter(fm: FragmentManager, lifecycle: Lifecycle): FragmentStateAdapter(fm, lifecycle)

Use getChildFragmentManager() insead of getSupportFragmentManager()

This is answer for going detail fragment from a ViewPager's fragment as question states.

I also made it a little bit complicated by putting viewPager into a fragment, you can skip and add it to activity if you wish.

  1. Create a layout for Activity

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


    <com.google.android.material.appbar.AppBarLayout
            android:id="@+id/appbar"
            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"
                app:popupTheme="@style/ThemeOverlay.AppCompat.ActionBar" />

    </com.google.android.material.appbar.AppBarLayout>

    <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <androidx.fragment.app.FragmentContainerView
                android:id="@+id/fragment_container_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

FragmentContainerView contains the fragment that has ViewPager2

//  Replace Fragment with DSL style
    supportFragmentManager.commit {
        replace<FragmentThatContainsViewPager>(R.id.fragment_container_view)

    }

FragmentThatContainsViewPager's layout

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">

    <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        <com.google.android.material.tabs.TabLayout
                android:id="@+id/tabLayout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:tabMode="scrollable" />

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

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Code for container fragment

class FragmentThatContainsViewPager : BaseDataBindingFragment<FragmentWithViewpagerBinding>() {

    override fun getLayoutRes(): Int {
        return R.layout.fragment_with_viewpager
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return super.onCreateView(inflater, container, savedInstanceState)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // TabLayout
        val tabLayout = dataBinding.tabLayout
        // ViewPager2
        val viewPager = dataBinding.viewPager

        /*
            🔥 Set Adapter for ViewPager inside this fragment using this Fragment,
            more specifically childFragmentManager as param
         */
        viewPager.adapter = ChildFragmentStateAdapter(this)

        // Bind tabs and viewpager
        TabLayoutMediator(tabLayout, viewPager) { tab, position ->
            tab.text = "Tab $position"
        }.attach()

    }

}

Adapter of ViewPager, you can use GenericFragmentParent.newInstance() if you wish i used FragmentFactory

class ChildFragmentStateAdapter(private val fragment: Fragment): FragmentStateAdapter(fragment) {

private val genericFragmentFactory = GenericFragmentFactory.getFragmentFactory()


override fun getItemCount(): Int = 5

override fun createFragment(position: Int): Fragment {

    genericFragmentFactory.fragID = position

    return genericFragmentFactory.instantiate(
        fragment.requireActivity().classLoader,
        GenericFragmentParent::class.java.name
    )
}

}

class GenericFragmentParent(private val fragID: Int) :
    BaseDataBindingFragment<FragmentGenericParentBinding>() {


    override fun getLayoutRes(): Int {
        return R.layout.fragment_generic_parent
    }



    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        dataBinding.btnNextPage.setOnClickListener {

            /*
                🔥🔥 Replacing in container in Activity for both fragments to
                be in same stack of supportFragmentManager
             */
            requireActivity().supportFragmentManager.commit {
                replace<GenericChildFragment>(R.id.fragment_container_view)
                    .addToBackStack(null)
            }
        }

    }

}

Also used back stack count to display back arrow on top

  supportFragmentManager.addOnBackStackChangedListener {

            val fragmentOnTop = supportFragmentManager.findFragmentById(
                R.id.fragment_container_view
            )

            val fragmentCount = supportFragmentManager.backStackEntryCount

            supportActionBar?.setDisplayHomeAsUpEnabled(fragmentCount > 0)

        }

And

override fun onSupportNavigateUp(): Boolean {
    supportFragmentManager.popBackStack()
    return true
}

to remove fragment when back arrow is touched.

I added sample here , it's in MaterialDesign module, probably add another example for navigation for child fragments of ViewPager tabs each with their own child fragments instead of opening detail fragment while you can swipe neighbor fragments of ViewPager2

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM