Get current fragment with ViewPager2

后端 未结 11 1850
野的像风
野的像风 2020-12-24 01:15

I\'m migrating my ViewPager to ViewPager2 since the latter is supposed to solve all the problems of the former. Unfortunately, when using it with a

相关标签:
11条回答
  • 2020-12-24 01:36

    The ViewPagerAdapter is intended to hide all these implementation details which is why there is no straight-forward way to do it.

    You could try setting and id or tag on the fragment when you instantiate it in getItem() then use fragmentManager.findFragmentById() or fragmentManager.findFragmentByTag() to retrieve.

    Doing it like this, however, is a bit of a code smell. It suggests to me that stuff is being done in the activity when it should be done in the fragment (or elsewhere).

    Perhaps there is another approach to achieve what you want but it's hard to give suggestions without knowing why you need to get the current fragment.

    0 讨论(0)
  • 2020-12-24 01:40

    In ViewPager2 the FragmentManager by default have assigned tags to fragments like this:

    Fragment in 1st position has a tag of "f0"

    Fragment in 2nd position has a tag of "f1"

    Fragment in 3rd position has a tag of "f2" and so on... so you can get your fragment's tag and by concatenating the "f" with position of your fragment. To get the current Fragment you can get current position from the viewPager2 position and make your tag like this (For Kotlin):

    val myFragment = supportFragmentManager.findFragmentByTag("f" + viewpager.currentItem)
    

    For fragment at a certain position

    val myFragment = supportFragmentManager.findFragmentByTag("f" + position)
    

    You can cast the Fragment and always check if it is not null if you are using this technique.

    If you host your ViewPager2 in Fragment, use childFragmentManager instead.

    REMEMBER

    If you have overriden the getItemId(position: Int) in your adapter. Then your case is different. It should be:

    val myFragment = supportFragmentManager.findFragmentByTag("f" + your_id_at_that_position)
    

    OR SIMPLY:

    val myFragment = supportFragmentManager.findFragmentByTag("f" + adapter.getItemId(position))
    

    If you host your ViewPager2 in Fragment, use childFragmentManager instead of supportFragmentManager.

    0 讨论(0)
  • 2020-12-24 01:41

    May as well post my solution to this - it's the same basic approach as @Almighty 's, except I'm keeping the Fragment weak references in a lookup table in the PagerAdapter:

    private class PagerAdapter(fm: FragmentManager, lifecycle: Lifecycle) : FragmentStateAdapter(fm, lifecycle) {
    
        // only store as weak references, so you're not holding discarded fragments in memory
        private val fragmentCache = mutableMapOf<Int, WeakReference<Fragment>>()
    
        override fun getItemCount(): Int = tabList.size
        
        override fun createFragment(position: Int): Fragment {
            // return the cached fragment if there is one
            fragmentCache[position]?.get()?.let { return it }
    
            // no fragment found, time to make one - instantiate one however you
            // like and add it to the cache
            return tabList[position].fragment.newInstance()
                .also { fragmentCache[position] = WeakReference(it) }
                .also { Timber.d("Created a fragment! $it") }
        }
    
        // not necessary, but I think the code reads better if you
        // can use a getter when you want to... try to get an existing thing
        fun getFragment(position: Int) = createFragment(position)
    }
    

    and then you can call getFragment with the appropriate page number, like adapter.currentPage or whatever.

    So basically, the adapter is keeping its own cache of fragments it's created, but with WeakReferences so it's not actually holding onto them, once the components actually using the fragments are done with them, they won't be in the cache anymore. So you can hold a lookup for all the current fragments.


    You could have the getter just return the (nullable) result of the lookup, if you wanted. This version obviously creates the fragment if it doesn't already exist, which is useful if you expect it to be there. This can be handy if you're using ViewPager2.OnPageChangeCallback, which will fire with the new page number before the view pager creates the fragment - you can "get" the page, which will create and cache it, and when the pager calls createFragment it should still be in the cache and avoid recreating it.

    It's not guaranteed the weak reference won't have been garbage collected between those two moments though, so if you're setting stuff on that fragment instance (rather than just reading something from it, like a title you want to display) be aware of that!

    0 讨论(0)
  • 2020-12-24 01:44

    was facing same issue now its solved by adding one object in adapter

    class MyViewPager2Adapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) {
    
    
        private val FRAGMENTS_SIZE = 2
    
        var currentFragmentWeakReference: WeakReference<Fragment>? = null
    
        override fun getItemCount(): Int {
            return this.FRAGMENTS_SIZE
        }
    
        override fun createFragment(position: Int): Fragment {
    
            when (position) {
                0 -> {
                    currentFragmentWeakReference= MyFirstFragment()
                    return MyFirstFragment()
                }
                1 -> {
                    currentFragmentWeakReference= MySecondFragment()
                    return MySecondFragment()
                }
            }
    
            return MyFirstFragment() /for default view
    
        }
    
    }
    

    after creating adapter I registered my Viewpager 2 with ViewPager2.OnPageChangeCallback() and overrided its method onPageSelected

    now simple did this trick to get current fragment

     private fun getCurrentFragment() :Fragment?{
    
            val fragment = (binding!!.pager.adapter as MyViewPager2Adapter).currentFragmentWeakReference?.get()
    
            retrun fragment
        }
    

    I've only tested this with 2 fragments in ViewPager2

    cheers guys , hope this mayhelp you.!

    0 讨论(0)
  • 2020-12-24 01:47

    I had similar problem when migrating to ViewPager2.

    In my case I decided to use parentFragment property (I think you can make it also work for activity) and hope, that ViewPager2 will keep only the current fragment resumed. (i.e. page fragment that was resumed last is the current one.)

    So in my main fragment (HostFragment) that contains ViewPager2 view I created following property:

    private var _currentPage: WeakReference<MyPageFragment>? = null
    val currentPage
        get() = _currentPage?.get()
    
    fun setCurrentPage(page: MyPageFragment) {
        _currentPage = WeakReference(page)
    }
    

    I decided to use WeakReference, so I don't leak inactive Fragment instances

    And each of my fragments that I display inside ViewPager2 inherits from common super class MyPageFragment. This class is responsible for registering its instance with host fragment in onResume:

    override fun onResume() {
        super.onResume()
        (parentFragment as HostFragment).setCurrentPage(this)
    }
    

    I also used this class to define common interface of paged fragments:

    abstract fun someOperation1()
    
    abstract fun someOperation2()
    

    And then I can call them from the HostFragment like this:

    currentPage?.someOperation1()
    

    I'm not saying it's a nice solution, but I think it's more elegant than relying on internals of ViewPager's adapter with instantiateItem method that we had to use before.

    0 讨论(0)
提交回复
热议问题