如何使用FragmentPagerAdapter获取现有片段
我有问题,通过使用FragmentPagerAdapter
的Activity
将我的片段彼此进行通信,作为帮助程序类实现标签的pipe理以及将ViewPager
与关联的ViewPager
连接的所有详细信息。 我已经实现了FragmentPagerAdapter
就像Android示例项目Support4Demos提供的一样 。
主要的问题是我怎样才能从FragmentManager
得到特定的片段,当我没有Id或标签? FragmentPagerAdapter
正在创build片段并自动生成标识和标签。
问题的总结
注意:在这个答案中,我将引用FragmentPagerAdapter
及其源代码。 但一般的解决scheme也应该适用于FragmentStatePagerAdapter
。
如果你正在读这个,你可能已经知道FragmentPagerAdapter
/ FragmentStatePagerAdapter
是为你的ViewPager
创buildFragments
,但在Activity重新创build时(无论是从设备旋转还是系统杀死你的应用程序重新获得内存),这些Fragments
都不会被创build再次,而是他们的实例从FragmentManager
检索 。 现在说你的Activity
需要得到这些Fragments
的参考来做他们的工作。 您没有这些创build的Fragments
的id
或tag
,因为FragmentPagerAdapter
在内部设置它们 。 所以问题是如何得到一个没有这些信息的参考…
目前的解决scheme的问题:依靠内部代码
我在这个和类似的问题上看到的很多解决scheme都是通过调用FragmentManager.findFragmentByTag()
和模仿内部创build的标签来获得对现有Fragment
的引用: "android:switcher:" + viewId + ":" + id
。 与此相关的问题是您依赖内部源代码,因为我们都知道这是不能保证永远保持不变的。 Google的Android工程师可以轻松地决定更改tag
结构,这会破坏您的代码,从而无法find对现有Fragments
的引用。
不依赖于内部tag
替代解决scheme
下面是一个简单的例子,介绍如何获得对Fragments
返回的FragmentPagerAdapter
的引用,而不依赖FragmentPagerAdapter
上设置的内部tags
。 关键是覆盖instantiateItem()
并保存引用, 而不是在getItem()
。
public class SomeActivity extends Activity { private FragmentA m1stFragment; private FragmentB m2ndFragment; // other code in your Activity... private class CustomPagerAdapter extends FragmentPagerAdapter { // other code in your custom FragmentPagerAdapter... public CustomPagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { // Do NOT try to save references to the Fragments in getItem(), // because getItem() is not always called. If the Fragment // was already created then it will be retrieved from the FragmentManger // and not here (ie getItem() won't be called again). switch (position) { case 0: return new FragmentA(); case 1: return new FragmentB(); default: // This should never happen. Always account for each position above return null; } } // Here we can finally safely save a reference to the created // Fragment, no matter where it came from (either getItem() or // FragmentManger). Simply save the returned Fragment from // super.instantiateItem() into an appropriate reference depending // on the ViewPager position. @Override public Object instantiateItem(ViewGroup container, int position) { Fragment createdFragment = (Fragment) super.instantiateItem(container, position); // save the appropriate reference depending on position switch (position) { case 0: m1stFragment = (FragmentA) createdFragment; break; case 1: m2ndFragment = (FragmentB) createdFragment; break; } return createdFragment; } } public void someMethod() { // do work on the referenced Fragments, but first check if they // even exist yet, otherwise you'll get an NPE. if (m1stFragment != null) { // m1stFragment.doWork(); } if (m2ndFragment != null) { // m2ndFragment.doSomeWorkToo(); } } }
或者如果您喜欢使用tags
而不是类成员variables/对Fragments
引用,那么您也可以按照相同的方式获取由FragmentPagerAdapter
设置的tags
:注意:这不适用于FragmentStatePagerAdapter
因为它在创build时不会设置tags
其Fragments
。
@Override public Object instantiateItem(ViewGroup container, int position) { Fragment createdFragment = (Fragment) super.instantiateItem(container, position); // get the tags set by FragmentPagerAdapter switch (position) { case 0: String firstTag = createdFragment.getTag(); break; case 1: String secondTag = createdFragment.getTag(); break; } // ... save the tags somewhere so you can reference them later return createdFragment; }
请注意,此方法不依赖于模仿由FragmentPagerAdapter
设置的内部tag
,而是使用适当的API来检索它们。 这样,即使tag
在未来版本的SupportLibrary
发生变化,您仍然是安全的。
不要忘记 ,根据您的Activity
的devise,您尝试处理的Fragments
可能还可能不存在,因此您必须在使用引用之前通过执行null
检查来解决这个问题。
另外,如果你正在使用FragmentStatePagerAdapter
,那么你不想保留对你的Fragments
硬引用,因为你可能拥有很多引用,而且硬引用会不必要地将它们留在内存中。 而是将Fragment
引用保存在WeakReference
variables中而不是标准引用中。 喜欢这个:
WeakReference<Fragment> m1stFragment = new WeakReference<Fragment>(createdFragment); // ...and access them like so Fragment firstFragment = m1stFragment.get(); if (firstFragment != null) { // reference hasn't been cleared yet; do work... }
我已经find了回答我的问题基于以下post: 重复使用fragmentpageradapter中的片段
我所学到的一些事情:
-
FragmentPagerAdapter
中的getItem(int position)
相当具有误导性,这个方法实际上做了什么。 它创build新的碎片,不返回现有的碎片。 在这个意义上,该方法应该重命名为Android SDK中的createItem(int position)
。 所以这个方法不能帮助我们得到碎片。 - 根据后期支持FragmentPagerAdapterholds对旧片段的引用中的解释,您应该将片段的创build保留到
FragmentPagerAdapter
中,这意味着您没有对片段或其标签的引用。 如果你有片段标签,你可以通过调用findFragmentByTag()
从FragmentManager
轻松地获得对它的引用。 我们需要一种方法来查找给定页面位置的片段标签。
解
在你的类中添加下面的帮助器方法来检索片段标签并将其发送到findFragmentByTag()
方法。
private String getFragmentTag(int viewPagerId, int fragmentPosition) { return "android:switcher:" + viewPagerId + ":" + fragmentPosition; }
注意! 这是FragmentPagerAdapter
在创build新片段时使用的相同方法。 请参阅此链接http://code.google.com/p/openintents/source/browse/trunk/compatibility/AndroidSupportV2/src/android/support/v2/app/FragmentPagerAdapter.java#104
我创build了这个方法,它正在为我获得对当前片段的引用。
public static Fragment getCurrentFragment(ViewPager pager, FragmentPagerAdapter adapter) { try { Method m = adapter.getClass().getSuperclass().getDeclaredMethod("makeFragmentName", int.class, long.class); Field f = adapter.getClass().getSuperclass().getDeclaredField("mFragmentManager"); f.setAccessible(true); FragmentManager fm = (FragmentManager) f.get(adapter); m.setAccessible(true); String tag = null; tag = (String) m.invoke(null, pager.getId(), (long) pager.getCurrentItem()); return fm.findFragmentByTag(tag); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } return null; }
我做的方式是定义一个弱引用Hashtable如下:
protected Hashtable<Integer, WeakReference<Fragment>> fragmentReferences;
然后,我写下了这样的getItem()方法:
@Override public Fragment getItem(int position) { Fragment fragment; switch(position) { case 0: fragment = new MyFirstFragmentClass(); break; default: fragment = new MyOtherFragmentClass(); break; } fragmentReferences.put(position, new WeakReference<Fragment>(fragment)); return fragment; }
那么你可以写一个方法:
public Fragment getFragment(int fragmentId) { WeakReference<Fragment> ref = fragmentReferences.get(fragmentId); return ref == null ? null : ref.get(); }
这似乎工作得很好,我发现它比黑客less一点
"android:switcher:" + viewId + ":" + position
技巧,因为它不依赖于如何实现FragmentPagerAdapter。 当然,如果片段已经被FragmentPagerAdapter释放,或者如果尚未创build,getFragment将返回null。
如果有人发现这种方法有什么问题,评论是值得欢迎的。
您不需要重写instantiateItem
也不需要使用内部makeFragmentName
创build片段标记的makeFragmentName
。 instantiateItem
是一个公共的方法,所以你可以(实际上你应该 )在你的activity的onCreate
方法中调用它来获取(并存储一个本地var,如果你需要的话)引用你的片段的实例。 只要记住围绕一组instantiateItem
调用startUpdate
和finishUpdate
方法,如在PagerAdapter
javadoc中所述:
对PagerAdapter方法startUpdate(ViewGroup)的调用表明ViewPager的内容即将改变。 随后将对一个或多个对instantiateItem(ViewGroup,int)和/或destroyItem(ViewGroup,int,Object)的调用进行调用,并通过调用finishUpdate(ViewGroup)来通知更新结束。
所以例如这是在onCreate
方法中存储对你的标签片段的引用的方法:
public class MyActivity extends AppCompatActivity { Fragment0 tab0; Fragment1 tab1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.myLayout); ViewPager viewPager = (ViewPager) findViewById(R.id.myViewPager); MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager()); viewPager.setAdapter(adapter); TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs); tabLayout.setupWithViewPager(viewPager); adapter.startUpdate(viewPager); tab0 = (Fragment0) adapter.instantiateItem(viewPager, 0); tab1 = (Fragment1) adapter.instantiateItem(viewPager, 1); adapter.finishUpdate(viewPager); } class MyPagerAdapter extends FragmentPagerAdapter { public MyPagerAdapter(FragmentManager manager) {super(manager);} @Override public int getCount() {return 2;} @Override public Fragment getItem(int position) { if (position == 0) return new Fragment0(); if (position == 1) return new Fragment1(); return null; // or throw some exception } @Override public CharSequence getPageTitle(int position) { if (position == 0) return getString(R.string.tab0); if (position == 1) return getString(R.string.tab1); return null; // or throw some exception } } }
instantiateItem
将首先尝试从FragmentManager
获取对现有片段实例的引用,并且只有当它们不存在时,才会使用来自适配器的getItem
方法创build新的FragmentManager
(以及在FragmentManager
中的“存储”)。
一些额外的信息:
如果你没有在你的onCreate
方法中调用instantiateItem
然后finishUpdate
(和之前的startUpdate
),那么你有冒险,你的片段实例永远不会被提交给FragmentManager
:当你的活动成为前景时instantiateItem
将被自动调用来获取你的片段,但start
/ finishUpdate
可能 不 (取决于实现细节),他们基本上是做什么是开始/提交FragmentTransaction
。 这可能会导致对创build的片段实例的引用非常快(例如,当您旋转屏幕时)并重新创build的次数超过必要的次数。 根据碎片的“重”程度,可能会有不可忽视的性能后果。
@ personne3000提出的解决scheme是很好的,但它有一个问题:当活动进入后台,被系统杀死(为了获得一些空闲的内存),然后恢复, fragmentReferences
将是空的,因为getItem
wouldn'不要叫。
下面的课程处理这种情况:
public abstract class AbstractHolderFragmentPagerAdapter<F extends Fragment> extends FragmentPagerAdapter { public static final String FRAGMENT_SAVE_PREFIX = "holder"; private final FragmentManager fragmentManager; // we need to store fragment manager ourselves, because parent's field is private and has no getters. public AbstractHolderFragmentPagerAdapter(FragmentManager fm) { super(fm); fragmentManager = fm; } private SparseArray<WeakReference<F>> holder = new SparseArray<WeakReference<F>>(); protected void holdFragment(F fragment) { holdFragment(holder.size(), fragment); } protected void holdFragment(int position, F fragment) { if (fragment != null) holder.put(position, new WeakReference<F>(fragment)); } public F getHoldedItem(int position) { WeakReference<F> ref = holder.get(position); return ref == null ? null : ref.get(); } public int getHolderCount() { return holder.size(); } @Override public void restoreState(Parcelable state, ClassLoader loader) { // code inspired by Google's FragmentStatePagerAdapter implementation super.restoreState(state, loader); Bundle bundle = (Bundle) state; for (String key : bundle.keySet()) { if (key.startsWith(FRAGMENT_SAVE_PREFIX)) { int index = Integer.parseInt(key.substring(FRAGMENT_SAVE_PREFIX.length())); Fragment f = fragmentManager.getFragment(bundle, key); holdFragment(index, (F) f); } } } @Override public Parcelable saveState() { Bundle state = (Bundle) super.saveState(); if (state == null) state = new Bundle(); for (int i = 0; i < holder.size(); i++) { int id = holder.keyAt(i); final F f = getHoldedItem(i); String key = FRAGMENT_SAVE_PREFIX + id; fragmentManager.putFragment(state, key, f); } return state; } }
获取片段句柄的主要障碍是你不能依赖getItem()。 方向更改后,对片段的引用将为空,并且不会再调用getItem()。
这是一个不依赖于FragmentPagerAdapter的实现来获取标签的方法。 覆盖instantiateItem(),它将返回从getItem()创build的片段或从片段pipe理器中find的片段。
@Override public Object instantiateItem(ViewGroup container, int position) { Object value = super.instantiateItem(container, position); if (position == 0) { someFragment = (SomeFragment) value; } else if (position == 1) { anotherFragment = (AnotherFragment) value; } return value; }
我总是使用这个基类,当我需要访问子片段或主(当前可见)片段。 它不依赖于任何实现细节,并且考虑到生命周期的变化,因为在这两种情况下都会调用覆盖的方法 – 创build新的片段实例,以及何时从FragmentManager接收到实例。
public abstract class FragmentPagerAdapterExt extends FragmentPagerAdapter { private final ArrayList<Fragment> mFragments; private Fragment mPrimaryFragment; public FragmentPagerAdapterExt(FragmentManager fm) { super(fm); mFragments = new ArrayList<>(getCount()); } @Override public Object instantiateItem(ViewGroup container, int position) { Object object = super.instantiateItem(container, position); mFragments.add((Fragment) object); return object; } @Override public void destroyItem(ViewGroup container, int position, Object object) { mFragments.remove(object); super.destroyItem(container, position, object); } @Override public void setPrimaryItem(ViewGroup container, int position, Object object) { super.setPrimaryItem(container, position, object); mPrimaryFragment = (Fragment) object; } /** Returns currently visible (primary) fragment */ public Fragment getPrimaryFragment() { return mPrimaryFragment; } /** Returned list can contain null-values for not created fragments */ public List<Fragment> getFragments() { return Collections.unmodifiableList(mFragments); } }
我设法通过使用ids而不是标签来解决这个问题。 (我正在使用我定义的FragmentStatePagerAdapter,它使用我的自定义片段,其中我覆盖了onAttach方法,你保存在某处的id:
@Override public void onAttach(Context context){ super.onAttach(context); MainActivity.fragId = getId(); }
然后您只需在活动内部轻松访问片段即可:
Fragment f = getSupportFragmentManager.findFragmentById(fragId);
看到这个post从FragmentPagerAdapter返回片段。 是否依靠你知道你的片段的索引 – 但这将设置在getItem()(仅在实例化)
我不知道这是否是最好的办法,但没有别的办法为我工作。 所有其他选项,包括getActiveFragment返回null或导致应用程序崩溃。
我注意到,在屏幕旋转的片段被连接,所以我用它发送片段回到活动。
在片段中:
@Override public void onAttach(Activity activity) { super.onAttach(activity); try { mListener = (OnListInteractionListener) activity; mListener.setListFrag(this); } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement OnFragmentInteractionListener"); } }
然后在活动中:
@Override public void setListFrag(MyListFragment lf) { if (mListFragment == null) { mListFragment = lf; } }
最后在activityCreate()中:
if (savedInstanceState != null) { if (mListFragment != null) mListFragment.setListItems(items); }
这种方法将实际可见的片段附加到活动,而不创build一个新的。
只要继续尝试这个代码,
public class MYFragmentPAdp extends FragmentPagerAdapter { public MYFragmentPAdp(FragmentManager fm) { super(fm); } @Override public int getCount() { return 2; } @Override public Fragment getItem(int position) { if (position == 0) Fragment fragment = new Fragment1(); else (position == 1) Fragment fragment = new Fragment2(); return fragment; } }
- 最喜欢的内容在webview上没有正确显示
- 如何在Eclipse中为Android启用LogCat / Console?
- 无法parsing:com.android.support.design:25.4.0
- 如何在电子邮件正文中添加图片
- NETWORK_PROVIDER的LocationListener已启用,但onLocationChanged永远不会被调用
- 如何在Windows 8 API 21和19上运行Intel x86 Atom的Android模拟器,而无需硬件加速?
- 强制Android活动始终使用横向模式
- TaskStackBuilder转换animation
- 用于ListView的Android自定义行项目