Я использую ViewPager
вместе с FragmentStatePagerAdapter
для размещения трех разных фрагментов:
- [Fragment1]
- [FRAGMENT2]
- [Fragment3]
Когда я хочу получить Fragment1
из ViewPager
в FragmentActivity
.
В чем проблема и как его исправить?
Я использую ViewPager
вместе с FragmentStatePagerAdapter
для размещения трех разных фрагментов:
Когда я хочу получить Fragment1
из ViewPager
в FragmentActivity
.
В чем проблема и как его исправить?
Основной ответ основывается на имени, генерируемом каркасом. Если это когда-либо изменится, то оно больше не будет работать.
Как насчет этого решения, переопределяя 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);
}
}
Это, похоже, работает для меня, когда вы имеете дело с доступными фрагментами. Фрагменты, которые еще не были созданы, возвращают значение null при вызове getRegisteredFragment
. Но я использовал это в основном для получения текущего Fragment
из ViewPager
: adapater.getRegisteredFragment(viewPager.getCurrentItem())
, и это не вернет null
.
Я не знаю никаких других недостатков этого решения. Если они есть, я хотел бы знать.
Для захвата фрагментов из ViewPager есть много ответов здесь и на других связанных с ним потоках/блогах SO. Все, кого я видел, сломаны, и они, как правило, попадают в один из двух типов, перечисленных ниже. Существуют и другие допустимые решения, если вы хотите только захватить текущий фрагмент, например этот > другой ответ на этот поток.
При использовании FragmentPagerAdapter
см. ниже. Если вы используете FragmentStatePagerAdapter
, стоит посмотреть . Захватывающие индексы, которые не являются текущими в FragmentStateAdapter, не так полезны, как по своей природе, они будут полностью снесены из-за пределов обзора/вне пределов offScreenLimit.
Неверно: сохраняйте свой собственный внутренний список фрагментов, добавленный к тому, когда FragmentPagerAdapter.getItem()
называется
SparseArray
или Map
getItem
вызывается только в первый раз, когда страница прокручивается (или получена, если ваша ViewPager.setOffscreenPageLimit(x)
> 0) в ViewPager
, если хостинг Activity
/Fragment
убит или перезапущен, тогда внутренняя SpaseArray
будет уничтожен, когда пользовательская FragmentPagerActivity будет воссоздана, но за кулисами внутренние фрагменты ViewPagers будут воссозданы, а getItem
будет вызываться NOT для любого из индексов, поэтому возможность получить фрагмент из индекса будет потерян навсегда. Вы можете объяснить это, сохранив и восстановив эти ссылки на фрагменты с помощью FragmentManager.getFragment()
и putFragment
, но это начинает запутываться IMHO. Неверно: создайте собственный идентификатор тега, соответствующий тому, что используется под капотом в FragmentPagerAdapter
, и используйте его для извлечения страницы Фрагменты из FragmentManager
ViewPager
, который может быть изменен в любое время или для любой версии ОС.Метод, воссозданный для этого решения,
private static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
ViewPager.instantiateItem()
Подобный подход к getItem()
выше, но не относящийся к жизненному циклу заключается в том, что он связан с instantiateItem()
вместо getItem()
, поскольку первый будет вызываться каждый раз, когда создается этот индекс/доступ. См. этот ответ
FragmentViewPager
Создайте собственный FragmentViewPager
класс из источника последней версии поддержки и измените метод, используемый для создания тегов фрагментов. Вы можете заменить его ниже. Это имеет то преимущество, что вы знаете, что создание тега никогда не изменится, и вы не полагаетесь на частный 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;
}
Затем, как указывает документ, когда вы хотите захватить фрагмент, используемый для индекса, просто вызовите что-то вроде этого метода (который вы можете поместить в пользовательский FragmentPagerAdapter
или подкласс), который знает результат может быть null, если 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)
!!! EDIT!!!
Я обнаружил, что 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())));
}
Это основано на ответе Стивена выше. Это вернет фактический экземпляр фрагмента, который уже привязан к родительской активности.
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, в которой размещаются ваши фрагменты. ViewPager имеет эти фрагменты как непосредственные дети. Таким образом, вы можете просто перебрать их (используя .getChildCount() и .getChildAt()) и посмотреть, загружен ли экземпляр фрагмента, который вы ищете, в ViewPager и получить ссылку на него. Например. вы можете использовать некоторое статическое уникальное поле идентификатора, чтобы разделить фрагменты.
Обратите внимание, что ViewPager, возможно, не загрузил фрагмент, который вы ищете, поскольку он представляет собой виртуализующий контейнер, например ListView.
FragmentPagerAdapter является factory фрагментов. Чтобы найти фрагмент, основанный на его позиции, если он все еще используется в памяти, используйте это:
public Fragment findFragmentByPosition(int position) {
FragmentPagerAdapter fragmentPagerAdapter = getFragmentPagerAdapter();
return getSupportFragmentManager().findFragmentByTag(
"android:switcher:" + getViewPager().getId() + ":"
+ fragmentPagerAdapter.getItemId(position));
}
Пример кода для поддержки v4 api.
Я обработал его, сначала создав список всех фрагментов (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();
}
но это только мой подход к взлому и слэшу, но он работал у меня, я использую его, делают релевантные изменения для моего отображаемого в данный момент фрагмента при нажатии кнопки "Назад".
Вам не нужно вызывать 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
в ваш класс адаптера 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;
Лучшим решением является использование расширения, которое мы создали в CodePath под названием SmartFragmentStatePagerAdapter. Следуя этому руководству, это значительно облегчает извлечение фрагментов и выделенного в данный момент фрагмента из ViewPager. Он также лучше справляется с управлением памятью фрагментов, встроенных в адаптер.
Эй, я ответил на этот вопрос здесь. В принципе, вам нужно переопределить
public Object instantiateItem (контейнер ViewGroup, позиция int)
метод FragmentStatePagerAdapter.
Самый простой и самый лаконичный путь. Если все ваши фрагменты в ViewPager
имеют разные классы, вы можете получить их и отличить их от следующих:
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 yourFragment = yourviewpageradapter.getItem(int index);
index
- это место фрагмента в адаптере, например, вы добавили fragment1
сначала, так что retreive fragment1
pass index
как 0 и т.д. для отдыха
Создать идентификатор целочисленного ресурса в файле /values/integers.xml
<integer name="page1">1</integer>
<integer name="page2">2</integer>
<integer name="page3">3</integer>
Затем в функции 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;
}
Затем в действии напишите эту функцию, чтобы получить ссылку на фрагмент:
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;
}
Простой способ перебора фрагментов в менеджере фрагментов. Найти 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;
}
В фрагменте
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();
}
}
в TabLayout есть несколько вкладок для фрагмента. вы можете найти фрагмент с помощью тега, используя индекс фрагмента.
Напр. индекс для Fragment1 равен 0, поэтому в findFragmentByTag()
передайте тег для Viewpager.after используя фрагментTransaction, который вы можете добавить, замените фрагмент.
String tag = "android:switcher:" + R.id.viewPager + ":" + 0; Fragment1 f = (Fragment1) getSupportFragmentManager().findFragmentByTag(tag);
Хорошо для адаптера 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;
}
}
Это легкое решение отлично работало для меня.
YourFragment fragment =
(YourFragment)getSupportFragmentManager().getFragments().get(0);
(Если вы знаете позицию своего фрагмента в списке, то это решение будет работать)