Получить фрагмент из 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;
}

}
Другие вопросы по тегам