Получить фрагмент из ViewPager
Я использую ViewPager
вместе с FragmentStatePagerAdapter
разместить три разных фрагмента:
- [Fragment1]
- [FRAGMENT2]
- [Fragment3]
Когда я хочу получить Fragment1
от ViewPager
в FragmentActivity
,
В чем проблема, и как мне это исправить?
23 ответа
Основной ответ основан на имени, сгенерированном фреймворком. Если это когда-либо изменится, то это больше не будет работать.
Как насчет этого решения, переопределения instantiateItem()
а также destroyItem()
вашей Fragment(State)PagerAdapter
:
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
, Но я использовал это в основном, чтобы получить текущий Fragment
вне ViewPager
: adapater.getRegisteredFragment(viewPager.getCurrentItem())
и это не вернется null
,
Я не знаю никаких других недостатков этого решения. Если таковые имеются, я хотел бы знать.
Для получения фрагментов из ViewPager есть много ответов здесь и в других связанных с этим темах / блогах. Все, кого я видел, сломаны, и, как правило, они попадают в один из двух типов, перечисленных ниже. Существуют и другие допустимые решения, если вы хотите получить только текущий фрагмент, как этот другой ответ в этой теме.
При использовании FragmentPagerAdapter
увидеть ниже. При использовании FragmentStatePagerAdapter
на это стоит посмотреть. Захват индексов, которые не являются текущими в FragmentStateAdapter, не так полезен, так как по своей природе они будут полностью снесены, выйдя за пределы видимости / вне границ offScreenLimit.
Несчастные пути
Неправильно: ведите свой собственный внутренний список фрагментов, добавленный к FragmentPagerAdapter.getItem()
называется
- Обычно используя
SparseArray
или жеMap
- Не один из многих примеров, которые я видел, объясняет события жизненного цикла, поэтому это решение хрупкое. Как
getItem
вызывается только в первый раз, когда страница прокручивается (или получается, если вашаViewPager.setOffscreenPageLimit(x)
> 0) вViewPager
, если хостингActivity
/Fragment
убит или перезапущен тогда внутреннийSpaseArray
будет уничтожен при воссоздании пользовательской FragmentPagerActivity, но за кулисами будут воссозданы внутренние фрагменты ViewPager, иgetItem
НЕ будет вызван ни для одного из индексов, поэтому возможность получить фрагмент из индекса будет потеряна навсегда. Вы можете объяснить это, сохранив и восстановив ссылки на фрагменты с помощьюFragmentManager.getFragment()
а такжеputFragment
но это начинает становиться грязным ИМХО.
Неправильно: Создайте свой собственный идентификатор тега, соответствующий тому, что используется под капотом в FragmentPagerAdapter
и использовать это, чтобы получить фрагменты страницы из FragmentManager
- Это лучше, так как он справляется с проблемой потерянных фрагментов ссылок в первом решении внутреннего массива, но, как справедливо указано в ответах выше и в других местах в сети - он воспринимается как хакерский, как его частный метод, внутренний для
ViewPager
это может измениться в любое время или для любой версии ОС.
Метод, воссозданный для этого решения,
private static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
СЧАСТЛИВЫЙ ПУТЬ: ViewPager.instantiateItem()
Аналогичный подход к getItem()
выше, но без нарушения жизненного цикла, чтобы это, чтобы зацепить instantiateItem()
вместо getItem()
так как первый будет вызываться каждый раз, когда индекс создается / доступен. Смотрите этот ответ
СЧАСТЛИВЫЙ ПУТЬ: Построй свой собственный FragmentViewPager
Построй свой FragmentViewPager
Класс из источника последней поддержки lib и изменить метод, используемый для генерации тегов фрагмента. Вы можете заменить его ниже. Преимущество этого в том, что вы знаете, что создание тега никогда не изменится, и вы не полагаетесь на частный 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
или подкласс), зная, что результат может быть нулевым, если 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
Вы можете иметь своих детейFragments
реализовать интерфейс с помощью метода, какgetUniqueId()
и использовать это, чтобы дифференцировать их. Или вы можете перебрать всеFragments
и проверьте тип класса, такой какif(viewPagerFragment instanceof FragmentClassYouWant)
!!! РЕДАКТИРОВАТЬ!!!
Я обнаружил, что getItem
вызывается только FragmentPagerAdapter
когда каждый Fragment
необходимо создать в первый раз, после этого появляется Fragments
перерабатываются с использованием FragmentManager
, Таким образом, многие реализации FragmentPagerAdapter
создать новый Fragment
в 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 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();
}
но это только мой подход к хаку и слэшу, но он сработал для меня, я использую его, чтобы отменить изменения в моем отображаемом фрагменте, когда нажата кнопка "Назад".
Это основано на ответе Стивена выше. Это вернет фактический экземпляр фрагмента, который уже присоединен к родительскому действию.
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
}
}
}
}
Вам не нужно звонить 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 является фабрикой фрагментов. Чтобы найти фрагмент на основе его положения, если он все еще находится в памяти, используйте это:
public Fragment findFragmentByPosition(int position) {
FragmentPagerAdapter fragmentPagerAdapter = getFragmentPagerAdapter();
return getSupportFragmentManager().findFragmentByTag(
"android:switcher:" + getViewPager().getId() + ":"
+ fragmentPagerAdapter.getItemId(position));
}
Пример кода для поддержки API v4.
Должен расширяться FragmentPagerAdapter
в ваш класс адаптера 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;
Я не мог найти простой, чистый способ сделать это. Однако виджет ViewPager - это просто еще одна ViewGroup, в которой хранятся ваши фрагменты. ViewPager имеет эти фрагменты как непосредственные потомки. Таким образом, вы можете просто перебрать их (используя.getChildCount() и.getChildAt()) и посмотреть, загружен ли искомый экземпляр в данный момент в ViewPager и получить ссылку на него. Например, вы можете использовать какое-то статическое поле уникального идентификатора, чтобы различать фрагменты.
Обратите внимание, что ViewPager, возможно, не загрузил искомый фрагмент, так как это виртуализированный контейнер, такой как ListView.
Эй, я ответил на этот вопрос здесь. В основном вам нужно переопределить
открытый объект instantiateItem(контейнер ViewGroup, позиция int)
метод FragmentStatePagerAdapter.
Лучшее решение - использовать расширение, созданное нами в CodePath, под названием SmartFragmentStatePagerAdapter. Следуя этому руководству, это значительно упрощает извлечение фрагментов и выбранного в данный момент фрагмента из ViewPager. Это также лучше справляется с управлением памятью фрагментов, встроенных в адаптер.
Во фрагменте
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();
}
}
Простой способ перебирать фрагменты в диспетчере фрагментов. Найдите viewpager с аргументом позиции раздела, размещенный в общедоступном статическом 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;
}
В TabLayout есть несколько вкладок для фрагмента. Вы можете найти фрагмент по тегу, используя индекс фрагмента.
Например индекс для Fragment1 равен 0, поэтому в findFragmentByTag()
метод, передайте тег для Viewpager.after с помощью фрагмента транзакции, который вы можете добавить, заменить фрагмент.
String tag = "android:switcher:" + R.id.viewPager + ":" + 0;
Fragment1 f = (Fragment1) getSupportFragmentManager().findFragmentByTag(tag);
Я реализовал это легко с немного другим подходом.
Мой пользовательский метод FragmentAdapter.getItem возвратил не новый MyFragment(), а экземпляр MyFragment, созданный в конструкторе FragmentAdapter.
Затем в своей деятельности я получил фрагмент из адаптера, проверил, является ли он экземпляром необходимого фрагмента, затем произвел приведение и использовал необходимые методы.
Самый простой и краткий способ. Если все ваши фрагменты в ViewPager
принадлежат к разным классам, которые вы можете найти и выделить следующим образом:
public class MyActivity extends Activity
{
@Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
if (fragment.getClass() == MyFragment.class) {
mMyFragment = (MyFragment) fragment;
}
}
}
Создать целочисленный идентификатор ресурса в /values/integers.xml
<integer name="page1">1</integer>
<integer name="page2">2</integer>
<integer name="page3">3</integer>
Затем в функции getItem PagerAdapter:
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;
}
Затем в действии напишите эту функцию, чтобы получить ссылку на фрагмент:
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;
}
Fragment yourFragment = yourviewpageradapter.getItem(int index);
index
это место фрагмента в адаптере, как вы добавили fragment1
сначала так отступить fragment1
проходить index
как 0 и так далее для отдыха
Хорошо для адаптера 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 - чтобы этот метод предоставил вам доступ к вашему фрагменту, вам просто нужно указать ему позицию нужного фрагмента:
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;
}
}