我将ViewPager
迁移到ViewPager2
,因为后者应该可以解决前者的所有问题。不幸的是,当与FragmentStateAdapter
一起使用时,我找不到任何方法来获取当前显示的片段。
viewPager.getCurrentItem()
给出当前显示的索引,而adapter.getItem(index)
通常为当前索引创建一个新的Fragment
。除非保留对getItem()
中所有已创建片段的引用,否则我不知道如何访问当前显示的片段。
对于旧的ViewPager
,一种解决方案是调用adapter.instantiateItem(index)
,它将以所需的索引返回该片段。
我是否缺少ViewPager2
的东西?
答案 0 :(得分:30)
在ViewPager2
中,默认情况下,FragmentManager已将标签分配给片段,如下所示:
第1位的片段的标签为"f0"
第2个位置的片段的标签为"f1"
第3个位置的片段的标签为"f2"
,依此类推...因此,您可以获取片段的标签,并将“ f”与片段的位置串联起来。要获取当前的Fragment,您可以从viewPager2位置获取当前位置,并使标签像这样(对于Kotlin):
val myFragment = supportFragmentManager.findFragmentByTag("f" + viewpager.currentItem)
对于特定位置的片段
val myFragment = supportFragmentManager.findFragmentByTag("f" + position)
如果使用此技术,您可以转换Fragment并始终检查它是否不为空。
如果将ViewPager2托管在Fragment中,请改用childFragmentManager
。
记住
如果适配器中有overriden
和getItemId(position: Int)
。那么您的情况就不同了。应该是:
val myFragment = supportFragmentManager.findFragmentByTag("f" + your_id_at_that_position)
或简单地:
val myFragment = supportFragmentManager.findFragmentByTag("f" + adapter.getItemId(position))
如果您在Fragment中托管ViewPager2,请使用childFragmentManager
代替supportFragmentManager
。
答案 1 :(得分:6)
通过其标签查找当前Fragment的解决方案似乎最适合我。我已经为此创建了这些扩展功能:
fun ViewPager2.findCurrentFragment(fragmentManager: FragmentManager): Fragment? {
return fragmentManager.findFragmentByTag("f$currentItem")
}
fun ViewPager2.findFragmentAtPosition(
fragmentManager: FragmentManager,
position: Int
): Fragment? {
return fragmentManager.findFragmentByTag("f$position")
}
Activity
,请使用supportFragmentManager
或fragmentManager
。Fragment
,请使用childFragmentManager
请注意:
findFragmentAtPosition
仅适用于在ViewPager2的RecyclerView中初始化的片段。因此,您只能获得可见+ 1的位置。ViewPager2.
中删除fun ViewPager2.findFragmentAtPosition
,因为您不使用ViewPager2类中的任何内容。我认为它应该留在那儿,因为此解决方法仅适用于ViewPager2。答案 2 :(得分:4)
迁移到ViewPager2
时遇到类似的问题。
在我的情况下,我决定使用parentFragment
属性(我认为您也可以使其用于活动),并希望ViewPager2
仅保留当前的片段。 (即最后恢复的页面片段是当前片段。)
因此,在包含HostFragment
视图的主片段(ViewPager2
)中,我创建了以下属性:
private var _currentPage: WeakReference<MyPageFragment>? = null
val currentPage
get() = _currentPage?.get()
fun setCurrentPage(page: MyPageFragment) {
_currentPage = WeakReference(page)
}
我决定使用WeakReference
,所以我不会泄漏不活动的Fragment实例
我在ViewPager2
中显示的每个片段都继承自普通的超类MyPageFragment
。此类负责向onResume
中的主机片段注册其实例:
override fun onResume() {
super.onResume()
(parentFragment as HostFragment).setCurrentPage(this)
}
我还使用此类定义了分页片段的通用接口:
abstract fun someOperation1()
abstract fun someOperation2()
然后我可以像这样从HostFragment
打电话给他们:
currentPage?.someOperation1()
我并不是说这是一个不错的解决方案,但我认为它比依靠ViewPager's
适配器的内部结构和我们以前必须使用的instantiateItem
方法更为美观。
答案 3 :(得分:2)
我能够使用 reflection 来访问FragmentStateAdapter中的当前片段。
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)]
}
}
根据需要添加Kotlin反射依赖项:
implementation "org.jetbrains.kotlin:kotlin-reflect:1.3.61"
示例调用:
val tabsAdapter = viewpager.adapter as FragmentStateAdapter
val currentFragment = tabsAdapter.getItem(viewpager.currentItem)
答案 4 :(得分:1)
也可以发布我的解决方案-它与@Almighty的基本方法相同,只是我将Fragment
弱引用保留在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)
}
然后您可以使用适当的页码呼叫getFragment
,例如adapter.currentPage
或其他任何内容。
因此,基本上,适配器将保留自己创建的片段的缓存,但是使用WeakReference
时,适配器实际上并没有保留它们,一旦实际使用了片段的组件完成了使用,它们就不会在缓存中了。因此,您可以查找所有当前片段。
如果需要,您可以让吸气剂仅返回查找的(可为空)结果。如果该片段尚不存在,该版本显然会创建该片段。如果您希望它存在,这将很有用。如果您使用的是ViewPager2.OnPageChangeCallback
,这会很方便,它将在视图分页器创建片段之前的新页面编号之前触发。-您可以“获取”页面,该页面将创建并对其进行缓存,并且当寻呼机调用createFragment
时,它仍应位于缓存中,并避免重新创建它。
这不能保证弱引用不会在这两个时刻之间被垃圾收集,因此,如果您要在该片段实例上设置内容(而不是像从其中读取内容一样)您要显示的标题)请注意!
答案 5 :(得分:1)
如果您希望当前的 Fragment
只在其中执行某些操作,您可以使用 SharedViewModel
容器及其 ViewPager
之间共享的 Fragment
并传递每个片段的 Identifier
并观察 LiveData
中的 SharedViewModel
。将该 LiveData
的值设置为一个对象,该对象由您要更新的片段的 Identifier
组成(即 Pair<String, MyData>
,其中 String
是 Identifier
的类型) .然后在您的观察者内部检查当前发出的 Identifer
是否与片段的 Identifier
相同,如果相等则消耗数据。
它不像使用fragment的标签来查找它们那么简单,但至少你不需要担心ViewPager2
如何为每个fragment创建标签的变化。
答案 6 :(得分:0)
ViewPagerAdapter旨在隐藏所有这些实现细节,这就是为什么没有简单的方法做到这一点的原因。
在getItem()
中实例化片段时,您可以尝试在片段上设置,设置ID或标记,然后使用fragmentManager.findFragmentById()
或fragmentManager.findFragmentByTag()
进行检索。
但是,这样做有点代码味道。它向我暗示,应该在片段中(或其他地方)完成活动中的工作。
也许还有另一种方法可以实现您想要的,但是在不知道为什么需要获取当前片段的情况下很难给出建议。
答案 7 :(得分:0)
supportFragmentManager.findFragmentByTag("f" + viewpager.currentItem)
在FragmentStateAdapter
中与placeFragmentInViewHolder(@NonNull final FragmentViewHolder holder)
一起添加片段
mFragmentManager.beginTransaction()
.add(fragment, "f" + holder.getItemId())
.setMaxLifecycle(fragment, STARTED)
.commitNow()
答案 8 :(得分:0)
类似于这里的其他答案,这是我当前正在使用的。
我不清楚它的可靠性如何,但其中至少有一个是可以使用的。?
//Unclear how reliable this is
fun getFragmentAtIndex(index: Int): Fragment? {
return fm.findFragmentByTag("f${getItemId(index)}")
?: fm.findFragmentByTag("f$index")
?: fm.findFragmentById(getItemId(index).toInt())
?: fm.findFragmentById(index)
}
fm
是supportFragmentManager
答案 9 :(得分:0)
我有同样的问题。我使用FragmentStateAdapter从ViewPager转换为ViewPager2。就我而言,我有一个DetailActivity类(扩展了AppCompatActivity),该类包含ViewPager2,该类用于在较小尺寸的设备上分页浏览数据列表(联系人,媒体等)。
我需要知道当前显示的片段(这是我自己的类DetailFragment,它扩展了androidx.fragment.app.Fragment),因为该类包含用于更新DetailActivity工具栏上标题的字符串。
我首先按照某些人的建议开始了注册onPageChangeCallback侦听器的工作,但是我很快遇到了问题:
adapter.createFragment()
调用期间创建了标签,这是有人建议的,目的是将带有标签的新创建的片段添加到Bundle对象(使用FragmentManager.put()
)中。这样,我就可以在配置更改中保存它们。这里的问题是在createFragment()
期间,该片段实际上还不是FragmentManager的一部分,因此put()调用失败。createFragment()
调用之前被调用-因此尚无片段被创建并添加到FragmentManager,因此我无法使用“ f0”来引用该第一个片段“标签。createFragment()
调用后引用该位置,以便检索该片段-但我可以无法识别适配器内的任何类型的处理程序,相关的recyclerview,viewpager等,这些处理程序允许我显示片段列表,然后可以将该片段列表引用到该侦听器中标识的位置。奇怪的是,例如,一个看起来非常有前途的适配器方法是onViewAttachedToWindow()
-但是它被标记为final,因此不能被覆盖(即使JavaDoc明确预期它会以这种方式使用)。所以我最终为我工作的是以下内容:
public interface DetailFragmentShownListener {
// Allows classes that extend this to update visual items after shown
void onDetailFragmentShown(DetailFragment me);
}
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);
}
}
});
}
}
@Override
public void onDetailFragmentShown(DetailFragment me) {
mCurrentFragment = me;
updateToolbarTitle();
}
mCurrentFragment
是此类的一个属性,它在其他各种地方都使用过。
答案 10 :(得分:0)
按位置获取片段的解决方案:
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()
}
答案 11 :(得分:-1)
面临同样的问题,现在可以通过在适配器中添加一个对象来解决
struct ContainerView: View {
@State var hasSet = false
var body: some View {
Group {
MyView(a: $hasSet)
MyView() // should be equivalent to MyView(a: .constant(true))
}
}
}
创建适配器后,我向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
}
}
注册了Viewpager 2
并覆盖了其方法ViewPager2.OnPageChangeCallback()
现在很简单地用此技巧来获取当前片段
onPageSelected
我只在ViewPager2中用2个片段对此进行了测试
干杯,希望这对您有帮助。
答案 12 :(得分:-4)
我也遇到了您的问题,并使用了这个技巧
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;
}
您会得到这样的片段
LocalFragment f = (LocalFragment) map.get(1);