【发布时间】:2012-02-05 19:10:35
【问题描述】:
我使用ViewPager 和FragmentStatePagerAdapter 来托管三个不同的片段:
- [片段1]
- [片段2]
- [片段3]
当我想从FragmentActivity 中的ViewPager 获取Fragment1 时。
什么是问题,我该如何解决?
【问题讨论】:
标签: android android-fragments android-viewpager
我使用ViewPager 和FragmentStatePagerAdapter 来托管三个不同的片段:
当我想从FragmentActivity 中的ViewPager 获取Fragment1 时。
什么是问题,我该如何解决?
【问题讨论】:
标签: android android-fragments android-viewpager
主要答案取决于框架生成的名称。如果这种情况发生变化,那么它将不再起作用。
这个解决方案怎么样,覆盖你的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 时将返回 null。但我一直使用它主要是为了从ViewPager 中获取当前的Fragment:adapater.getRegisteredFragment(viewPager.getCurrentItem()),这不会返回null。
我不知道此解决方案的任何其他缺点。如果有的话,我想知道。
【讨论】:
Fragment 包装在WeakReference 中,以保证您不会阻止被破坏的片段被垃圾收集?这似乎是正确的做法......
MyPagerAdapter 因生命周期而被销毁(即旋转)时会发生什么,registerdFragments 不会丢失吗?使用MyPagerAdapter 的Activity/Fragment 是否必须将其保存在onSaveInstanceState 中,然后需要使用新的FragmentManager ref 对其进行更新?
getItem 不会在轮换时再次被调用(对于任何已经创建的片段),因为 FragmentManager 恢复了它在寻呼机。如果在每个Fragment 恢复时调用instantiateItem,那么这个解决方案实际上比我的或接受的答案更安全,更面向未来。我会考虑自己尝试一下。
对于从 ViewPager 中获取片段,这里和其他相关的 SO 线程/博客上有很多答案。我见过的每个人都坏了,他们通常似乎属于下面列出的两种类型之一。如果您只想获取当前片段,还有其他一些有效的解决方案,例如 this 此线程上的其他答案。
如果使用 FragmentPagerAdapter,请参见下文。如果使用 FragmentStatePagerAdapter 值得关注this。在 FragmentStateAdapter 中抓取不是当前索引的索引并不像它的本质那样有用,这些索引将被完全拆除,超出视野/超出 offScreenLimit 范围。
错误:维护自己的内部片段列表,在调用 FragmentPagerAdapter.getItem() 时添加
SparseArray 或Map
getItem 仅在页面第一次滚动到ViewPager.setOffscreenPageLimit(x) > 0 时调用ViewPager,如果托管Activity / Fragment 被杀死或重新启动,则内部@重新创建自定义 FragmentPagerActivity 时,987654335@ 将被清除,但在幕后将重新创建 ViewPagers 内部片段,并且 getItem 将不会为任何索引调用,因此能够从索引中获取片段将永远丢失。您可以通过FragmentManager.getFragment() 和putFragment 保存和恢复这些片段引用来解决此问题,但恕我直言,这开始变得混乱。错误:构造您自己的标签 id 匹配 FragmentPagerAdapter 中使用的标签 ID,并使用它从 FragmentManager 检索页面片段
ViewPager 内部的私有方法,可以随时更改或针对任何操作系统版本。为此解决方案重新创建的方法是
private static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
ViewPager.instantiateItem()与上述getItem() 类似但不会破坏生命周期的方法 是挂钩到instantiateItem() 而不是getItem(),因为每次创建索引时都会调用前者/访问。见this answer
FragmentViewPager从source of the latest support lib 构造您自己的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 或子类中)并意识到结果可能是null 如果尚未为该页面调用 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;
}
这是一个简单的解决方案,解决了网络上随处可见的其他两个解决方案中的问题
【讨论】:
instantiateItem() 是当您想要访问尚未访问的片段在发生生命周期事件之后并且在生命周期事件之前被访问过。我知道的一个小差异,但它导致了我的程序中的一个微妙的错误,因此我正在研究这个。
FragmentStatePagerAdapter 里面没有getItemId(pos)
将下一个方法添加到您的 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() 被称为 after ViewPager.OnPageChangeListener#onPageSelected() 我不能使用它:-(
我知道这有几个答案,但也许这会对某人有所帮助。当我需要从我的ViewPager 获取Fragment 时,我使用了一个相对简单的解决方案。在您的Activity 或Fragment 持有ViewPager,您可以使用此代码循环遍历它持有的每个Fragment。
FragmentPagerAdapter 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.
}
}
如果你知道你的Fragment在ViewPager中的位置,你可以直接打电话给getItem(knownPosition)。
如果你不知道你的Fragment在ViewPager中的位置,你可以让你的孩子Fragments用getUniqueId()这样的方法实现一个接口,并用它来区分他们.也可以循环遍历所有Fragments,查看类类型,如if(viewPagerFragment instanceof FragmentClassYouWant)
!!!编辑!!!
我发现getItem 只有在每个Fragment 需要第一次创建时才被FragmentPagerAdapter 调用,之后,Fragments 似乎被回收了使用FragmentManager。这样,FragmentPagerAdapter 的许多实现都会在 getItem 中创建 new Fragments。使用我上面的方法,这意味着我们将在每次调用getItem 时创建新的Fragments,因为我们遍历FragmentPagerAdapter 中的所有项目。因此,我找到了一种更好的方法,使用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 的“隐藏”命名约定。 Streets of Boston 的答案是一个更全面的解决方案,我现在在我的项目中使用它(而不是这种方法)。
就我而言,上述解决方案均无效。
但是,由于我在片段中使用子片段管理器,因此使用了以下内容:
Fragment f = getChildFragmentManager().getFragments().get(viewPager.getCurrentItem());
这只有在管理器中的片段对应于 viewpager 项时才有效。
【讨论】:
为了从 ViewPager 获取 当前可见片段。我正在使用这个简单的语句,它运行良好。
public Fragment getFragmentFromViewpager()
{
return ((Fragment) (mAdapter.instantiateItem(mViewPager, mViewPager.getCurrentItem())));
}
【讨论】:
我首先列出了我将要使用的所有片段 (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;
}
所以我可以把它放在一个 if 语句中,如果它在正确的片段上才会运行
Fragment tempFragment = getFragment();
if(tempFragment == MyFragmentNo2.class){
MyFragmentNo2 theFrag = (MyFragmentNo2) tempFragment;
//then you can do whatever with the fragment
theFrag.costomFunction();
}
但这只是我的 hack 和 slash 方法,但它对我有用,我使用它确实会在按下后退按钮时对我当前显示的片段进行相关更改。
【讨论】:
这是基于上面 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
}
}
}
}
【讨论】:
我找不到一种简单、干净的方法来做到这一点。但是, ViewPager 小部件只是另一个 ViewGroup ,它托管您的片段。 ViewPager 将这些片段作为直接子级。因此,您可以迭代它们(使用 .getChildCount() 和 .getChildAt() ),并查看您正在寻找的片段实例当前是否已加载到 ViewPager 并获取对它的引用。例如。您可以使用一些静态唯一 ID 字段来区分片段。
请注意,ViewPager 可能没有加载您要查找的片段,因为它是一个类似于 ListView 的虚拟化容器。
【讨论】:
FragmentPagerAdapter 是片段的工厂。如果仍在内存中,要根据其位置查找片段,请使用:
public Fragment findFragmentByPosition(int position) {
FragmentPagerAdapter fragmentPagerAdapter = getFragmentPagerAdapter();
return getSupportFragmentManager().findFragmentByTag(
"android:switcher:" + getViewPager().getId() + ":"
+ fragmentPagerAdapter.getItemId(position));
}
v4 支持 api 的示例代码。
【讨论】:
您不需要在后期需要调用getItem() 或其他方法来获取托管在ViewPager 中的Fragment 的引用。如果您想更新Fragment 内部的一些数据,请使用这种方法:Update ViewPager dynamically?
关键是在Adaper 中设置新数据并调用notifyDataSetChanged(),后者又会调用getItemPosition(),将Fragment 的引用传递给您,并让您有机会更新它。所有其他方式都需要您参考自己或其他一些不是好的解决方案。
@Override
public int getItemPosition(Object object) {
if (object instanceof UpdateableFragment) {
((UpdateableFragment) object).update(xyzData);
}
//don't return POSITION_NONE, avoid fragment recreation.
return super.getItemPosition(object);
}
【讨论】:
getItemPosition() 是适配器中的一个方法。你不会直接调用它。你有你的适配器的引用,所以你调用adapter.notifyDataSetChanged(),然后通过传递你的Fragments 的引用调用你的适配器的getItemPosition()。你可以在这里看到一个完整的实现stackoverflow.com/questions/19891828/…
Fragment 需要参考,但如何获得参考是有争议的。其他解决方案使用类似于"android:switcher:"+id 的字符串从FragmentManager 获取引用,或者从getItemosition() 返回POSITION_NONE,导致所有片段重新创建自己。
必须将 FragmentPagerAdapter 扩展至您的 ViewPager 适配器类。
如果您使用FragmentStatePagerAdapter,那么您将无法通过其ID 找到您的Fragment
public static String makeFragmentName(int viewPagerId, int index) {
return "android:switcher:" + viewPagerId + ":" + index;
}
如何使用这个方法:-
Fragment mFragment = ((FragmentActivity) getContext()).getSupportFragmentManager().findFragmentByTag(
AppMethodUtils.makeFragmentName(mViewPager.getId(), i)
);
InterestViewFragment newFragment = (InterestViewFragment) mFragment;
【讨论】:
嘿,我已经回答了这个问题here。基本上,你需要覆盖
public Object instantiateItem(ViewGroup container, int position)
FragmentStatePagerAdapter 的方法。
【讨论】:
最好的解决方案是使用我们在CodePath 创建的名为SmartFragmentStatePagerAdapter 的扩展。遵循该指南,这使得从 ViewPager 中检索片段和当前选择的片段变得更加容易。它还可以更好地管理嵌入在适配器中的片段的内存。
【讨论】:
最简单最简洁的方式。如果您在ViewPager 中的所有片段都属于不同的类,您可以按如下方式检索和区分它们:
public class MyActivity extends Activity
{
@Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
if (fragment.getClass() == MyFragment.class) {
mMyFragment = (MyFragment) fragment;
}
}
}
【讨论】:
我用一些不同的方法轻松实现了这一点。
我的自定义 FragmentAdapter.getItem 方法返回的不是 new MyFragment(),而是在 FragmentAdapter 构造函数中创建的 MyFragment 实例。
然后在我的活动中,我从适配器获取片段,检查它是否是 instanceOf 需要的片段,然后强制转换并使用所需的方法。
【讨论】:
在 /values/integers.xml 中创建整数资源 id
<integer name="page1">1</integer>
<integer name="page2">2</integer>
<integer name="page3">3</integer>
然后在PagerAdapter的getItem函数中:
public Fragment getItem(int position) {
Fragment fragment = null;
if (position == 0) {
fragment = FragmentOne.newInstance();
mViewPager.setTag(R.integer.page1,fragment);
}
else if (position == 1) {
fragment = FragmentTwo.newInstance();
mViewPager.setTag(R.integer.page2,fragment);
} else if (position == 2) {
fragment = FragmentThree.newInstance();
mViewPager.setTag(R.integer.page3,fragment);
}
return fragment;
}
然后在activity中编写这个函数来获取片段引用:
private Fragment getFragmentByPosition(int position) {
Fragment fragment = null;
switch (position) {
case 0:
fragment = (Fragment) mViewPager.getTag(R.integer.page1);
break;
case 1:
fragment = (Fragment) mViewPager.getTag(R.integer.page2);
break;
case 2:
fragment = (Fragment) mViewPager.getTag(R.integer.page3);
break;
}
return fragment;
}
通过调用上述函数获取片段引用,然后将其转换为您的自定义片段:
Fragment fragment = getFragmentByPosition(position);
if (fragment != null) {
FragmentOne fragmentOne = (FragmentOne) fragment;
}
【讨论】:
在片段管理器中迭代片段的简单方法。找到 viewpager,它有部分位置参数,放在 public static PlaceholderFragment newInstance(int sectionNumber)。
public PlaceholderFragment getFragmentByPosition(Integer pos){
for(Fragment f:getChildFragmentManager().getFragments()){
if(f.getId()==R.id.viewpager && f.getArguments().getInt("SECTNUM") - 1 == pos) {
return (PlaceholderFragment) f;
}
}
return null;
}
【讨论】:
在片段中
public int getArgument(){
return mPage;
{
public void update(){
}
在 FragmentActivity 中
List<Fragment> fragments = getSupportFragmentManager().getFragments();
for(Fragment f:fragments){
if((f instanceof PageFragment)&&(!f.isDetached())){
PageFragment pf = (PageFragment)f;
if(pf.getArgument()==pager.getCurrentItem())pf.update();
}
}
【讨论】:
在 TabLayout 中有多个用于 Fragment 的选项卡。您可以使用片段的索引通过 Tag 找到片段。
例如。 Fragment1 的索引为 0,所以在findFragmentByTag() 方法中,为 Viewpager 传递标签。使用 fragmentTransaction 后,您可以添加,替换片段。
String tag = "android:switcher:" + R.id.viewPager + ":" + 0;
Fragment1 f = (Fragment1) getSupportFragmentManager().findFragmentByTag(tag);
【讨论】:
好的适配器FragmentStatePagerAdapter我资助了一个解决方案:
在您的 FragmentActivity 中:
ActionBar mActionBar = getSupportActionBar();
mActionBar.addTab(mActionBar.newTab().setText("TAB1").setTabListener(this).setTag(Fragment.instantiate(this, MyFragment1.class.getName())));
mActionBar.addTab(mActionBar.newTab().setText("TAB2").setTabListener(this).setTag(Fragment.instantiate(this, MyFragment2.class.getName())));
mActionBar.addTab(mActionBar.newTab().setText("TAB3").setTabListener(this).setTag(Fragment.instantiate(this, MyFragment3.class.getName())));
viewPager = (STViewPager) super.findViewById(R.id.viewpager);
mPagerAdapter = new MyPagerAdapter(getSupportFragmentManager(), mActionBar);
viewPager.setAdapter(this.mPagerAdapter);
并在您的 FragmentActivity 类中创建一个方法 - 这样该方法就可以让您访问您的 Fragment,您只需为其指定您想要的 Fragment 的位置:
public Fragment getActiveFragment(int position) {
String name = MyPagerAdapter.makeFragmentName(position);
return getSupportFragmentManager().findFragmentByTag(name);
}
在您的适配器中:
public class MyPagerAdapter extends FragmentStatePagerAdapter {
private final ActionBar actionBar;
private final FragmentManager fragmentManager;
public MyPagerAdapter(FragmentManager fragmentManager, com.actionbarsherlock.app.ActionBarActionBar mActionBar) {super(fragmentManager);
this.actionBar = mActionBar;
this.fragmentManager = fragmentManager;
}
@Override
public Fragment getItem(int position) {
getSupportFragmentManager().beginTransaction().add(mTchatDetailsFragment, makeFragmentName(position)).commit();
return (Fragment)this.actionBar.getTabAt(position);
}
@Override
public int getCount() {
return this.actionBar.getTabCount();
}
@Override
public CharSequence getPageTitle(int position) {
return this.actionBar.getTabAt(position).getText();
}
private static String makeFragmentName(int viewId, int index) {
return "android:fragment:" + index;
}
}
【讨论】:
Fragment yourFragment = yourviewpageradapter.getItem(int index);
index 是适配器中片段的位置,就像您首先添加 fragment1 所以检索 fragment1 传递 index 为 0 等等休息
【讨论】: