Отображать фрагмент просмотра в фрагменте
У меня есть фрагмент, содержащий ViewPager. ViewPager связан с адаптером, который содержит набор фрагментов.
После загрузки родительского фрагмента меня встречает IllegalStateException
с сообщением: java.lang.IllegalStateException: Recursive entry to executePendingTransactions
.
Некоторые исследования привели меня к выводу, что система не может отображать фрагменты внутри другого фрагмента, ОДНАКО, похоже, есть некоторые признаки того, что можно сделать именно это с помощью ViewPager (Ошибка в ViewPager, используя его с другим фрагментом).
На самом деле, если я добавлю кнопку в родительский фрагмент, который вызывает mPager.setAdapter(mAdapter)
на моем ViewPager при нажатии, ViewPager успешно загружается. Это не идеально.
Затем проблема должна быть связана с жизненным циклом фрагмента. Поэтому мой вопрос заключается в следующем:
Кто-нибудь еще нашел способ обойти эту проблему, и если да, то как?
Есть ли способ отложить настройку адаптера на ViewPager до завершения транзакции фрагмента?
Вот мой код родительского фрагмента:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mView = inflater.inflate(R.layout.team_card_master, container, false);
mViewPager = (ViewPager)mView.findViewById(R.id.team_card_master_view_pager);
final Button button = (Button)mView.findViewById(R.id.load_viewpager_button);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mViewPager.setAdapter(mAdapter);
button.setVisibility(View.GONE);
}
});
mAdapter = new ViewPagerAdapter(getFragmentManager());
// mViewPager.setAdapter(mAdapter);
return mView;
}
И адаптер:
public class ViewPagerAdapter extends FragmentPagerAdapter {
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public int getCount() {
if (mCursor == null) return 0;
else return mCursor.getCount();
}
@Override
public Fragment getItem(int position) {
Bundle b = createBundle(position, mCursor);
return TeamCardFragment.newInstance(b);
}
}
Ответы
Ответ 1
используйте AsyncTask для установки адаптера для ViewPager. Меня устраивает. AsyncTask должен сделать исходный фрагмент завершенным. а затем мы переходим к фрагментам viewPager, в основном, чтобы избежать рекурсии.
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mView = inflater.inflate(R.layout.team_card_master, container, false);
mViewPager = (ViewPager)mView.findViewById(R.id.team_card_master_view_pager);
final Button button = (Button)mView.findViewById(R.id.load_viewpager_button);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mViewPager.setAdapter(mAdapter);
button.setVisibility(View.GONE);
}
});
mAdapter = new ViewPagerAdapter(getFragmentManager());
new setAdapterTask().execute();
return mView;
}
private class setAdapterTask extends AsyncTask<Void,Void,Void>{
protected Void doInBackground(Void... params) {
return null;
}
@Override
protected void onPostExecute(Void result) {
mViewPager.setAdapter(mAdapter);
}
}
Ответ 2
Вы можете использовать FragmentStatePagerAdapter
вместо FragmentPageAdapter
. Это удалит фрагменты для вас.
Ответ 3
Я столкнулся с этой проблемой. У меня был Fragment
, который должен был разместить ViewPager
Fragments
. Когда я уложил еще один Fragment
поверх моего ViewPagerFragment
, а затем ударил, я получил бы IllegalStateException
. После проверки журналов (при укладке нового Fragment
) я обнаружил, что мой ViewPagerFragment
будет использовать методы жизненного цикла для остановки и уничтожения, однако его дочерние элементы Fragments
в ViewPager
будут оставаться в onResume
, Я понял, что дети Fragments
должны управляться FragmentManager
для ViewPagerFragment
, а не FragmentManager
Activity
.
В то время я понял, что ответы, приведенные выше, заключались в том, чтобы обойти ограничения Fragments
неспособности иметь детей Fragments
. Однако теперь, когда в последней библиотеке поддержки есть поддержка Fragment
nesting, вам больше не нужно взломать настройку адаптера для вашего ViewPager
, а передача getChildFragmentManager()
- это все, что вам нужно. Это до сих пор отлично работало для меня.
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mView = inflater.inflate(R.layout.team_card_master, container, false);
mViewPager = (ViewPager) mView.findViewById(R.id.team_card_master_view_pager);
mAdapter = new ViewPagerAdapter(getChildFragmentManager());
mViewPager.setAdapter(mAdapter);
return mView;
}
Ответ 4
Я использовал Handler.post()
для установки адаптера вне транзакции оригинального фрагмента:
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mHandler.post(new Runnable() {
@Override
public void run() {
mViewPager.setAdapter(mAdapter);
}
});
}
Ответ 5
Я столкнулся с этой проблемой при попытке инициализировать адаптер viewPager на вкладке TabIndicator в ViewCreated. Прочитав об этом в сети, я понял, что это может быть только потому, что фрагмент еще не добавлен в эту деятельность. Поэтому я переместил код инициализации в onStart() фрагмента, это должно решить вашу проблему. Но не для меня, я все еще получал такую же проблему снова.
Но это было из-за того, что в моем коде я пытался обходить обычное асинхронное выполнение ожидающей задачи, явно вызывая executePendingTransactions(), после блокировки, что все работает хорошо
Ответ 6
При загрузке родительского фрагмента мне встречается исключение IllegalStateException с сообщением: java.lang.IllegalStateException: рекурсивная запись в executePendingTransactions.
Некоторые исследования привели меня к выводу, что система не может отображать фрагменты внутри другого фрагмента, ОДНАКО, похоже, есть некоторые признаки того, что это можно сделать именно с использованием ViewPager (ошибка в ViewPager, использующая его с другим фрагментом).
явно указать Id для ViewPager.
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mView = inflater.inflate(R.layout.team_card_master, container, false);
mViewPager = (ViewPager)mView.findViewById(R.id.team_card_master_view_pager);
mViewPager.setId(100);
final Button button = (Button)mView.findViewById(R.id.load_viewpager_button);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mViewPager.setAdapter(mAdapter);
button.setVisibility(View.GONE);
}
});
mAdapter = new ViewPagerAdapter(getFragmentManager());
mViewPager.setAdapter(mAdapter);
return mView;
}
Вы можете использовать id в своих xml-ресурсах вместо магического числа 100.
<resources>
<item name="Id_For_ViewPager" type="id"/>
</resources>
и setId (R.Id_For_ViewPager).
Подробнее см. источник FragmentManager.java.
https://github.com/android/platform_frameworks_support/blob/master/v4/java/android/support/v4/app/FragmentManager.java#L900
Или посмотреть эту страницу (японский). Первоначальная идея от нее, на самом деле, не я.
http://y-anz-m.blogspot.jp/2012/04/androidfragment-viewpager-id.html
Ответ 7
Используя этот блестящий пост Томаса Амсслера: http://tamsler.blogspot.nl/2011/11/android-viewpager-and-fragments-part-ii.html
Iv'e создал адаптер, который можно использовать для создания всех типов фрагментов.
Сам адаптер выглядит следующим образом:
public abstract class EmbeddedAdapter extends FragmentStatePagerAdapter {
private SparseArray<WeakReference<Fragment>> mPageReferenceMap = new SparseArray<WeakReference<Fragment>>();
private ArrayList<String> mTabTitles;
public abstract Fragment initFragment(int position);
public EmbeddedAdapter(FragmentManager fm, ArrayList<String> tabTitles) {
super(fm);
mTabTitles = tabTitles;
}
@Override
public Object instantiateItem(ViewGroup viewGroup, int position) {
mPageReferenceMap.put(Integer.valueOf(position), new WeakReference<Fragment>(initFragment(position)));
return super.instantiateItem(viewGroup, position);
}
@Override
public int getCount() {
return mTabTitles.size();
}
@Override
public CharSequence getPageTitle(int position) {
return mTabTitles.get(position);
}
@Override
public Fragment getItem(int pos) {
return getFragment(pos);
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
mPageReferenceMap.remove(Integer.valueOf(position));
}
public Fragment getFragment(int key) {
WeakReference<Fragment> weakReference = mPageReferenceMap.get(key);
if (null != weakReference) {
return (Fragment) weakReference.get();
} else {
return null;
}
}
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
Чтобы использовать адаптер, вам нужно будет сделать следующее:
private void setAdapter() {
if(mAdapter == null) mAdapter = new EmbeddedAdapter(getActivity().getSupportFragmentManager(), mTabTitles) {
@Override
public Fragment initFragment(int position) {
Fragment fragment;
if(position == 0){
// A start fragment perhaps?
fragment = StartFragment.newInstance(mBlocks);
}else{
// Some other fragment
fragment = PageFragment.newInstance(mCategories.get((position)));
}
return fragment;
}
};
// Have to set the adapter in a handler
Handler handler = new Handler();
handler.post(new Runnable() {
@Override
public void run() {
mViewPager.setVisibility(View.VISIBLE);
mPagerTabStrip.setVisibility(View.VISIBLE);
mAdapter.notifyDataSetChanged();
}
});
}
Обратите внимание, что все фрагменты используют:
setRetainInstance(true);
Ответ 8
В соответствии с документацией: https://developer.android.com/reference/android/app/Fragment.html#getChildFragmentManager()
Если вы хотите управлять фрагментом вложенности, вы должны использовать getChildFragmentManager
вместо getFragmentManager
.
Ответ 9
Вместо того, чтобы устанавливать адаптер в AsyncTask или любом обработчике, просто напишите эту часть кода в разделе onResume()
Делая так, вы будете:
- Пусть переход основного фрагмента завершен.
- Показать дочерний фрагмент без использования каких-либо фоновых задач.
Код onResume() в вашем случае выглядит следующим образом:
@Override
public void onResume() {
super.onResume();
if(mViewPager.getAdapter()==null||mViewPager.getAdapter().getCount()==0)
mViewPager.setAdapter(mAdapter);
}
Также в onCreateView вместо
mAdapter = new ViewPagerAdapter(getFragmentManager());
Заменить
mAdapter = new ViewPagerAdapter(getChildFragmentManager());