it-swarm.com.ru

Получить фрагмент из ViewPager

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

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

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

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

227
Qun Qin

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

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

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

492
Streets Of Boston

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

При использовании FragmentPagerAdapter см. Ниже. При использовании FragmentStatePagerAdapter стоит посмотреть на this . Получение индексов, которые не являются текущими в FragmentStateAdapter, не так полезно, как по природе если они будут полностью снесены, вышли из поля зрения/вне границ.

СЧАСТЛИВЫЕ ПУТИ

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

  • Обычно используется SparseArray или Map
  • Не один из многих примеров, которые я видел, описывает события жизненного цикла, поэтому это решение хрупкое. Поскольку getItem вызывается только в первый раз, когда страница прокручивается (или получается, если ваша ViewPager.setOffscreenPageLimit(x)> 0) в ViewPager, если хост ActivityFragment БУДЕТ УБИТ ИЛИ ПЕРЕЗАПУЩЕН, ТО ВНУТРЕННЯЯ SpaseArray БУДЕТ СТЕРТА ПРИ ПОВТОРНОМ СОЗДАНИИ ПОЛЬЗОВАТЕЛЬСКОЙ FRAGMENTPAGERACTIVITY. , НО ЗА КУЛИСАМИ ВНУТРЕННИЕ ФРАГМЕНТЫ VIEWPAGER БУДУТ ВОССОЗДАНЫ, И getItem БУДЕТ _/_ НЕ вызываться для любого из индексов, поэтому возможность получить фрагмент из индекса будет потеряна навсегда. Вы можете объяснить это, сохранив и восстановив ссылки на эти фрагменты с помощью FragmentManager.getFragment() и putFragment, но это начинает становиться беспорядочным IMHO.

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

  • Это лучше, так как он справляется с проблемой потерянных фрагментов-ссылок в первом решении внутреннего массива, но, как справедливо указано в ответах выше и в других местах в сети, - он чувствует себя хакером как метод private внутренний к 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;
}

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

83
Dori

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

70
kzotin

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

    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);
        }
    }
38
Mindphaser

Я знаю, что у этого есть несколько ответов, но, возможно, это поможет кому-то. Я использовал относительно простое решение, когда мне нужно было получить 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 Fragments в 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;
}
21
Steven Byle

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

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

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

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

10
Veedka

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

      public Fragment getFragmentFromViewpager()  
      {
         return ((Fragment) (mAdapter.instantiateItem(mViewPager, mViewPager.getCurrentItem())));
      }
6
Xar E Ahmer

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

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
                }
            }
        }
    }
3
OrangeTree

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

открытый объект instantiateItem (контейнер ViewGroup, позиция int)

метод FragmentStatePagerAdapter.

2
Utkarsh Saxena

Вы не должны вызывать 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
M-WaJeEh

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

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

2
Russell Cargill

Я не мог найти простой, чистый способ сделать это. Однако виджет ViewPager - это просто еще одна ViewGroup, в которой хранятся ваши фрагменты. ViewPager имеет эти фрагменты как непосредственные потомки. Таким образом, вы можете просто перебрать их (используя .getChildCount () и .getChildAt ()) и посмотреть, загружен ли в данный момент искомый экземпляр фрагмента в ViewPager и получить ссылку на него. Например. Вы можете использовать какое-то статическое поле уникального идентификатора, чтобы различать фрагменты.

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

2
Zsombor Erdődy-Nagy

Необходимо расширить 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;
2
i_m_mahii

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

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

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

2
Daniel De León

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

1
Nathan

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

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

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

0
lxknvlk

в TabLayout есть несколько вкладок для фрагмента. Вы можете найти фрагмент по тегу, используя индекс фрагмента.

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

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

0
SUMIT MONAPARA

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

public class MyActivity extends Activity
{

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

}
0
riwnodennyk

Во фрагменте

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();
    }
}
0
Mark Zhuravlyov

Создать идентификатор целочисленного ресурса в /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;
                    }
0
user2574090

Простой способ перебирать фрагменты в диспетчере фрагментов. Найти 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;
}
0
Evgeny