我已实施FragmentPagerAdapter
并使用List<Fragment>
来保存我ViewPager
的所有片段。在addItem()
我只需添加一个实例化的Fragment
,然后调用notifyDataSetChanged()
。我不确定这是否有必要。
我的问题只是...... 从片段1开始
[Fragment 1]
添加新片段2
[Fragment 1] [Fragment 2]
删除片段2
[Fragment 1]
添加新的片段3
[Fragment 1] [Fragment 2]
添加新片段时,一切看起来都很棒。删除片段然后添加新实例化的片段后,仍会显示旧片段。当我去.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世界。
答案 0 :(得分:34)
我遇到了同样的问题,我的解决方案是将“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();
}
}
答案 1 :(得分:6)
更新了这篇文章并包含了我的解决方案(如果有人可以改进,请告诉我)
好的,我现在以一种黑客的方式解决了我的问题,但是,它正在发挥作用;)。如果有人可以改进我的解决方案,请告诉我。对于我的新解决方案,我现在使用CustomFragmentStatePagerAdapter,但它不会保存状态,并将所有片段存储在列表中。如果用户有超过50个片段,这可能会导致内存问题,就像普通的FragmentPagerAdapter一样。如果有人可以在不删除我的修复程序的情况下将State-thing添加回我的解决方案,那就太棒了。感谢。
所以这是我的 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,class中保存了对这些对象的引用,所以我总是可以使用它们,而不需要特殊的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的声誉。我也可以为解决它的人捐款。
由于
答案 2 :(得分: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))
有助于确定是否不再需要该项目。
答案 3 :(得分:3)
也许这个answer会帮助你。
使用 FragmentStatePagerAdapter 代替 FragmentPagerAdapter 。
因为FragmentPagerAdapter不会破坏视图。有关详细信息,请阅读此answer。
答案 4 :(得分:1)
我遇到了与你相似的情况。我最近需要在ViewPager中添加和删除Fragments。在第一种模式中,我有片段0,1和2,在第二种模式中,我有片段0和3.我希望片段0对于两种模式都相同并保留信息。
我需要做的就是覆盖FragmentPagerAdapter.getItemId以确保我为每个不同的片段返回一个唯一的数字 - 默认是返回&#34; position&#34;。我还必须再次在ViewPager中设置适配器 - 一个新实例可以工作,但我将它设置回同一个实例。设置适配器会导致ViewPager删除所有视图,尝试再次添加它们。
然而,诀窍是适配器只在想要实例化片段时调用getItem - 而不是每次显示它时。这是因为它们被缓存并通过&#34;位置&#34;来查找它们。由getItemId返回。
想象一下,你有三个碎片(0,1和2),你想要删除&#34; 1&#34;。 如果你返回&#34;位置&#34;对于getItemId然后删除片段1将无法工作,因为当您尝试在删除片段1后显示片段2时,寻呼机/适配器会认为它已经获得了片段#34;位置&#34;并将继续显示片段1。
仅供参考:我尝试过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);
}
答案 5 :(得分:0)
没有为我工作。我的解决方案是将FragmentStatePagerAdapter.java放入我的项目中,重命名为FragmentStatePagerAdapter2.java。 在destroyItem()中,我根据错误日志稍微改了一下。来自
// mFragments.set(position, null);
到
if (position < mFragments.size())mFragments.remove(position);
也许你没有同样的问题,只需检查一下日志。希望这对某人有帮助!
答案 6 :(得分:0)
经过大量的尝试,我得到它的工作,以便它正确地删除或附加在结束位置的第三个片段。
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返回的对象存储在一个Array中。添加或删除片段时,必须使用startUpdate,finishUpdate和notifyDataSetChanged进行声明。必须手动更改项目数,为了增加它并实例化它,getItem然后创建它。对于删除,您调用destroyItem,并且在此代码中,位置&gt; = mItems是必要的,因为如果片段超出缓存,也会调用destroyItem。您不想删除它。唯一不起作用的是滑动动画。删除最后一页后,新的最后一页无法正确恢复“无法向左滑动”动画。如果您滑动它,会显示一个空白页面,然后它会反弹。
答案 7 :(得分:0)
真正的问题是FragmentPagerAdapter使用列表中片段的位置作为ID。因此,如果您添加新的List或只是删除项“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;
}
答案 8 :(得分:0)
我一直遇到同样的问题,直到我突然意识到,我是在另一个Fragment而不是主要活动中创建PagerView的。
我的解决方案是将ChildFragment Manager传递给Fragment(State)PagerAdapter的构造函数,而不是父Fragment的Fragment Manager。
使用ChildFragmentManager销毁父Fragment时,将自动清除ViewPagerAdapter创建的所有Fragment。