使用ViewPager2获取当前片段

106

我正在将我的 ViewPager 迁移到 ViewPager2,因为后者应该解决前者的所有问题。不幸的是,当与 FragmentStateAdapter 一起使用时,我找不到任何方法来获取当前显示的片段。

viewPager.getCurrentItem() 给出当前显示的索引,adapter.getItem(index) 通常为当前索引创建一个新的 Fragment。除非在 getItem() 中保留对于所有创建的片段的引用,否则我不知道如何访问当前显示的片段。

对于旧的 ViewPager,一个解决方案是调用 adapter.instantiateItem(index),它将返回所需索引处的片段。

我是否遗漏了 ViewPager2 的某些功能?


1
你解决了你的问题吗? - Aditi
还没有解决方案。 - guillaume-tgl
1
fragmentManager.findFragmentById(getItemId(index).toInt()) from within the FragmentStateAdapter seems to work but only after the fragment has been added. From the docs this may not work though if the fragments are moved. Default implementation works for collections that don't add, move, remove items - Markymark
4
给未来的读者一个一般性的提示(我知道这不是你的问题)。如果你正在寻找一种通知片段已被显示的方法,你可以使用一个ViewModel,并使每个片段观察同一个ViewModel。然后,如果它们的“javaClass.name”与传递的内容匹配,那么每个片段都会做出响应。 - Markymark
可行的解决方案 https://dev59.com/P1MI5IYBdhLWcg3wn9D6#69440532 - Jaydipsinh Zala
18个回答

1
获取特定位置的片段的解决方案:
class ClubPhotoAdapter(
    private val dataList: List<MyData>,
    fm: FragmentManager,
    lifecycle: Lifecycle
) : FragmentStateAdapter(fm, lifecycle) {

    private val fragmentMap = mutableMapOf<Int, WeakReference<MyFragment>>()

    override fun getItemCount(): Int = dataList.size

    override fun createFragment(position: Int): Fragment {
        val fragment = MyFragment[dataList.getOrNull(position)]

        fragmentMap[position] = WeakReference(fragment)

        return fragment
    }

    fun getItem(position: Int): MyFragment? = fragmentMap[position]?.get()
}

1
ViewPagerAdapter旨在隐藏所有这些实现细节,这就是为什么没有直接的方法来做到这一点。
您可以尝试在getItem()中实例化片段时设置id或标签,然后使用fragmentManager.findFragmentById()fragmentManager.findFragmentByTag()检索。
然而,像这样做有点不好。这让我觉得应该在片段(或其他地方)中完成某些工作,而不是在活动中完成。
也许有另一种方法来实现您想要的内容,但是如果不知道为什么需要获取当前片段,很难提出建议。

1
由于Android会恢复状态并且不总是调用createFragment,使用WeakReference等解决方案对我没有起作用,因此我总是得到null值。

这是我的尝试,看起来工作得很好:

val fragment = childFragmentManager.fragments.first {
    it.lifecycle.currentState == Lifecycle.State.RESUMED
}

这将迭代片段并返回 RESUMED 的一个,意味着它将是当前选定的片段


0

我发现即使活动被销毁并重新创建,这对我仍然有效。

密钥可以是任何类型。假设您为片段使用了ViewModel。

fun findFragment(fragmentId: String?): YourFragment? {
    fragmentId?: return null
    return supportFragmentManager.fragments.filterIsInstance(YourFragment::class.java).find { it.viewModel.yourData.value.id == fragmentId}
}

0

为了避免使用硬编码标记 f$id set internally 寻找片段,这可能会在 Google 未来的版本中更改:

方法一:使用已恢复的页面片段过滤

假设 ViewPager2 的页面片段为 PageFragment,那么您可以在当前的 fragmentManager fragments 中查找当前的 ViewPager 片段,并检查它是否处于已恢复状态(当前在屏幕上显示)。

val fragment = supportFragmentManager.fragments
                            .find{ it is PageFragment && it.isResumed } as PageFragment

注意:如果 ViewPager 是 Fragment 的一部分,则应将 supportFragmentManager 替换为 childFragmentManager
对于 Java(API 24+):
Fragment fragment =
        getSupportFragmentManager().getFragments().stream()
                .filter(it -> it instanceof PageFragment && it.isResumed())
                .collect(Collectors.toList()).stream().findFirst().get();

方法二:将某些参数设置为PageFragment,并根据其过滤片段

Kotlin:

适配器

class MyAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) :
    FragmentStateAdapter(fragmentManager, lifecycle) {

    //.... omitted

    override fun createFragment(position: Int): Fragment 
                = PageFragment.getInstance(position)

}

页面片段:

class PageFragment : Fragment() {

    //.... omitted

    companion object {
        const val POSITION = "POSITION";
        fun getInstance(position: Int): PageFragment {
            return PageFragment().apply {
                arguments = Bundle().also {
                    it.putInt(POSITION, position)
                }
            }
        }
    }

}

使用位置参数进行过滤,以获取所需的PageFragment:

val fragment = supportFragmentManager.fragments.firstOrNull { 
             it is PageFragment && it.arguments?.getInt("POSITION") == id }  // id from the viewpager >> for instance `viewPager.currentItem`

Java(API 24+):

适配器:

class MyAdapter extends FragmentStateAdapter {
    
    // .... omitted

    @NonNull
    @Override
    public Fragment createFragment(int position) {
        return PagerFragment.newInstance(position);
    }

}

页面片段:

public class PagerFragment extends Fragment {

    // .... omitted

    public static Fragment newInstance(int position) {
        PagerFragment fragment = new PagerFragment();
        Bundle args = new Bundle();
        args.putInt("POSITION", position);
        fragment.setArguments(args);
        return fragment;
    }
}

获取特定 viewPager 项的片段:
Optional<Fragment> optionalFragment = getSupportFragmentManager().getFragments()
                    .stream()
                    .filter(it -> it instanceof PagerFragment && it.getArguments() != null && it.getArguments().getInt("POSITION") == id)
                    .findFirst();

optionalFragment.ifPresent(fragment -> {
    // This is your needed PageFragment
});

0

无需依赖标签。 ViewPager2 隐藏了实现细节,但我们都知道它有一个内部回收器,因此布局管理器会起到作用。我们可以编写类似于以下内容的代码:

fun ViewPager2.getCurrentView(): View? {
    return (getChildAt(0) as RecyclerView).layoutManager?.getChildAt(currentItem)
}

fun ViewPager2.getCurrentFragment(): Fragment? {
    return getCurrentView().findFragment()
}

如果需要,它也可以适用于任何给定的位置,而不仅仅是第一个位置。


这将返回一个 View 而不是一个 Fragment - Jahir Fiquitiva
@JahirFiquitiva,Views 上有一个名为 findFragment() 的 SDK 扩展,您可以在检索到的视图上使用它。我已经更新了我的响应来涵盖它。 - Murciegalo84

0
你可以像下面这样从 ViewPager2 获取当前片段,
adapter.getRegisteredFragment(POSITION);

示例 FragmentStateAdapter

public class ViewPagerAdapterV2 extends FragmentStateAdapter {
    private final FragmentActivity context;
    private final HashMap<Integer, Fragment> mapFragments;

    public ViewPagerAdapterV2(FragmentActivity fm) {
        super(fm);
        this.context = fm;
        this.mapFragments = new HashMap<>();
    }

    public Context getContext() {
        return context;
    }

    @Override
    public int getItemCount() {
        return NUMBER_OF_PAGES;
    }

    public Fragment getRegisteredFragment(int position) {
        return mapFragments.get(position);
    }

    @NonNull
    @Override
    public Fragment createFragment(int position) {    
        MyFragment fragment = new MyFragment();
        mapFragments.put(position, fragment);
        return fragment;
    }
}

2
这似乎是个不好的主意,因为你在存储片段,而ViewPager 可能正在回收它(根据需要销毁和重新创建)。 - Jeff Padgett
@JeffPadgett 确实,这个实现会导致内存泄漏问题。 - Sagar Pujari

-2

之前遇到了同样的问题,现在通过在适配器中添加一个对象已经解决了。

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

    }

}

创建适配器后,我使用 ViewPager2.OnPageChangeCallback() 注册了我的 ViewPager 2,并重写了它的方法 onPageSelected

现在简单地通过这个技巧来获取当前片段。

 private fun getCurrentFragment() :Fragment?{

        val fragment = (binding!!.pager.adapter as MyViewPager2Adapter).currentFragmentWeakReference?.get()

        retrun fragment
    }

我只在ViewPager2中测试过两个片段。
希望这能对你们有所帮助,谢谢!

6
这个解决方案无法处理进程配置更改(例如,手机在后台杀死应用程序)- 当发生这种情况时,createFragment不会被调用,因为它只会恢复先前的片段状态。你的弱引用将变为空。 - lawonga
3
那么你将会泄漏。 - lawonga

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接