作为 ViewPager 的一部分更新 ListFragment 中的数据
Posted
技术标签:
【中文标题】作为 ViewPager 的一部分更新 ListFragment 中的数据【英文标题】:Update data in ListFragment as part of ViewPager 【发布时间】:2011-11-14 19:15:49 【问题描述】:我在 android 中使用 v4 兼容性 ViewPager。我的 FragmentActivity 有一堆数据,这些数据将以不同的方式显示在我的 ViewPager 的不同页面上。到目前为止,我只有 3 个相同 ListFragment 的实例,但将来我将拥有 3 个不同 ListFragment 的实例。 ViewPager 位于垂直手机屏幕上,列表不是并排的。
现在 ListFragment 上的一个按钮启动一个单独的整页活动(通过 FragmentActivity),该活动返回,FragmentActivity 修改数据、保存数据,然后尝试更新其 ViewPager 中的所有视图。就在这里,我被卡住了。
public class ProgressMainActivity extends FragmentActivity
MyAdapter mAdapter;
ViewPager mPager;
@Override
public void onCreate(Bundle savedInstanceState)
...
mAdapter = new MyAdapter(getSupportFragmentManager());
mPager = (ViewPager) findViewById(R.id.viewpager);
mPager.setAdapter(mAdapter);
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
...
updateFragments();
...
public void updateFragments()
//Attempt 1:
//mAdapter.notifyDataSetChanged();
//mPager.setAdapter(mAdapter);
//Attempt 2:
//HomeListFragment fragment = (HomeListFragment) getSupportFragmentManager().findFragmentById(mAdapter.fragId[0]);
//fragment.updateDisplay();
public static class MyAdapter extends FragmentPagerAdapter implements
TitleProvider
int[] fragId = 0,0,0,0,0;
public MyAdapter(FragmentManager fm)
super(fm);
@Override
public String getTitle(int position)
return titles[position];
@Override
public int getCount()
return titles.length;
@Override
public Fragment getItem(int position)
Fragment frag = HomeListFragment.newInstance(position);
//Attempt 2:
//fragId[position] = frag.getId();
return frag;
@Override
public int getItemPosition(Object object)
return POSITION_NONE; //To make notifyDataSetChanged() do something
public class HomeListFragment extends ListFragment
...
public static HomeListFragment newInstance(int num)
HomeListFragment f = new HomeListFragment();
...
return f;
...
现在你可以看到,我的第一次尝试是在整个 FragmentPagerAdapter 上通知DataSetChanged,这表明有时会更新数据,但其他我得到一个 IllegalStateException:在 onSaveInstanceState 之后无法执行此操作。
我的第二次尝试涉及尝试在我的 ListFragment 中调用更新函数,但 getItem 中的 getId 返回 0。根据我尝试过的文档
从 FragmentManager 获取对 Fragment 的引用,使用 findFragmentById() 或 findFragmentByTag()
但我不知道我的片段的标签或 ID!我在 ListFragment 布局中有一个用于 ViewPager 的 android:id="@+id/viewpager" 和一个用于我的 ListView 的 android:id="@android:id/list",但我认为这些没有用。
那么,我该怎么做: a) 一次安全地更新整个 ViewPager(理想情况下将用户返回到他之前所在的页面) - 用户可以看到视图更改。 或者最好是, b) 在每个受影响的 ListFragment 中调用一个函数来手动更新 ListView。
我们将不胜感激地接受任何帮助!
【问题讨论】:
【参考方案1】:Barkside 的答案适用于FragmentPagerAdapter
,但不适用于FragmentStatePagerAdapter
,因为它不会在传递给FragmentManager
的片段上设置标签。
使用FragmentStatePagerAdapter
似乎我们可以通过使用它的instantiateItem(ViewGroup container, int position)
调用。它返回对位置position
的片段的引用。如果FragmentStatePagerAdapter
已经持有对相关片段的引用,instantiateItem
只会返回对该片段的引用,并且不会调用getItem()
来再次实例化它。
所以,假设我目前正在查看片段 #50,并且想要访问片段 #49。因为它们很接近,所以#49 很有可能已经被实例化了。所以,
ViewPager pager = findViewById(R.id.viewpager);
FragmentStatePagerAdapter a = (FragmentStatePagerAdapter) pager.getAdapter();
MyFragment f49 = (MyFragment) a.instantiateItem(pager, 49)
【讨论】:
我将通过将“a”设置为 PagerAdapter (PagerAdapter a = pager.getAdapter();) 来简化这一点。甚至 MyFragment f49 = (MyFragment) pager.getAdapter().instantiateItem(pager, 49); instanceiteItem() 来自 PagerAdapter,因此您不必对它进行类型转换即可调用 instantiateItem() ... 万一有人想知道,ViewPager 确实会跟踪查看片段的索引 (ViewPager.getCurrentItem()),即上面示例中的 49...但我不得不说,我很惊讶 ViewPager API 不会直接返回对实际 Fragment 的引用。 使用FragmentStatePagerAdapter
,使用@mblackwell8 建议的a.instantiateItem(viewPager, viewPager.getCurrentItem());
(摆脱49)上面的代码完美工作。
等等,所以我必须将 ViewPager 传递给它包含的每个片段,才能对片段中的视图执行操作?目前我在当前页面的 onCreate 中所做的一切都发生在下一页。这听起来非常可疑。
重要的是要提到任何对instantiateItem
的调用都应该被startUpdate
/finishUpdate
调用包围。详细信息在我对类似问题的回答中:***.com/questions/14035090/…【参考方案2】:
好的,我想我已经找到了一种在我自己的问题中执行请求 b) 的方法,所以我会为了他人的利益而分享。 ViewPager 中 Fragment 的标签形式为"android:switcher:VIEWPAGER_ID:INDEX"
,其中VIEWPAGER_ID
是XML 布局中的R.id.viewpager
,INDEX 是viewpager 中的位置。所以如果位置已知(例如0),我可以在updateFragments()
执行:
HomeListFragment fragment =
(HomeListFragment) getSupportFragmentManager().findFragmentByTag(
"android:switcher:"+R.id.viewpager+":0");
if(fragment != null) // could be null if not instantiated yet
if(fragment.getView() != null)
// no need to call if fragment's onDestroyView()
//has since been called.
fragment.updateDisplay(); // do what updates are required
我不知道这是否是一种有效的方法,但在有人提出更好的建议之前它会起作用。
【讨论】:
我登录只是为了 +1 你的回答和问题。 由于官方文档中没有,我不会使用它。如果他们在未来的版本中更改标签怎么办? FragmentPagerAdapter 带有一个静态方法makeFragmentName
用于生成标签,您可以使用它来代替稍微不那么麻烦的方法:HomeListFragment fragment = (HomeListFragment) getSupportFragmentManager().findFragmentByTag(FragmentPagerAdapter.makeFragmentName(R.id.viewpager, 0));
@dgmltn, makeFragmentName
是private static
方法,所以你无法访问它。
耶稣的慈母,这行得通!我会保持手指交叉并使用它。【参考方案3】:
尝试在每次实例化一个 Fragment 时记录标签。
public class MPagerAdapter extends FragmentPagerAdapter
private Map<Integer, String> mFragmentTags;
private FragmentManager mFragmentManager;
public MPagerAdapter(FragmentManager fm)
super(fm);
mFragmentManager = fm;
mFragmentTags = new HashMap<Integer, String>();
@Override
public int getCount()
return 10;
@Override
public Fragment getItem(int position)
return Fragment.instantiate(mContext, AFragment.class.getName(), null);
@Override
public Object instantiateItem(ViewGroup container, int position)
Object obj = super.instantiateItem(container, position);
if (obj instanceof Fragment)
// record the fragment tag here.
Fragment f = (Fragment) obj;
String tag = f.getTag();
mFragmentTags.put(position, tag);
return obj;
public Fragment getFragment(int position)
String tag = mFragmentTags.get(position);
if (tag == null)
return null;
return mFragmentManager.findFragmentByTag(tag);
【讨论】:
这看起来不错,但在我的课堂上不起作用。它对你有用吗?我仍然收到null
的片段。
它对我有用,我在几个项目中使用它。可以尝试阅读 FragmentPagerAdapter 的源码,打印一些日志进行调试。
感谢即使在旋转屏幕后也很有魅力。即使我能够使用 FragmentB 中的 MyActivity 更改 FragmentA 的内容,使用此解决方案。
它适用于FragmentPagerAdaper
,但不适用于FragmentStatePagerAdaper
(Fragment.getTag()
总是返回null
。
我尝试了这个解决方案,创建了一个更新片段数据的方法。但是现在我看不到片段的视图。我可以看到数据会碎片化,但视图不可见。有什么帮助吗?【参考方案4】:
如果你问我,下面页面上的第二个解决方案,跟踪所有“活动”片段页面,更好:http://tamsler.blogspot.nl/2011/11/android-viewpager-and-fragments-part-ii.html
barkside 的回答对我来说太老套了。
您跟踪所有“活动”片段页面。在这种情况下,您在 ViewPager 使用的 FragmentStatePagerAdapter 中跟踪片段页面。
private final SparseArray<Fragment> mPageReferences = new SparseArray<Fragment>();
public Fragment getItem(int index)
Fragment myFragment = MyFragment.newInstance();
mPageReferences.put(index, myFragment);
return myFragment;
为了避免保留对“非活动”片段页面的引用,我们需要实现 FragmentStatePagerAdapter 的 destroyItem(...) 方法:
public void destroyItem(View container, int position, Object object)
super.destroyItem(container, position, object);
mPageReferences.remove(position);
...当您需要访问当前可见的页面时,您可以调用:
int index = mViewPager.getCurrentItem();
MyAdapter adapter = ((MyAdapter)mViewPager.getAdapter());
MyFragment fragment = adapter.getFragment(index);
... MyAdapter 的 getFragment(int) 方法如下所示:
public MyFragment getFragment(int key)
return mPageReferences.get(key);
"
【讨论】:
@Frank 链接中的第二个解决方案很简单...!谢谢..:) 如果使用 SparseArray 而不是地图会更好 或SparseArrayCompat 更新了我的答案,因此它使用 SparseArray,如果您的目标 这将中断生命周期事件。见***.com/a/24662049/236743【参考方案5】:好的,在上面通过@barkside 测试该方法后,我无法让它与我的应用程序一起使用。然后我记得iosched2012 app 也使用viewpager
,这就是我找到解决方案的地方。它不使用任何片段 ID 或标签,因为 viewpager
不会以易于访问的方式存储这些 ID 或标签。
这是来自 IOSched 应用 HomeActivity 的important parts。请特别注意评论,因为它是关键。:
@Override
protected void onSaveInstanceState(Bundle outState)
super.onSaveInstanceState(outState);
// Since the pager fragments don't have known tags or IDs, the only way to persist the
// reference is to use putFragment/getFragment. Remember, we're not persisting the exact
// Fragment instance. This mechanism simply gives us a way to persist access to the
// 'current' fragment instance for the given fragment (which changes across orientation
// changes).
//
// The outcome of all this is that the "Refresh" menu button refreshes the stream across
// orientation changes.
if (mSocialStreamFragment != null)
getSupportFragmentManager().putFragment(outState, "stream_fragment",
mSocialStreamFragment);
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState)
super.onRestoreInstanceState(savedInstanceState);
if (mSocialStreamFragment == null)
mSocialStreamFragment = (SocialStreamFragment) getSupportFragmentManager()
.getFragment(savedInstanceState, "stream_fragment");
并像这样将Fragments
的实例存储在FragmentPagerAdapter
中:
private class HomePagerAdapter extends FragmentPagerAdapter
public HomePagerAdapter(FragmentManager fm)
super(fm);
@Override
public Fragment getItem(int position)
switch (position)
case 0:
return (mMyScheduleFragment = new MyScheduleFragment());
case 1:
return (mExploreFragment = new ExploreFragment());
case 2:
return (mSocialStreamFragment = new SocialStreamFragment());
return null;
另外,请记住保护您的 Fragment
呼叫,如下所示:
if (mSocialStreamFragment != null)
mSocialStreamFragment.refresh();
【讨论】:
Ryan,这是否是说您正在覆盖 ViewPager 用来“保留”您的片段实例并将它们设置为可访问的公共变量的函数?为什么在恢复实例上有 if (mSocialStreamFragment == null) ? 这是一个不错的答案,对于 io12 应用程序引用 +1 - 不确定这是否会在生命周期更改中起作用,尽管由于生命周期更改在重新创建父级后未调用getItem()
。见***.com/a/24662049/236743。在示例中,这部分是针对一个片段而不是其他两个片段进行防范的【参考方案6】:
您可以复制FragmentPagerAdapter
并修改一些源代码,添加getTag()
方法
例如
public abstract class AppFragmentPagerAdapter extends PagerAdapter
private static final String TAG = "FragmentPagerAdapter";
private static final boolean DEBUG = false;
private final FragmentManager mFragmentManager;
private FragmentTransaction mCurTransaction = null;
private Fragment mCurrentPrimaryItem = null;
public AppFragmentPagerAdapter(FragmentManager fm)
mFragmentManager = fm;
public abstract Fragment getItem(int position);
@Override
public void startUpdate(ViewGroup container)
@Override
public Object instantiateItem(ViewGroup container, int position)
if (mCurTransaction == null)
mCurTransaction = mFragmentManager.beginTransaction();
final long itemId = getItemId(position);
String name = getTag(position);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null)
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
else
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
getTag(position));
if (fragment != mCurrentPrimaryItem)
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
return fragment;
@Override
public void destroyItem(ViewGroup container, int position, Object object)
if (mCurTransaction == null)
mCurTransaction = mFragmentManager.beginTransaction();
if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
+ " v=" + ((Fragment) object).getView());
mCurTransaction.detach((Fragment) object);
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object)
Fragment fragment = (Fragment) object;
if (fragment != mCurrentPrimaryItem)
if (mCurrentPrimaryItem != null)
mCurrentPrimaryItem.setMenuVisibility(false);
mCurrentPrimaryItem.setUserVisibleHint(false);
if (fragment != null)
fragment.setMenuVisibility(true);
fragment.setUserVisibleHint(true);
mCurrentPrimaryItem = fragment;
@Override
public void finishUpdate(ViewGroup container)
if (mCurTransaction != null)
mCurTransaction.commitAllowingStateLoss();
mCurTransaction = null;
mFragmentManager.executePendingTransactions();
@Override
public boolean isViewFromObject(View view, Object object)
return ((Fragment) object).getView() == view;
@Override
public Parcelable saveState()
return null;
@Override
public void restoreState(Parcelable state, ClassLoader loader)
public long getItemId(int position)
return position;
private static String makeFragmentName(int viewId, long id)
return "android:switcher:" + viewId + ":" + id;
protected abstract String getTag(int position);
然后扩展它,重写这些抽象方法,不用害怕Android Group改变
FragmentPageAdapter
以后的源码
class TimeLinePagerAdapter extends AppFragmentPagerAdapter
List<Fragment> list = new ArrayList<Fragment>();
public TimeLinePagerAdapter(FragmentManager fm)
super(fm);
list.add(new FriendsTimeLineFragment());
list.add(new MentionsTimeLineFragment());
list.add(new CommentsTimeLineFragment());
public Fragment getItem(int position)
return list.get(position);
@Override
protected String getTag(int position)
List<String> tagList = new ArrayList<String>();
tagList.add(FriendsTimeLineFragment.class.getName());
tagList.add(MentionsTimeLineFragment.class.getName());
tagList.add(CommentsTimeLineFragment.class.getName());
return tagList.get(position);
@Override
public int getCount()
return list.size();
【讨论】:
【参考方案7】:也可以正常工作:
页面片段布局中的某处:
<FrameLayout android:layout_ android:layout_ android:visibility="gone" android:id="@+id/fragment_reference">
<View android:layout_ android:layout_ android:visibility="gone"/>
</FrameLayout>
在片段的 onCreateView() 中:
...
View root = inflater.inflate(R.layout.fragment_page, container, false);
ViewGroup ref = (ViewGroup)root.findViewById(R.id.fragment_reference);
ref.setTag(this);
ref.getChildAt(0).setTag("fragment:" + pageIndex);
return root;
以及从 ViewPager 返回 Fragment 的方法(如果存在):
public Fragment getFragment(int pageIndex)
View w = mViewPager.findViewWithTag("fragment:" + pageIndex);
if (w == null) return null;
View r = (View) w.getParent();
return (Fragment) r.getTag();
【讨论】:
【参考方案8】:或者,您可以像这样覆盖 FragmentPagerAdapter
中的 setPrimaryItem
方法:
public void setPrimaryItem(ViewGroup container, int position, Object object)
if (mCurrentFragment != object)
mCurrentFragment = (Fragment) object; //Keep reference to object
((MyInterface)mCurrentFragment).viewDidAppear();//Or call a method on the fragment
super.setPrimaryItem(container, position, object);
public Fragment getCurrentFragment()
return mCurrentFragment;
【讨论】:
【参考方案9】:我想给出我的方法,以防它可以帮助其他人:
这是我的寻呼机适配器:
public class CustomPagerAdapter extends FragmentStatePagerAdapter
private Fragment[] fragments;
public CustomPagerAdapter(FragmentManager fm)
super(fm);
fragments = new Fragment[]
new FragmentA(),
new FragmentB()
;
@Override
public Fragment getItem(int arg0)
return fragments[arg0];
@Override
public int getCount()
return fragments.length;
在我的活动中,我有:
public class MainActivity
private ViewPager view_pager;
private CustomPagerAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
adapter = new CustomPagerAdapter(getSupportFragmentManager());
view_pager = (ViewPager) findViewById(R.id.pager);
view_pager.setAdapter(adapter);
view_pager.setOnPageChangeListener(this);
...
那么我要做的是获取当前片段:
int index = view_pager.getCurrentItem();
Fragment currentFragment = adapter.getItem(index);
【讨论】:
这将在活动/片段生命周期方法之后中断【参考方案10】:这是我的解决方案,因为我不需要跟踪我的标签,并且无论如何都需要刷新它们。
Bundle b = new Bundle();
b.putInt(Constants.SharedPreferenceKeys.NUM_QUERY_DAYS,numQueryDays);
for(android.support.v4.app.Fragment f:getSupportFragmentManager().getFragments())
if(f instanceof HomeTermFragment)
((HomeTermFragment) f).restartLoader(b);
【讨论】:
以上是关于作为 ViewPager 的一部分更新 ListFragment 中的数据的主要内容,如果未能解决你的问题,请参考以下文章
过滤作为 ViewPager 片段一部分的 ListView