FragmentPagerAdapter не удаляет элементы (фрагменты) правильно
Я реализовал FragmentPagerAdapter
и с помощью List<Fragment>
для хранения всех фрагментов для моего ViewPager
для отображения. В addItem()
я просто добавляю экземпляр Fragment
, а затем вызываю notifyDataSetChanged()
. Я не уверен, что это необходимо или нет.
Моя проблема просто...
начать с фрагмента 1
[Fragment 1]
добавить новый фрагмент 2
[Fragment 1] [Fragment 2]
удалить фрагмент 2
[Fragment 1]
добавить новый фрагмент 3
[Fragment 1] [Fragment 2]
При добавлении новых фрагментов все кажется отличным. Как только я удаляю фрагмент, а затем добавляю вновь созданный фрагмент, старый фрагмент все еще отображается. Когда я иду a .getClass.getName()
, он дает мне имя Фрагмента 3, но я все еще вижу Фрагмент 2.
Я считаю, что это может быть проблемой с instantiateItem()
или такой, но я думал, что адаптер должен был обработать это для нас. Любые предложения были бы замечательными.
код адаптера...
public class MyPagerAdapter extends FragmentPagerAdapter {
public final ArrayList<Fragment> screens2 = new ArrayList<Fragment>();
private Context context;
public MyPagerAdapter(FragmentManager fm, Context context) {
super(fm);
this.context = context;
}
public void removeType(String name){
for(Fragment f: screens2){
if(f.getClass().getName().contains(name)){ screens2.remove(f); return; }
}
this.notifyDataSetChanged();
}
public boolean addSt(String tag, Class<?> clss, Bundle args){
if(clss==null) return false;
if(!clss.getName().contains("St")) return false;
if(!args.containsKey("cId")) return false;
boolean has = false;
boolean hasAlready = false;
for(Fragment tab: screens2){
if(tab.getClass().getName().contains("St")){
has = true;
if(tab.getArguments().containsKey("cId"))
if(tab.getArguments().getLong("cId") == args.getLong("cId")){
hasAlready = true;
}
if(!hasAlready){
// exists but is different so replace
screens2.remove(tab);
this.addScreen(tag, clss, args, C.PAGE_ST);
// if returned true then it notifies dataset
return true;
}
}
hasAlready = false;
}
if(!has){
// no st yet exist in adapter
this.addScreen(tag, clss, args, C.PAGE_ST);
return true;
}
return false;
}
public boolean removeCCreate(){
this.removeType("Create");
return false;
}
@Override
public int getItemPosition(Object object) {
return POSITION_NONE; //To make notifyDataSetChanged() do something
}
public void addCCreate(){
this.removeCCreate();
Log.w("addding c", " ");
this.addScreen("Create C", CreateCFragment.class, null, C.PAGE_CREATE_C);
}
public void addScreen(String tag, Class<?> clss, Bundle args, int type){
if(clss!=null){
screens2.add(Fragment.instantiate(context, clss.getName(), args));
}
}
@Override
public int getCount() {
return screens2.size();
}
@Override
public Fragment getItem(int position) {
return screens2.get(position);
}
}
Я понимаю, что в коде используется некоторое "гетто" для определения типа фрагмента, но я написал этот код строго для тестирования функциональности. Любая помощь или идеи были бы замечательными, поскольку кажется, что не многие люди отважились в мир FragmentPagerAdapter
s.
Ответы
Ответ 1
У меня такая же проблема, и мое решение перегрузило метод "destroyItem" следующим образом.
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
FragmentManager manager = ((Fragment)object).getFragmentManager();
FragmentTransaction trans = manager.beginTransaction();
trans.remove((Fragment)object);
trans.commit();
}
Это работает для меня, есть ли у кого-нибудь другие решения?
Обновлено:
Я обнаружил, что этот код не был удален, и я добавил условие, чтобы избежать его.
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (position >= getCount()) {
FragmentManager manager = ((Fragment) object).getFragmentManager();
FragmentTransaction trans = manager.beginTransaction();
trans.remove((Fragment) object);
trans.commit();
}
}
Ответ 2
Обновил этот пост и включил мое решение (если кто-то может улучшить, сообщите мне)
Хорошо, теперь я решил свою проблему хакерским способом, но да, это работает;). Если кто-то может улучшить мое решение, пожалуйста, дайте мне знать. Для моего нового решения я теперь использую CustomFragmentStatePagerAdapter, но он не сохраняет состояние, как должно, и сохраняет все фрагменты в списке. Это может вызвать проблему с памятью, если пользователь имеет более 50 фрагментов, например, обычный FragmentPagerAdapter. Было бы здорово, если бы кто-то мог добавить состояние к моему решению, не удаляя мои исправления. Спасибо.
Итак, здесь CustomFragmentStatePagerAdapter.java
package com.tundem.webLab.Adapter;
import java.util.ArrayList;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.PagerAdapter;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
public abstract class CustomFragmentStatePagerAdapter extends PagerAdapter {
private static final String TAG = "FragmentStatePagerAdapter";
private static final boolean DEBUG = false;
private final FragmentManager mFragmentManager;
private FragmentTransaction mCurTransaction = null;
public ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
public ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
private Fragment mCurrentPrimaryItem = null;
public CustomFragmentStatePagerAdapter(FragmentManager fm) {
mFragmentManager = fm;
}
/**
* Return the Fragment associated with a specified position.
*/
public abstract Fragment getItem(int position);
@Override
public void startUpdate(ViewGroup container) {}
@Override
public Object instantiateItem(ViewGroup container, int position) {
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
// taken care of restoring the fragments we previously had instantiated.
// DONE Remove of the add process of the old stuff
/* if (mFragments.size() > position) { Fragment f = mFragments.get(position); if (f != null) { return f; } } */
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
Fragment fragment = getItem(position);
if (DEBUG)
Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
if (mSavedState.size() > position) {
Fragment.SavedState fss = mSavedState.get(position);
if (fss != null) {
try // DONE: Try Catch
{
fragment.setInitialSavedState(fss);
} catch (Exception ex) {
// Schon aktiv (kA was das heißt xD)
}
}
}
while (mFragments.size() <= position) {
mFragments.add(null);
}
fragment.setMenuVisibility(false);
mFragments.set(position, fragment);
mCurTransaction.add(container.getId(), fragment);
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment) object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.remove(fragment);
/*if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object + " v=" + ((Fragment)
* object).getView()); while (mSavedState.size() <= position) { mSavedState.add(null); } mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));
* mFragments.set(position, null); mCurTransaction.remove(fragment); */
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment) object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
}
mCurrentPrimaryItem = fragment;
}
}
@Override
public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
mCurTransaction.commitAllowingStateLoss();
mCurTransaction = null;
mFragmentManager.executePendingTransactions();
}
}
@Override
public boolean isViewFromObject(View view, Object object) {
return ((Fragment) object).getView() == view;
}
@Override
public Parcelable saveState() {
Bundle state = null;
if (mSavedState.size() > 0) {
state = new Bundle();
Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
mSavedState.toArray(fss);
state.putParcelableArray("states", fss);
}
for (int i = 0; i < mFragments.size(); i++) {
Fragment f = mFragments.get(i);
if (f != null) {
if (state == null) {
state = new Bundle();
}
String key = "f" + i;
mFragmentManager.putFragment(state, key, f);
}
}
return state;
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
if (state != null) {
Bundle bundle = (Bundle) state;
bundle.setClassLoader(loader);
Parcelable[] fss = bundle.getParcelableArray("states");
mSavedState.clear();
mFragments.clear();
if (fss != null) {
for (int i = 0; i < fss.length; i++) {
mSavedState.add((Fragment.SavedState) fss[i]);
}
}
Iterable<String> keys = bundle.keySet();
for (String key : keys) {
if (key.startsWith("f")) {
int index = Integer.parseInt(key.substring(1));
Fragment f = mFragmentManager.getFragment(bundle, key);
if (f != null) {
while (mFragments.size() <= index) {
mFragments.add(null);
}
f.setMenuVisibility(false);
mFragments.set(index, f);
} else {
Log.w(TAG, "Bad fragment at key " + key);
}
}
}
}
}
}
Здесь мой обычный FragmentAdapter.java
package com.tundem.webLab.Adapter;
import java.util.LinkedList;
import java.util.List;
import android.support.v4.app.FragmentManager;
import com.tundem.webLab.fragments.BaseFragment;
import com.viewpagerindicator.TitleProvider;
public class FragmentAdapter extends CustomFragmentStatePagerAdapter implements TitleProvider {
public List<BaseFragment> fragments = new LinkedList<BaseFragment>();
private int actPage;
public FragmentAdapter(FragmentManager fm) {
super(fm);
}
public void setActPage(int actPage) {
this.actPage = actPage;
}
public void addItem(BaseFragment fragment) {
// TODO if exists don't open / change to that tab
fragments.add(fragment);
}
public BaseFragment getActFragment() {
return getItem(getActPage());
}
public int getActPage() {
return actPage;
}
@Override
public BaseFragment getItem(int position) {
if (position < getCount()) {
return fragments.get(position);
} else
return null;
}
@Override
public int getCount() {
return fragments.size();
}
@Override
public String getTitle(int position) {
return fragments.get(position).getTitle();
}
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
И так я удаляю фрагмент. (Я знаю это немного больше, чем только .remove()). Быть свободным улучшить свое решение, вы также можете добавить этот код где-нибудь в адаптер, так что да. Это зависит от пользователя, который пытается это реализовать. Я использую это в своем TabHelper.java (класс, который обрабатывает все операции табуляции, такие как delete, add,...)
int act = Cfg.mPager.getCurrentItem();
Cfg.mPager.removeAllViews();
Cfg.mAdapter.mFragments.remove(act);
try {
Cfg.mAdapter.mSavedState.remove(act);
} catch (Exception ex) {/* Already removed */}
try {
Cfg.mAdapter.fragments.remove(act);
} catch (Exception ex) {/* Already removed */}
Cfg.mAdapter.notifyDataSetChanged();
Cfg.mIndicator.notifyDataSetChanged();
Описание Cfg. вещь. Я сохраняю ссылку на эти объекты в классе cfg, поэтому я всегда могу использовать их без необходимости специального Factory.java...
Да. Надеюсь, я смог помочь. Не стесняйтесь улучшать это, но дайте мне знать, чтобы я мог улучшить свой код.
Спасибо.
Если я пропустил какой-либо код, дайте мне знать.
Мой старый ответ также работает, но только если у вас разные фрагменты. FileFragment, WebFragment,... Нет, если вы дважды используете один из этих фрагментов.
Я получил псевдо-работу сейчас. Это действительно грязное решение, и я все еще ищу лучшего. Пожалуйста помоги.
Я изменил код, где я удаляю вкладку:
public static void deleteActTab()
{
//We set this on the indicator, NOT the pager
int act = Cfg.mPager.getCurrentItem();
Cfg.mAdapter.removeItem(act);
List<BaseFragment> frags = new LinkedList<BaseFragment>();
frags = Cfg.mAdapter.fragments;
Cfg.mPager = (ViewPager)Cfg.act.findViewById(R.id.pager);
Cfg.mPager.setAdapter(Cfg.mAdapter);
Cfg.mIndicator.setViewPager(Cfg.mPager);
Cfg.mAdapter.fragments = frags;
if(act > 0)
{
Cfg.mPager.setCurrentItem(act-1);
Cfg.mIndicator.setCurrentItem(act-1);
}
Cfg.mIndicator.notifyDataSetChanged();
}
Если кто-то может улучшить этот код, дайте мне знать. Если кто-то может сказать нам реальный ответ на эту проблему. пожалуйста, добавьте его здесь. Многие люди сталкиваются с этой проблемой. Я добавил репутацию 50 для тех, кто ее решает. Я также могу дать пожертвование тому, кто его решает.
Спасибо
Ответ 3
Взяв "лучшее из обоих миров" (я имею в виду ответы @Tericky Shih и @mikepenz), мы делаем это коротко и просто:
public class MyPagerAdapter extends FragmentPagerAdapter {
public ArrayList<Fragment> fragments = new ArrayList<Fragment>();
...
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
if (position >= getCount()) fm.beginTransaction().remove((Fragment) object).commit();
}
@Override
public int getItemPosition(Object object) {
if (fragments.contains(object)) return fragments.indexOf(object);
else return POSITION_NONE;
}
}
Основное отличие состоит в том, что если какой-либо фрагмент не изменяется, вам не нужно уничтожать его представление и не нужно возвращать POSITION_NONE
для него. В то же время я столкнулся с ситуацией, когда ViewPager держал ссылку на элемент, который был уже уничтожен, поэтому проверка if (fragments.contains(object))
помогает определить, нужен ли этот элемент больше.
Ответ 4
Возможно, это поможет answer.
Используйте FragmentStatePagerAdapter вместо FragmentPagerAdapter.
Потому что FragmentPagerAdapter не разрушает представления. Для получения дополнительной информации прочтите answer.
Ответ 5
У меня была ситуация, похожая на вашу. Недавно мне нужно было добавить и удалить фрагменты из ViewPager. В первом режиме у меня есть Фрагменты 0, 1 и 2, а во втором режиме у меня есть Фрагменты 0 и 3. Я хочу, чтобы Фрагмент 0 был одинаковым для обоих режимов и сохранял информацию.
Все, что мне нужно было сделать, это переопределить FragmentPagerAdapter.getItemId, чтобы убедиться, что я вернул уникальный номер для каждого другого фрагмента - по умолчанию возвращается "позиция". Мне также пришлось снова установить адаптер в ViewPager - новый экземпляр будет работать, но я верну его обратно к тому же экземпляру. Настройка адаптера приводит к тому, что ViewPager удаляет все представления и пытается их добавить снова.
Однако, трюк заключается в том, что адаптер только вызывает getItem, когда он хочет создать экземпляр фрагмента - не каждый раз, когда он показывает это. Это происходит потому, что они кэшируются и просматривают их по "позиции", возвращенной getItemId.
Представьте, что у вас есть три фрагмента (0, 1 и 2), и вы хотите удалить "1".
Если вы вернете "позицию" для getItemId, то удаление фрагмента 1 не будет работать, потому что когда вы попытаетесь показать фрагмент 2 после удаления фрагмента 1, пейджер/адаптер подумает, что он уже получил фрагмент для этой "позиции" и продолжит отображение фрагмента 1.
FYI: я попробовал notifyDataSetChanged вместо установки адаптера, но это не сработало для меня.
Сначала, пример getItemId переопределить и что я сделал для моего getItem:
public class SectionsPagerAdapter extends FragmentPagerAdapter
{
...
@Override
public long getItemId(int position)
{
// Mode 1 uses Fragments 0, 1 and 2. Mode 2 uses Fragments 0 and 3
if ( mode == 2 && position == 1 )
return 3;
return position;
}
@Override
public Fragment getItem(int position)
{
if ( mode == 1 )
{
switch (position)
{
case 0:
return <<fragment 0>>;
case 1:
return <<fragment 1>>;
case 2:
return <<fragment 2>>;
}
}
else // Mode 2
{
switch (position)
{
case 0:
return <<fragment 0>>;
case 1:
return <<fragment 3>>;
}
}
return null;
}
}
Теперь изменение режима:
private void modeChanged(int newMode)
{
if ( newMode == mode )
return;
mode = newMode;
// Calling mSectionsPagerAdapter.notifyDataSetChanged() is not enough here
mViewPager.setAdapter(mSectionsPagerAdapter);
}
Ответ 6
Не получилось. Моему решению был добавлен FragmentStatePagerAdapter.java в мой проект, переименованный в FragmentStatePagerAdapter2.java.
В destroyItem() я немного изменился на основе журналов ошибок. Из
// mFragments.set(position, null);
to
if (position < mFragments.size())mFragments.remove(position);
Возможно, у вас нет такой же проблемы, просто проверьте журнал. Упование это помогает кому-то!
Ответ 7
После многих попыток я заработал, чтобы он правильно удалял или прикреплял третий фрагмент в конечной позиции.
Object fragments[] = new Object[3];
int mItems = 2;
MyAdapter mAdapter;
ViewPager mPager;
public void addFragment(boolean bool) {
mAdapter.startUpdate(mPager);
if (!bool) {
mAdapter.destroyItem(mPager, 2, fragments[2]);
mItems = 2;
fNach = false;
}
else if (bool && !fNach){
mItems = 3;
mAdapter.instantiateItem(mPager,2);
fNach = true;
}
mAdapter.finishUpdate(mPager);
mAdapter.notifyDataSetChanged();
}
public class MyAdapter extends FragmentPagerAdapter {
MyAdapter(FragmentManager fm) {
super(fm);
}
@Override
public int getCount() {
return mItems;
}
@Override
public CharSequence getPageTitle(int position) {
... (code for the PagerTitleStrip)
}
@Override
public Fragment getItem(int position) {
Fragment f = null;
switch (position) {
case 0:
f = new Fragment1();
break;
case 1:
f = new Fragment2();
break;
case 2:
f = new Fragment3();
break;
}
return f;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Object o = super.instantiateItem(container,position);
fragments[position] = o;
return o;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
System.out.println("Destroy item " + position);
if (position >= getCount()) {
FragmentManager manager = ((Fragment) object).getFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
ft.remove((Fragment) object);
ft.commit();
}
}
}
Некоторое уточнение: чтобы получить ссылку на объект для вызова destroyItem, я сохранил объекты, возвращенные из объекта instantiateItem в массиве. Когда вы добавляете или удаляете фрагменты, вы должны объявить об этом с помощью startUpdate, finishUpdate и notifyDataSetChanged. Количество элементов должно быть изменено вручную, для добавления вы увеличиваете его и создаете экземпляр, тогда getItem создает его. Для удаления вы вызываете destroyItem, и в этом коде важно положение >= mItems, потому что destroyItem также вызывается, если фрагмент выходит из кеша. Вы не хотите его удалять. Единственное, что не работает, - это анимация прокрутки. После удаления последней страницы анимация "не может прокрутить влево" на новой последней странице не восстановлена правильно. Если вы проведете его, появится пустая страница, и она отскакивает назад.
Ответ 8
Реальная проблема заключается в том, что FragmentPagerAdapter использует позицию фрагмента в вашем списке как идентификатор. Поэтому, если вы добавляете новый список или просто удаляете элементы, элемент "instantiateItem" найдет разные фрагменты для новых элементов в списке...
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
и
private static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
и
* Return a unique identifier for the item at the given position.
* <p>
* <p>The default implementation returns the given position.
* Subclasses should override this method if the positions of items can change.</p>
*
* @param position Position within this adapter
* @return Unique identifier for the item at position
*/
public long getItemId(int position) {
return position;
}
Ответ 9
У меня была такая же проблема, пока я не понял, я создавал свой PagerView из другого фрагмента, а не основной деятельности.
Мое решение состояло в том, чтобы передать ChildFragment Manager в конструктор PagerAdapter Fragment (State), а не в Fragment Manager родительского Fragment.
Используя ChildFragmentManager, все фрагменты, созданные ViewPagerAdapter, очищаются автоматически при уничтожении родительского фрагмента.