Получить фрагмент из ViewPager - PullRequest
236 голосов
/ 09 января 2012

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

  • [Fragment1]
  • [Fragment2]
  • [Фрагмент3]

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

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

Ответы [ 23 ]

497 голосов
/ 07 марта 2013

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

Как насчет этого решения, переопределяя 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.

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

85 голосов
/ 09 июля 2014

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

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

THEUNHAPPY PATHS

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

  • Обычно используется SparseArrayили Map
  • Не один из многих примеров, которые я видел, описывает события жизненного цикла, поэтому это решение хрупкое.Поскольку getItem вызывается только в первый раз, когда страница прокручивается (или получается, если ваш ViewPager.setOffscreenPageLimit(x)> 0) в ViewPager, если хост Activity / Fragment убит или перезапущен, тогда внутренний SpaseArray будет уничтожено при воссоздании пользовательской FragmentPagerActivity, но за кулисами внутренние фрагменты ViewPager будут воссозданы, и getItem будет NOT вызываться для любого из индексов, поэтому возможность получитьфрагмент из индекса будет потерян навсегда.Вы можете объяснить это, сохранив и восстановив эти фрагменты ссылок через 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;
}

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

70 голосов
/ 15 февраля 2012

Добавьте следующие методы к вашему 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. Если что-то не получится, вы увидите хороший журнал сбоев.

38 голосов
/ 14 января 2014

Еще одно простое решение:

    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);
        }
    }
21 голосов
/ 26 января 2013

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

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

Однако, поскольку я использую дочерний менеджер фрагментов во фрагменте, использовалось следующее:

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

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

6 голосов
/ 01 марта 2017

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

      public Fragment getFragmentFromViewpager()  
      {
         return ((Fragment) (mAdapter.instantiateItem(mViewPager, mViewPager.getCurrentItem())));
      }
3 голосов
/ 16 декабря 2015

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

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
                }
            }
        }
    }
2 голосов
/ 20 ноября 2013

Вы не должны вызывать 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);
}
2 голосов
/ 21 августа 2013

Я обработал это, сначала составив список всех фрагментов (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();
}

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

...