Извлечение fragmentа из ViewPager

Я использую ViewPager вместе с FragmentStatePagerAdapter для размещения трех разных fragmentов:

  • [Fragment1]
  • [FRAGMENT2]
  • [Fragment3]

Когда я хочу получить Fragment1 из ViewPager в FragmentActivity .

В чем проблема, и как ее исправить?

Основной ответ основывается на имени, генерируемом каркасом. Если это когда-либо изменится, то оно больше не будет работать.

Что относительно этого решения, переопределяя instantiateItem() и destroyItem() вашего Fragment(State)PagerAdapter :

 public class MyPagerAdapter extends FragmentStatePagerAdapter { SparseArray registeredFragments = new SparseArray(); 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); } } 

Кажется, это работает для меня, когда вы имеете дело с имеющимися fragmentами. Фрагменты, которые еще не были созданы, возвращают значение null при вызове getRegisteredFragment . Но я использовал это в основном, чтобы получить текущий Fragment из ViewPager : adapater.getRegisteredFragment(viewPager.getCurrentItem()) и это не вернет null .

Я не знаю никаких других недостатков этого решения. Если они есть, я хотел бы знать.

Для захвата fragmentов из ViewPager существует множество ответов здесь и на других связанных с ним streamах / блогах SO. Все, кого я видел, сломаны, и они, как правило, попадают в один из двух типов, перечисленных ниже. Существуют и другие допустимые решения, если вы хотите захватить текущий fragment, как этот другой ответ на этот stream.

При использовании FragmentPagerAdapter см. Ниже. Если вы используете FragmentStatePagerAdapter стоит посмотреть на это . Захватывающие индексы, которые не являются текущими в FragmentStateAdapter, не так полезны, как по своей природе, они будут полностью снесены из-за пределов обзора / вне пределов offScreenLimit.

НЕЗАВИСИМЫЕ ПУТЫ

Неверно: сохраняйте свой собственный внутренний список fragmentов, добавленных при вызове FragmentPagerAdapter.getItem()

  • Обычно используется SparseArray или Map
  • Ни один из многих примеров, которые я видел, не учитывает события жизненного цикла, поэтому это решение является хрупким. Поскольку getItem вызывается только в первый раз, когда страница прокручивается до (или получена, если ваш ViewPager.setOffscreenPageLimit(x) > 0) в ViewPager , если хостинг Activity / Fragment убит или перезагружен, тогда внутренний SpaseArray будет уничтожен, когда пользовательский FragmentPagerActivity воссоздается, но за кулисами внутренние fragmentы ViewPagers будут воссозданы, а getItem НЕ будет вызываться ни для одного из индексов, поэтому возможность получить fragment из индекса будет потеряна навсегда. Вы можете объяснить это, сохранив и восстановив эти ссылки fragmentов через FragmentManager.getFragment() и putFragment но это начинает putFragment IMHO.

Неверно: создайте собственный идентификатор тега, соответствующий тому, что используется под капотом в FragmentPagerAdapter и используйте его для извлечения страницы Фрагменты из FragmentManager

  • Это лучше, поскольку он справляется с проблемой «потеря-fragment-ссылки» в первом решении для внутренних массивов, но, как справедливо указано в ответах выше и в другом месте в сети, он чувствует себя взломанным как частный метод, внутренний для ViewPager который может измениться в любое время или для любой версии ОС.

Метод, который воссоздан для этого решения,

 private static String makeFragmentName(int viewId, long id) { return "android:switcher:" + viewId + ":" + id; } 

HAPPY PATH: ViewPager.instantiateItem()

Подобный подход к getItem() выше, но не связанный с жизненным циклом, заключается в том, чтобы подключиться к instantiateItem() вместо getItem() поскольку первый будет вызываться каждый раз, когда этот индекс создается / доступен. См. Этот ответ

HAPPY PATH: создайте собственный FragmentViewPager

Создайте свой собственный class FragmentViewPager из источника последней библиотеки поддержки и измените метод, используемый внутри, для создания тегов fragmentов. Вы можете заменить его ниже. Это имеет то преимущество, что вы знаете, что создание тега никогда не изменится, и вы не полагаетесь на частный api / method, который всегда опасен.

 /** * @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; } 

Затем, как говорит doc, когда вы хотите захватить fragment, используемый для индекса, просто вызовите что-то вроде этого метода (который вы можете добавить в пользовательский FragmentPagerAdapter или подclass), чтобы знать, что результат может быть пустым, если 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; } 

Это простое решение и решает проблемы в двух других решениях, найденных повсюду в Интернете.

Добавьте следующие методы в свой 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); } } 

Я знаю, что у этого есть несколько ответов, но, возможно, это поможет кому-то. Я использовал относительно простое решение, когда мне нужно было получить Fragment из моего ViewPager . В вашей 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 , вы можете ViewPager у своих детей Fragments интерфейс с помощью метода getUniqueId() и использовать его для их дифференциации. Или вы можете перебирать все Fragments и проверять тип classа, например if(viewPagerFragment instanceof FragmentClassYouWant)

!!! РЕДАКТИРОВАТЬ !!!

Я обнаружил, что getItem только getItem FragmentPagerAdapter когда каждый Fragment должен быть создан в первый раз , после этого появляется, что Fragments перерабатываются с помощью FragmentManager . Таким образом, многие реализации FragmentPagerAdapter создают новый Fragment s в getItem . Используя мой метод выше, это означает, что мы создадим новый Fragment каждый раз, когда getItem вызывается, когда мы просматриваем все элементы в 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; } 

В моем случае ни одно из вышеперечисленных решений не работало.

Однако, поскольку я использую Менеджер fragmentов для детей во Фрагменте, было использовано следующее:

Fragment f = getChildFragmentManager().getFragments().get(viewPager.getCurrentItem());

Это будет работать, только если ваши fragmentы в Менеджере соответствуют элементу viewpager.

Чтобы получить текущий видимый fragment из ViewPager . Я использую это простое заявление, и он работает нормально.

  public Fragment getFragmentFromViewpager() { return ((Fragment) (mAdapter.instantiateItem(mViewPager, mViewPager.getCurrentItem()))); } 

Это основано на ответе Стивена выше. Это вернет фактический экземпляр fragmentа, который уже привязан к родительской активности.

 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, в которой размещаются ваши fragmentы. ViewPager имеет эти fragmentы как непосредственные дети. Таким образом, вы можете просто перебрать их (используя .getChildCount () и .getChildAt ()) и посмотреть, загружен ли экземпляр fragmentа, который вы ищете, в ViewPager и получить ссылку на него. Например, вы можете использовать какое-то статическое уникальное поле идентификатора, чтобы разделить fragmentы.

Обратите внимание, что ViewPager, возможно, не загрузил fragment, который вы ищете, поскольку это виртуализирующий контейнер, например ListView.

FragmentPagerAdapter – это фабрика fragmentов. Чтобы найти fragment, основанный на его позиции, если он все еще используется в памяти, используйте это:

 public Fragment findFragmentByPosition(int position) { FragmentPagerAdapter fragmentPagerAdapter = getFragmentPagerAdapter(); return getSupportFragmentManager().findFragmentByTag( "android:switcher:" + getViewPager().getId() + ":" + fragmentPagerAdapter.getItemId(position)); } 

Пример кода для поддержки v4 api.

Я обработал его, сначала создав список всех fragmentов ( List fragments; ), которые я собирался использовать, а затем добавил их в пейджер, что упростило обработку просматриваемого в данный момент fragmentа.

Так:

 @Override onCreate(){ //initialise the list of fragments fragments = new Vector(); //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е

 Fragment tempFragment = getFragment(); if(tempFragment == MyFragmentNo2.class){ MyFragmentNo2 theFrag = (MyFragmentNo2) tempFragment; //then you can do whatever with the fragment theFrag.costomFunction(); } 

но это только мой подход к взлому и слэшу, но он работал для меня, я использую его, делают релевантные изменения для моего отображаемого в данный момент fragmentа при нажатии кнопки «Назад».

Вам не нужно вызывать getItem() или какой-либо другой метод на более позднем этапе, чтобы получить ссылку на Fragment размещенный внутри ViewPager . Если вы хотите обновить некоторые данные внутри Fragment используйте этот подход: обновить ViewPager динамически?

Ключ должен установить новые данные внутри 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); } 

Должен распространять FragmentPagerAdapter в ваш class адаптера ViewPager.
Если вы используете FragmentStatePagerAdapter вы не сможете найти свой Fragment по его ID

 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; 

Эй, я ответил на этот вопрос здесь . В принципе, вам нужно переопределить

public Object instantiateItem (контейнер ViewGroup, позиция int)

метод FragmentStatePagerAdapter.

Лучшим решением является использование расширения, которое мы создали в CodePath, называемом SmartFragmentStatePagerAdapter . Следуя этому руководству, это значительно облегчает извлечение fragmentов и выделенного в данный момент fragmentа из ViewPager. Он также лучше справляется с управлением памятью fragmentов, встроенных в адаптер.

Самый простой и самый лаконичный путь. Если все ваши fragmentы в ViewPager имеют разные classы, вы можете получить их и отличить их следующим образом:

 public class MyActivity extends Activity { @Override public void onAttachFragment(Fragment fragment) { super.onAttachFragment(fragment); if (fragment.getClass() == MyFragment.class) { mMyFragment = (MyFragment) fragment; } } } 

Я реализовал это легко с помощью немного другого подхода.

Мой пользовательский метод FragmentAdapter.getItem возвратил не новый MyFragment (), а экземпляр MyFragment, созданный в конструкторе FragmentAdapter.

В моей деятельности я получил fragment из адаптера, проверьте, нужен ли ему экземпляр, необходимый fragment, затем применяйте и применяйте необходимые методы.

Создать идентификатор целочисленного ресурса в файле /values/integers.xml

 1 2 3 

Затем в функции 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; } 

Затем в действии напишите эту функцию, чтобы получить ссылку на fragment:

 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у:

 Fragment fragment = getFragmentByPosition(position); if (fragment != null) { FragmentOne fragmentOne = (FragmentOne) fragment; } 

Простой способ перебора fragmentов в менеджере fragmentов. Найти viewpager, имеющий аргумент позиции раздела, помещенный в открытый 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; } 

В fragmentе

 public int getArgument(){ return mPage; { public void update(){ } 

В FragmentActivity

 List 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а. вы можете найти fragment с помощью тега, используя индекс fragmentа.

Напр. индекс для Fragment1 равен 0, поэтому в findFragmentByTag() передайте тег для Viewpager.after используя fragmentTransaction, который вы можете добавить, замените fragment.

String tag = "android:switcher:" + R.id.viewPager + ":" + 0; Fragment1 f = (Fragment1) getSupportFragmentManager().findFragmentByTag(tag);

Ok для адаптера 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); 

и создайте в своем classе методу 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 – это место fragmentа в адаптере, так как вы добавили fragment1 сначала, так что retreive fragment1 передайте index как 0 и так далее для restа

Давайте будем гением компьютера.