作为 ViewPager 的一部分更新 ListFragment 中的数据

我在 Android 中使用的是 v4兼容的 ViewPager。我的片段活动有一大堆数据,这些数据将以不同的方式显示在我的 ViewPager 的不同页面上。到目前为止,我只有3个实例的相同的列表片段,但在未来,我将有3个不同的列表片段的实例。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 中的 update 函数,但 getItem 中的 getId 返回0。根据我查过的文件

从 FragmentManager 获取对片段的引用,使用 FindFragmentById ()或 findFragmentByTag ()

但是我不知道我的碎片的标签和 ID!我有一个 android: id = “@+ id/ViewPager”用于 ViewPager,android: id = “@android: id/list”用于 ListFragment 布局中的 ListView,但我不认为这些是有用的。

所以,我怎么能: A)一次性安全地更新整个 ViewPager (理想情况下将用户返回到他之前所在的页面)-用户看到视图变化是可以的。 最好是, B)在每个受影响的 ListFragment 中调用一个函数手动更新 ListView。

任何帮助都会被感激地接受!

73622 次浏览

好了,我想我已经找到了一种方法来执行请求 b)在我自己的问题,所以我将分享其他人的利益。ViewPager 中的片段标记采用 "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
}
}

我不知道这是否是一种有效的方法,但在有更好的建议之前,它会起作用的。

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)

如果你问我,下面页面上的第二个解决方案,跟踪所有“活动”片段页面,是更好的: 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;
}

为了避免保留对“ inactive”片段页面的引用,我们需要实现 FragmentStatePagerAdapter 的 depyItem (...)方法:

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);
}

"

尝试在每次实例化 Frage 时记录标记。

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);
}
}

可以复制 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();
}




}

好的,在通过上面的@barkside 测试了该方法之后,我无法让它与我的应用程序一起工作。然后我记得 IOSched2012应用程序也使用 viewpager,这就是我找到我的解决方案的地方。它不使用任何片段 ID 或标签,因为这些不是由 viewpager以一种容易访问的方式存储的。

这是来自 IOSached 应用程序 HomeActivity 的 重要的部分。请特别注意 评论,因为它是关键。 :

@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();
}

我想给出我的方法,以防它可以帮助任何人:

这是我的呼机适配器:

 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);

或者,您可以像下面这样从 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;
}

也可以毫无问题地工作:

在页面片段的布局中的某个地方:

<FrameLayout android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" android:id="@+id/fragment_reference">
<View android:layout_width="0dp" android:layout_height="0dp" 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 返回片段(如果存在)的方法:

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();
}

这是我的解决方案,因为我不需要跟踪我的标签,无论如何都需要刷新它们。

    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);
}
}