Я использую ViewPager совместимости с v4 в Android. My FragmentActivity содержит набор данных, которые должны отображаться по-разному на разных страницах моего ViewPager. Пока у меня всего 3 экземпляра одного и того же ListFragment, но в будущем у меня будет 3 экземпляра разных ListFragments. 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;
}
...
Теперь, как вы можете видеть, моей первой попыткой было notifyDataSetChanged для всего FragmentPagerAdapter, и это показало, что иногда данные обновляются, но у других я получил исключение IllegalStateException: не могу выполнить это действие после onSaveInstanceState.
Моя вторая попытка вызвала попытку вызвать функцию обновления в моем ListFragment, но getId в getItem вернул 0. Согласно документам, которые я пробовал
получение ссылки на фрагмент из FragmentManager с помощью findFragmentById () или findFragmentByTag ()
но я не знаю тег или идентификатор моих фрагментов! У меня есть android: id = "@ + id / viewpager" для ViewPager и android: id = "@ android: id / list" для моего ListView в макете ListFragment, но я не думаю, что они полезны.
Итак, как я могу: а) обновить весь ViewPager за один раз (в идеале - вернуть пользователя на страницу, на которой он был раньше) - это нормально, что пользователь видит изменение представления. Или, предпочтительно, b) вызвать функцию в каждом затронутом ListFragment, чтобы обновить ListView вручную.
Любая помощь будет принята с благодарностью!
null
за фрагмент.FragmentPagerAdaper
но не работаетFragmentStatePagerAdaper
(Fragment.getTag()
всегда возвращаетсяnull
.Ответ 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)
источник
FragmentStatePagerAdapter
помощью выше кода сa.instantiateItem(viewPager, viewPager.getCurrentItem());
(чтобы избавиться от 49), предложенный @ mblackwell8 работает отлично.instantiateItem
должны быть окруженыstartUpdate
/finishUpdate
calls. подробности в моем ответе на аналогичный вопрос: stackoverflow.com/questions/14035090/…Хорошо, я думаю, что нашел способ выполнить запрос b) в моем собственном вопросе, поэтому я поделюсь им для пользы других. Тег фрагментов внутри ViewPager находится в форме
"android:switcher:VIEWPAGER_ID:INDEX"
, гдеVIEWPAGER_ID
- этоR.id.viewpager
макет в XML, а INDEX - это позиция в окне просмотра. Итак, если позиция известна (например, 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 } }
Понятия не имею, можно ли это сделать, но так будет до тех пор, пока не будет предложено что-нибудь получше.
источник
makeFragmentName
используемым для генерации тега, который вы могли бы использовать вместо этого для менее хакерского подхода:HomeListFragment fragment = (HomeListFragment) getSupportFragmentManager().findFragmentByTag(FragmentPagerAdapter.makeFragmentName(R.id.viewpager, 0));
makeFragmentName
- этоprivate static
метод, поэтому вы не можете получить к нему доступ.Если вы спросите меня, второе решение на следующей странице, отслеживающее все «активные» страницы фрагментов, лучше: http://tamsler.blogspot.nl/2011/11/android-viewpager-and-fragments-part -ii.html
Ответ от barkside для меня слишком хакерский.
вы отслеживаете все «активные» страницы-фрагменты. В этом случае вы отслеживаете страницы фрагментов в FragmentStatePagerAdapter, который используется ViewPager.
private final SparseArray<Fragment> mPageReferences = new SparseArray<Fragment>(); public Fragment getItem(int index) { Fragment myFragment = MyFragment.newInstance(); mPageReferences.put(index, myFragment); return myFragment; }
Чтобы избежать сохранения ссылки на «неактивные» страницы-фрагменты, нам необходимо реализовать метод destroyItem (...) FragmentStatePagerAdapter:
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); }
"
источник
Хорошо, после тестирования метода @barkside выше я не смог заставить его работать с моим приложением. Затем я вспомнил, что приложение IOSched2012 также использует a
viewpager
, и именно здесь я нашел свое решение. Он не использует идентификаторы фрагментов или теги, поскольку они не хранятсяviewpager
в легкодоступном виде.Вот важные части из приложений HomeActivity для IOSched. Обратите особое внимание на комментарий , в нем и заключается ключ:
@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(); }
источник
getItem()
не вызывается после воссоздания родителя из-за изменения жизненного цикла. См. Stackoverflow.com/a/24662049/236743 . В этом примере это частично защищено для одного фрагмента, но не для двух другихВы можете скопировать
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
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(); } }
источник
Также без проблем работает:
где-то в макете фрагмента страницы:
<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(); }
источник
В качестве альтернативы вы можете переопределить
setPrimaryItem
методFragmentPagerAdapter
так: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; }
источник
Я хочу поделиться своим подходом, если он может помочь кому-то еще:
Это мой адаптер пейджера:
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);
источник
Это мое решение, поскольку мне не нужно отслеживать свои вкладки, и мне все равно нужно обновлять их все.
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); } }
источник