Get current fragment with ViewPager2

后端 未结 11 1849
野的像风
野的像风 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:24

    I was able to get access to current fragment in FragmentStateAdapter using reflection.

    Extension function in Kotlin:

    fun FragmentStateAdapter.getItem(position: Int): Fragment? {
        return this::class.superclasses.find { it == FragmentStateAdapter::class }
            ?.java?.getDeclaredField("mFragments")
            ?.let { field ->
                field.isAccessible = true
                val mFragments = field.get(this) as LongSparseArray<Fragment>
                return@let mFragments[getItemId(position)]
            }
    }
    

    Add Kotlin reflection dependency if needed:

    implementation "org.jetbrains.kotlin:kotlin-reflect:1.3.61"
    

    Example call:

    val tabsAdapter = viewpager.adapter as FragmentStateAdapter
    val currentFragment = tabsAdapter.getItem(viewpager.currentItem)
    
    0 讨论(0)
  • 2020-12-24 01:24

    Similar to some other answers here, this is what I'm currently using.
    I'm not clear on how reliable it is but at least one of these is bound to work

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

    I had the same problem. I converted from ViewPager to ViewPager2, using FragmentStateAdapter. In my case I have a DetailActivity class (extends AppCompatActivity) which houses the ViewPager2, which is used to page through lists of data (Contacts, Media, etc.) on smaller form-factor devices.

    I need to know the currently shown fragment (which is my own class DetailFragment which extends androidx.fragment.app.Fragment), because that class contains the string I use to update the title on the DetailActivity toolbar.

    I first started down the road of registering an onPageChangeCallback listener as suggested by some, but I quickly ran into problems:

    1. I initially created tags during the ViewPager2's adapter.createFragment() call as suggested by some with the idea to add the newly created fragment to a Bundle object (using FragmentManager.put()) with that tag. This way I could then save them across config changes. The problem here is that during createFragment(), the fragment isn't actually yet part of the FragmentManager, so the put() calls fail.
    2. The other problem is that if the fundamental idea is to use the onPageSelected() method of the OnPageChangeCallback to find the fragment using the internally generated "f"+position tag names - we again have the same timing issue: The very first time in, onPageSelected() is called PRIOR to the createFragment() call on the adapter - so there are no fragments yet created and added to the FragmentManager, so I can't get a reference to that first fragment using the "f0" tag.
    3. I then tried to find a way where I could save the position passed in to onPageSelected, then try and reference that somewhere else in order to retrieve the fragment after the adapter had made the createFragment() calls - but I could not identify any type of handler within the adapter, the associated recyclerview, the viewpager etc. that allows me to surface the list of fragments that I could then reference that to the position identified within that listener. Strangely, for example, one adapter method that looked very promising was onViewAttachedToWindow() - however it is marked final so can't be overridden (even though the JavaDoc clearly anticipates it being used this way).

    So what I ended up doing that worked for me was the following:

    1. In my DetailFragment class, I created an interface that can be implemented by the hosting activity:
        public interface DetailFragmentShownListener {
            // Allows classes that extend this to update visual items after shown
            void onDetailFragmentShown(DetailFragment me);
        }
    
    1. Then I added code within onResume() of DetailFragment to see if the associated activity has implemented the DetailFragmentShownListener interface within this class, and if so I make the callback:
        public void onResume() {
            super.onResume();
            View v = getView();
            if (v!=null && v.getViewTreeObserver().isAlive()) {
                v.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        v.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                        // Let our parent know we are laid out
                        if ( getActivity() instanceof DetailFragmentShownListener ) {
                            ((DetailFragmentShownListener) getActivity()).onDetailFragmentShown(DetailFragment.this);
                        }
                    }
                });
            }
        }
    
    1. Then where I need to know when this fragment is shown (such as within DetailActivity), I implement the interface and when it receives this callback I know that this is the current fragment:
        @Override
        public void onDetailFragmentShown(DetailFragment me) {
            mCurrentFragment = me;
            updateToolbarTitle();
        }
    

    mCurrentFragment is a property of this class as its used in various other places.

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

    I also had your problem and used this trick

        Map<Integer, Fragment> map = new HashMap<>();
    
    @Override
            public Fragment createFragment(int position) {
                Fragment fragment;
                switch (position) {
                    case 0:
                        fragment = new CircularFragment();
                        map.put(0, fragment);
                        return fragment;
                    case 1:
                        fragment = new LocalFragment();
                        map.put(1, fragment);
                        return fragment;
                    case 2:
                        fragment = new SettingsFragment();
                        map.put(2, fragment);
                        return fragment;
                }
                fragment = new CircularFragment();
                map.put(0, fragment);
                return fragment;
            }
    

    and you can get fragment like this

     LocalFragment f = (LocalFragment) map.get(1);
    
    0 讨论(0)
  • 2020-12-24 01:31
    supportFragmentManager.findFragmentByTag("f" + viewpager.currentItem)
    

    with FragmentStateAdapter in placeFragmentInViewHolder(@NonNull final FragmentViewHolder holder)add Fragment

    mFragmentManager.beginTransaction()
                        .add(fragment, "f" + holder.getItemId())
                        .setMaxLifecycle(fragment, STARTED)
                        .commitNow()
    
    0 讨论(0)
  • 2020-12-24 01:32

    The solution to find current Fragment by its tag seems the most suitable for me. I've created these extension functions for that:

    fun ViewPager2.findCurrentFragment(fragmentManager: FragmentManager): Fragment? {
        return fragmentManager.findFragmentByTag("f$currentItem")
    }
    
    fun ViewPager2.findFragmentAtPosition(
        fragmentManager: FragmentManager,
        position: Int
    ): Fragment? {
        return fragmentManager.findFragmentByTag("f$position")
    }
    
    • If your ViewPager2 host is Activity, use supportFragmentManager or fragmentManager.
    • If your ViewPager2 host is Fragment, use childFragmentManager

    Note that:

    • findFragmentAtPosition will work only for Fragments that were initialized in ViewPager2's RecyclerView. Therefore you can get only the positions that are visible + 1.
    • Lint will suggest you to remove ViewPager2. from fun ViewPager2.findFragmentAtPosition, because you don't use anything from ViewPager2 class. I think it should stay there, because this workaround applies solely to ViewPager2.
    0 讨论(0)
提交回复
热议问题