我正在使用 ViewPager
和 FragmentStatePagerAdapter
一起托管三个不同的片段:
- [片段1]
- [片段2]
- [片段3]
当我想从 FragmentActivity
中获得 ViewPager
中的 片段1
时。
问题是什么,如何解决?
我正在使用 ViewPager
和 FragmentStatePagerAdapter
一起托管三个不同的片段:
当我想从 FragmentActivity
中获得 ViewPager
中的 片段1
时。
问题是什么,如何解决?
Fragment(State)PagerAdapter
的instantiateItem()
和destroyItem()
:public class MyPagerAdapter extends FragmentStatePagerAdapter {
SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
public MyPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public int getCount() {
return ...;
}
@Override
public Fragment getItem(int position) {
return MyFragment.newInstance(...);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
registeredFragments.put(position, fragment);
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
registeredFragments.remove(position);
super.destroyItem(container, position, object);
}
public Fragment getRegisteredFragment(int position) {
return registeredFragments.get(position);
}
}
getRegisteredFragment
时,尚未实例化的Fragment将返回null。但我大多数情况下使用它来从ViewPager
中获取当前的Fragment
:adapater.getRegisteredFragment(viewPager.getCurrentItem())
,这不会返回null
。对于从ViewPager中获取片段,这里和其他相关的SO线程/博客上有很多答案。我看到的每一个都是错误的,并且它们通常似乎分为下面列出的两种类型。如果您只想抓取当前片段,则还有一些其他有效的解决方案,例如此线程上的this。
如果使用FragmentPagerAdapter
,请参见下文。如果使用FragmentStatePagerAdapter
,值得看看this。在FragmentStateAdapter中抓取不是当前项的索引并不像它的本质那样有用,因为这些将在视图/超出屏幕限制范围后被完全拆除。
错误:维护自己的内部片段列表,在调用FragmentPagerAdapter.getItem()
时添加
SparseArray
或Map
ViewPager
中仅在第一次滚动到页面(或者如果您的ViewPager.setOffscreenPageLimit(x)
>0)时才调用getItem
,因此如果主机Activity
/Fragment
被杀死或重新启动,则在重新创建自定义FragmentPagerActivity时将清除内部SparseArray
,但是在幕后,ViewPagers内部片段将被重新创建,并且不会为任何索引调用getItem
,因此将永远失去根据索引获取片段的能力。您可以通过FragmentManager.getFragment()
和putFragment
保存和恢复这些片段引用来解决这个问题,但我认为这开始变得混乱了。错误:构建与FragmentPagerAdapter
底层使用的标记ID匹配的标记ID,然后使用它从FragmentManager
检索页面片段
ViewPager
内部的一个私有方法,可能随时或针对任何操作系统版本进行更改。为此解决方案重新创建的方法是
private static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
ViewPager.instantiateItem()
类似于上面的getItem()
方法,但不会破坏生命周期的方法是使用instantiateItem()
而不是getItem()
,因为前者每次创建/访问索引时都会被调用。请参见此答案。
FragmentViewPager
从最新支持库的源码中构建您自己的FragmentViewPager
类,并更改在其中生成片段标记的内部方法。您可以用以下代码替换它。这样做的好处是,您知道标记创建永远不会改变,并且不依赖于私有API /方法,这总是很危险的。
/**
* @param containerViewId the ViewPager this adapter is being supplied to
* @param id pass in getItemId(position) as this is whats used internally in this class
* @return the tag used for this pages fragment
*/
public static String makeFragmentName(int containerViewId, long id) {
return "android:switcher:" + containerViewId + ":" + id;
}
接着文档所说,当您想要获取用于索引的片段时,只需调用类似于此方法的内容(您可以将其放入自定义的FragmentPagerAdapter
或其子类中),请注意,如果尚未为该页面调用getItem(即尚未创建该页面),则结果可能为空。
/**
* @return may return null if the fragment has not been instantiated yet for that position - this depends on if the fragment has been viewed
* yet OR is a sibling covered by {@link android.support.v4.view.ViewPager#setOffscreenPageLimit(int)}. Can use this to call methods on
* the current positions fragment.
*/
public @Nullable Fragment getFragmentForPosition(int position)
{
String tag = makeFragmentName(mViewPager.getId(), getItemId(position));
Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
return fragment;
}
FragmentStatePagerAdapter
Д╦╜Ф╡║Ф°┴getItemId(pos)
Ф√╧ФЁ∙Ц─┌ - Jemshit Iskenderov在您的FragmentPagerAdapter中添加以下方法:
public Fragment getActiveFragment(ViewPager container, int position) {
String name = makeFragmentName(container.getId(), position);
return mFragmentManager.findFragmentByTag(name);
}
private static String makeFragmentName(int viewId, int index) {
return "android:switcher:" + viewId + ":" + index;
}
需要确保getActiveFragment(0)能够正常工作。
这里提供了一个实现在ViewPager中的解决方案https://gist.github.com/jacek-marchwicki/d6320ba9a910c514424d,如果出现问题,您将看到良好的崩溃日志。
另一个简单的解决方案:
public class MyPagerAdapter extends FragmentPagerAdapter {
private Fragment mCurrentFragment;
public Fragment getCurrentFragment() {
return mCurrentFragment;
}
//...
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
if (getCurrentFragment() != object) {
mCurrentFragment = ((Fragment) object);
}
super.setPrimaryItem(container, position, object);
}
}
setPrimaryItem()
在ViewPager.OnPageChangeListener#onPageSelected()
之后被调用,所以我不能使用它 :-( - hardysimFragmentPagerAdapter fragmentPagerAdapter = (FragmentPagerAdapter) mViewPager.getAdapter();
for(int i = 0; i < fragmentPagerAdapter.getCount(); i++) {
Fragment viewPagerFragment = fragmentPagerAdapter.getItem(i);
if(viewPagerFragment != null) {
// Do something with your Fragment
// Check viewPagerFragment.isResumed() if you intend on interacting with any views.
}
}
如果您知道在ViewPager
中您的Fragment
的位置,可以直接调用getItem(knownPosition)
方法。
如果您不知道在ViewPager
中您的Fragment
的位置,您可以让子Fragment
实现一个带有getUniqueId()
方法的接口,并使用它来区分它们。或者您可以循环遍历所有的Fragment
并检查其类类型,例如if(viewPagerFragment instanceof FragmentClassYouWant)
。
!!!编辑!!!
我发现只有当需要创建每个Fragment
时,FragmentPagerAdapter
才会调用getItem
方法,之后,似乎Fragments
是通过FragmentManager
进行回收利用的。因此,许多FragmentPagerAdapter
的实现都会在getItem
方法中创建新的Fragment
。使用上面的方法,这意味着我们将在通过FragmentPagerAdapter
中的所有项目时每次都创建新的Fragment
。由于这一点,我找到了一种更好的方法,使用FragmentManager
获取每个Fragment
(使用被接受的答案)。这是一个更完整的解决方案,并且对我来说一直运作良好。
FragmentPagerAdapter fragmentPagerAdapter = (FragmentPagerAdapter) mViewPager.getAdapter();
for(int i = 0; i < fragmentPagerAdapter.getCount(); i++) {
String name = makeFragmentName(mViewPager.getId(), i);
Fragment viewPagerFragment = getChildFragmentManager().findFragmentByTag(name);
// OR Fragment viewPagerFragment = getFragmentManager().findFragmentByTag(name);
if(viewPagerFragment != null) {
// Do something with your Fragment
if (viewPagerFragment.isResumed()) {
// Interact with any views/data that must be alive
}
else {
// Flag something for update later, when this viewPagerFragment
// returns to onResume
}
}
}
而您将需要这种方法。
private static String makeFragmentName(int viewId, int position) {
return "android:switcher:" + viewId + ":" + position;
}
ViewPager
本身设置了这些标签。这种解决方案很脆弱,因为它依赖于了解 ViewPager
的“隐藏”命名约定。Boston 街道的答案是一个更全面的解决方案,我现在在我的项目中使用它(而不是这种方法)。 - Steven Byle针对我的情况,以上解决方案均无效。
但由于我在Fragment中使用了Child Fragment Manager,因此采用了以下方法:
Fragment f = getChildFragmentManager().getFragments().get(viewPager.getCurrentItem());
仅当Manager中的fragments与viewpager项对应时,才会起作用。
public Fragment getFragmentFromViewpager()
{
return ((Fragment) (mAdapter.instantiateItem(mViewPager, mViewPager.getCurrentItem())));
}
这是基于Steven上面的回答。这将返回已附加到父活动的片段的实例。
FragmentPagerAdapter fragmentPagerAdapter = (FragmentPagerAdapter) mViewPager.getAdapter();
for(int i = 0; i < fragmentPagerAdapter.getCount(); i++) {
Fragment viewPagerFragment = (Fragment) mViewPager.getAdapter().instantiateItem(mViewPager, i);
if(viewPagerFragment != null && viewPagerFragment.isAdded()) {
if (viewPagerFragment instanceof FragmentOne){
FragmentOne oneFragment = (FragmentOne) viewPagerFragment;
if (oneFragment != null){
oneFragment.update(); // your custom method
}
} else if (viewPagerFragment instanceof FragmentTwo){
FragmentTwo twoFragment = (FragmentTwo) viewPagerFragment;
if (twoFragment != null){
twoFragment.update(); // your custom method
}
}
}
}
我通过首先创建一个包含所有碎片(List<Fragment> fragments;
)的列表,然后将它们添加到分页器中,使得当前查看的碎片更容易处理。
因此:
@Override
onCreate(){
//initialise the list of fragments
fragments = new Vector<Fragment>();
//fill up the list with out fragments
fragments.add(Fragment.instantiate(this, MainFragment.class.getName()));
fragments.add(Fragment.instantiate(this, MenuFragment.class.getName()));
fragments.add(Fragment.instantiate(this, StoresFragment.class.getName()));
fragments.add(Fragment.instantiate(this, AboutFragment.class.getName()));
fragments.add(Fragment.instantiate(this, ContactFragment.class.getName()));
//Set up the pager
pager = (ViewPager)findViewById(R.id.pager);
pager.setAdapter(new MyFragmentPagerAdapter(getSupportFragmentManager(), fragments));
pager.setOffscreenPageLimit(4);
}
public Fragment getFragment(ViewPager pager){
Fragment theFragment = fragments.get(pager.getCurrentItem());
return theFragment;
}
Fragment tempFragment = getFragment();
if(tempFragment == MyFragmentNo2.class){
MyFragmentNo2 theFrag = (MyFragmentNo2) tempFragment;
//then you can do whatever with the fragment
theFrag.costomFunction();
}
但这只是我的“hack and slash”方法,但它对我有用,当按下返回按钮时,我使用它来进行当前显示片段的相关更改。
public Object instantiateItem(ViewGroup container, int position)
方法。
MyPagerAdapter
由于生命周期(例如旋转)被销毁时,会发生什么情况?registerdFragments
会丢失吗?使用MyPagerAdapter
的Activity
/Fragment
是否必须在onSaveInstanceState
中保存它,然后需要使用新的FragmentManager
引用更新它? - Steven BylegetItem
在旋转时不会再次调用(对于已经创建的任何碎片),因为FragmentManager
恢复了它持有的pager中Fragments
的状态。如果当每个Fragment
被恢复时调用instantiateItem
,那么这个解决方案实际上比我的或被接受的答案更安全和具有未来性。我会考虑自己尝试这个解决方案。 - Steven Byle