我有问题让我的片段通过使用Activity
的{{1}}作为辅助类来实现对象的管理以及连接{{1的所有细节与关联的FragmentPagerAdapter
相关联。我已经实现了ViewPager
,就像Android示例项目 Support4Demos 所提供的那样。
主要问题是当我没有Id或Tag时,如何从TabHost
获取特定片段? FragmentPagerAdapter
正在创建片段并自动生成Id和标签。
答案 0 :(得分:171)
注意:在这个答案中,我将引用FragmentPagerAdapter
及其源代码。但一般解决方案也应适用于FragmentStatePagerAdapter
。
如果您正在阅读此内容,您可能已经知道FragmentPagerAdapter
/ FragmentStatePagerAdapter
旨在为您的Fragments
创建ViewPager
,但在活动娱乐时(无论是来自设备轮换或系统终止您的应用以重新获得内存)这些Fragments
将不会再次创建,而是创建instances retrieved from the FragmentManager
。现在说你的Activity
需要获得对这些Fragments
的引用才能对它们进行处理。由于id
set them internally,您创建的tag
没有Fragments
或FragmentPagerAdapter
。所以问题是如何在没有这些信息的情况下获得对它们的引用......
我在此问题和类似问题上看到的很多解决方案都依赖于通过调用Fragment
和模仿internally created tag: "android:switcher:" + viewId + ":" + id
来获取对现有FragmentManager.findFragmentByTag()
的引用。这样做的问题在于你依赖于内部源代码,我们都知道并不能保证它们永远保持不变。 Google的Android工程师可以轻松决定更改tag
结构,这会破坏您的代码,导致您无法找到对现有Fragments
的引用。
tag
以下是一个简单的示例,说明如何获取Fragments
返回的FragmentPagerAdapter
的引用,该引用不依赖于tags
上的内部Fragments
集。关键是覆盖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 (i.e. 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
代替Fragments
的类成员变量/引用,您还可以获取tags
设置的FragmentPagerAdapter
1}}以同样的方式:
注意:这不适用于FragmentStatePagerAdapter
,因为它在创建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;
}
请注意,此方法不依赖于模仿tag
设置的内部FragmentPagerAdapter
,而是使用适当的API来检索它们。这种方式即使tag
的未来版本中的SupportLibrary
更改,您仍然可以安全。
不要忘记,根据您Activity
的设计,您尝试工作的Fragments
可能存在,也可能不存在,所以你有在使用您的参考文献之前,通过null
检查来解释这一点。
此外,如果相反您正在使用FragmentStatePagerAdapter
,那么您不希望保留对Fragments
的硬性引用,因为您可能拥有其中许多内容硬引用会不必要地将它们留在内存中。而是将Fragment
个引用保存在WeakReference
个变量而不是标准变量中。像这样:
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...
}
答案 1 :(得分:79)
我根据以下帖子找到了我的问题的答案:reusing fragments in a fragmentpageradapter
我学到的东西很少:
getItem(int position)
中的FragmentPagerAdapter
对于此方法实际执行的操作具有相当误导性的名称。它创建新片段,而不是返回现有片段。在这个意义上,该方法应该重命名为Android SDK中的createItem(int position)
。所以这种方法无法帮助我们获取碎片。FragmentPagerAdapter
,这意味着您没有参考片段或其标签。如果您有片段标记,则可以通过调用FragmentManager
轻松地从findFragmentByTag()
检索对它的引用。我们需要一种方法来找出给定页面位置的片段标记。<强>解决方案强>
在您的类中添加以下辅助方法以检索片段标记并将其发送到findFragmentByTag()
方法。
private String getFragmentTag(int viewPagerId, int fragmentPosition)
{
return "android:switcher:" + viewPagerId + ":" + fragmentPosition;
}
请注意!这是FragmentPagerAdapter
在创建新片段时使用的相同方法。请参阅此链接http://code.google.com/p/openintents/source/browse/trunk/compatibility/AndroidSupportV2/src/android/support/v2/app/FragmentPagerAdapter.java#104
答案 2 :(得分:11)
我这样做的方法是定义WeakReferences的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();
}
这似乎运作良好,我发现它比
更少hacky"android:switcher:" + viewId + ":" + position
技巧,因为它不依赖于FragmentPagerAdapter的实现方式。 当然,如果片段已被FragmentPagerAdapter释放,或者尚未创建,则getFragment将返回null。
如果有人发现这种方法有问题,那么评论非常受欢迎。
答案 3 :(得分:10)
我创建了这个方法,它可以让我获得对当前片段的引用。
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;
}
答案 4 :(得分:10)
您不需要覆盖Visibility
,也不需要依赖于使用内部instantiateItem
方法创建片段代码的兼容性。 makeFragmentName
是一种公开方法,因此您可以(实际上应该)在您的活动的instantiateItem
方法中调用它,以获取对如果需要,可以将碎片存储在本地变量中。请记住用一组onCreate
电话包围
instantiateItem
javadoc中描述的startUpdate
和finishUpdate
方法:
调用PagerAdapter方法startUpdate(ViewGroup)表示ViewPager的内容即将更改。将跟随对instantiateItem(ViewGroup,int)和/或destroyItem(ViewGroup,int,Object)的一次或多次调用,并且将通过调用finishUpdate(ViewGroup)来发出更新结束信息。
例如,这是在PagerAdapter
方法中存储对标签片段的引用的方法:
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) findViewById(R.id.tabs)).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
方法创建新的方法,并且&#34;存储&#34;它们在getItem
以供将来使用。
其他一些信息:
如果您未在FragmentManager
方法中调用instantiateItem
/ startUpdate
所包围的finishUpdate
,那么您将冒险将您的片段实例永远不会提交< / em>到onCreate
:当您的活动变为前景FragmentManager
时,系统会自动调用instantiateItem
来获取您的碎片,但startUpdate
/ finishUpdate
可能 < em> not (取决于实施细节)他们基本上做的是开始/提交FragmentTransaction
。
此可能导致对创建的片段实例的引用非常快速地丢失(例如,当您旋转屏幕时)并且重新创建的频率远远超过必要时间。取决于&#34; heavy&#34;你的碎片,它可能会产生不可忽视的性能后果
但更重要的是,在这种情况下,存储在本地变量上的片段实例将变得陈旧:由于android平台无法从FragmentManager
获取相同的实例,因此它可能会创建并使用新的实例,而您的变量仍然会引用旧的。
答案 5 :(得分:2)
fragmentReferences
会是空的,因为getItem
不会被调用。
下面的课程处理这种情况:
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;
}
}
答案 6 :(得分:2)
获取片段句柄的主要障碍是你不能依赖getItem()。方向更改后,对片段的引用将为null,并且不会再次调用getItem()。
这是一种不依赖于FragmentPagerAdapter实现来获取标记的方法。覆盖instantiateItem(),它将返回从getItem()创建的片段或从片段管理器中找到的片段。
{{1}}
答案 7 :(得分:2)
当我需要访问子片段或主要(当前可见)片段时,我总是使用这个基类。它不依赖于任何实现细节,它会关注生命周期的变化,因为在两种情况下都会调用覆盖的方法 - 创建新的片段实例时以及从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);
}
}
答案 8 :(得分:1)
我设法通过使用ID而不是标记来解决此问题。 (我正在使用我定义的FragmentStatePagerAdapter,它使用我的自定义片段,我在其中覆盖onAttach方法,在那里你将id保存在某处:
@Override
public void onAttach(Context context){
super.onAttach(context);
MainActivity.fragId = getId();
}
然后你只需在活动中轻松访问片段:
Fragment f = getSupportFragmentManager.findFragmentById(fragId);
答案 9 :(得分:0)
有关从FragmentPagerAdapter返回片段的信息,请参阅this post。依赖于您知道片段的索引 - 但这将在getItem()中设置(仅在实例化时)
答案 10 :(得分:0)
我不知道这是否是最好的方法,但没有其他方法对我有用。 包括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;
}
}
最后在活动onCreate():
if (savedInstanceState != null) {
if (mListFragment != null)
mListFragment.setListItems(items);
}
此方法将实际可见片段附加到活动而不创建新片段。
答案 11 :(得分:0)
不确定我的方法是否是正确或最好的方法,因为我是Java / Android的相对初学者,但它确实有效(我确信它违反了面向对象的原则但没有其他解决方案适用于我的用例)。
我有一个使用带有FragmentStatePagerAdapter的ViewPager的托管活动。为了获得FragmentStatePagerAdapter创建的Fragments的引用,我在片段类中创建了一个回调接口:
public interface Callbacks {
public void addFragment (Fragment fragment);
public void removeFragment (Fragment fragment);
}
在托管活动中,我实现了接口并创建了一个LinkedHasSet来跟踪片段:
public class HostingActivity extends AppCompatActivity implements ViewPagerFragment.Callbacks {
private LinkedHashSet<Fragment> mFragments = new LinkedHashSet<>();
@Override
public void addFragment (Fragment fragment) {
mFragments.add(fragment);
}
@Override
public void removeFragment (Fragment fragment) {
mFragments.remove(fragment);
}
}
在ViewPagerFragment类中,我将片段添加到onAttach中的列表中,并在onDetach中删除它们:
public class ViewPagerFragment extends Fragment {
private Callbacks mCallbacks;
public interface Callbacks {
public void addFragment (Fragment fragment);
public void removeFragment (Fragment fragment);
}
@Override
public void onAttach (Context context) {
super.onAttach(context);
mCallbacks = (Callbacks) context;
// Add this fragment to the HashSet in the hosting activity
mCallbacks.addFragment(this);
}
@Override
public void onDetach() {
super.onDetach();
// Remove this fragment from the HashSet in the hosting activity
mCallbacks.removeFragment(this);
mCallbacks = null;
}
}
在托管活动中,您现在可以使用mFragments迭代FragmentStatePagerAdapter中当前存在的片段。
答案 12 :(得分:0)
此类无需依赖内部标签即可完成操作。警告:应该使用getFragment方法而不是getItem方法来访问片段。
public class ViewPagerAdapter extends FragmentPagerAdapter {
private final Map<Integer, Reference<Fragment>> fragments = new HashMap<>();
private final List<Callable0<Fragment>> initializers = new ArrayList<>();
private final List<String> titles = new ArrayList<>();
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
void addFragment(Callable0<Fragment> initializer, String title) {
initializers.add(initializer);
titles.add(title);
}
public Optional<Fragment> getFragment(int position) {
return Optional.ofNullable(fragments.get(position).get());
}
@Override
public Fragment getItem(int position) {
Fragment fragment = initializers.get(position).execute();
return fragment;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
fragments.put(position, new WeakReference<>(fragment));
return fragment;
}
@Override
public int getCount() {
return initializers.size();
}
@Override
public CharSequence getPageTitle(int position) {
return titles.get(position);
}
}
答案 13 :(得分:0)
FragmentStateAdapter
有 createFragment()
而不是 getItem()
。而且没有instantiateView()
这样的方法。
因此,在配置更改后重新创建托管 Activity
/Fragment
时,不会调用 createFragment()
。这意味着不会再次创建适配器中的片段,而是从 FragmentManager
检索它们的实例。
因此,如果您需要对片段进行一些处理,只需从 FragmentManager
获取它们即可。
适配器创建:
CustomFragmentAdapter adapter = new CustomFragmentAdapter(getChildFragmentManager(), getLifecycle());
在 FragmentManager
/Activity
重新加载后从 Fragment
检索片段:
FragmentManager manager = getChildFragmentManager();
ArrayList<Fragment> fragments = new ArrayList<>(manager.getFragments());
for (Fragment fr : fragments) {
// do something
}
答案 14 :(得分:-5)
继续尝试此代码,
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;
}
}