支持FragmentPagerAdapter保留对旧片段的引用


109

最新信息:

我将问题缩小为fragmentManager保留旧片段实例的问题,并且viewpager与FragmentManager不同步。请参阅此问题... http://code.google.com/p/android/issues/detail?id=19211#makechanges。我仍然不知道如何解决这个问题。有什么建议...

我已经尝试调试了很长时间,任何帮助将不胜感激。我正在使用FragmentPagerAdapter,它接受像这样的片段列表:

List<Fragment> fragments = new Vector<Fragment>();
fragments.add(Fragment.instantiate(this, Fragment1.class.getName())); 
...
new PagerAdapter(getSupportFragmentManager(), fragments);

实施是标准的。我正在为片段使用ActionBarSherlock和v4可计算性库。

我的问题是,离开应用程序并打开其他几个应用程序并返回后,这些片段会丢失对FragmentActivity的引用(即getActivity() == null)。我不知道为什么会这样。我试图手动设置,setRetainInstance(true);但这无济于事。我认为这是在我的FragmentActivity被销毁时发生的,但是如果在收到日志消息之前打开应用程序,这种情况仍然会发生。有什么想法吗?

@Override
protected void onDestroy(){
    Log.w(TAG, "DESTROYDESTROYDESTROYDESTROYDESTROYDESTROYDESTROY");
    super.onDestroy();
}

适配器:

public class PagerAdapter extends FragmentPagerAdapter {
    private List<Fragment> fragments;

    public PagerAdapter(FragmentManager fm, List<Fragment> fragments) {
        super(fm);

        this.fragments = fragments;

    }

    @Override
    public Fragment getItem(int position) {

        return this.fragments.get(position);

    }

    @Override
    public int getCount() {

        return this.fragments.size();

    }

}

我的片段之一被剥夺了,但是我很高兴地证明了一切都被剥夺了,但仍然无法正常工作...

public class MyFragment extends Fragment implements MyFragmentInterface, OnScrollListener {
...

@Override
public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    handler = new Handler();    
    setHasOptionsMenu(true);
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    Log.w(TAG,"ATTACHATTACHATTACHATTACHATTACH");
    context = activity;
    if(context== null){
        Log.e("IS NULL", "NULLNULLNULLNULLNULLNULLNULLNULLNULLNULLNULL");
    }else{
        Log.d("IS NOT NULL", "NOTNOTNOTNOTNOTNOTNOTNOT");
    }

}

@Override
public void onActivityCreated(Bundle savedState) {
    super.onActivityCreated(savedState);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.my_fragment,container, false);

    return v;
}


@Override
public void onResume(){
    super.onResume();
}

private void callService(){
    // do not call another service is already running
    if(startLoad || !canSet) return;
    // set flag
    startLoad = true;
    canSet = false;
    // show the bottom spinner
    addFooter();
    Intent intent = new Intent(context, MyService.class);
    intent.putExtra(MyService.STATUS_RECEIVER, resultReceiver);
    context.startService(intent);
}

private ResultReceiver resultReceiver = new ResultReceiver(null) {
    @Override
    protected void onReceiveResult(int resultCode, final Bundle resultData) {
        boolean isSet = false;
        if(resultData!=null)
        if(resultData.containsKey(MyService.STATUS_FINISHED_GET)){
            if(resultData.getBoolean(MyService.STATUS_FINISHED_GET)){
                removeFooter();
                startLoad = false;
                isSet = true;
            }
        }

        switch(resultCode){
        case MyService.STATUS_FINISHED: 
            stopSpinning();
            break;
        case SyncService.STATUS_RUNNING:
            break;
        case SyncService.STATUS_ERROR:
            break;
        }
    }
};

public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    menu.clear();
    inflater.inflate(R.menu.activity, menu);
}

@Override
public void onPause(){
    super.onPause();
}

public void onScroll(AbsListView arg0, int firstVisible, int visibleCount, int totalCount) {
    boolean loadMore = /* maybe add a padding */
        firstVisible + visibleCount >= totalCount;

    boolean away = firstVisible+ visibleCount <= totalCount - visibleCount;

    if(away){
        // startLoad can now be set again
        canSet = true;
    }

    if(loadMore) 

}

public void onScrollStateChanged(AbsListView arg0, int state) {
    switch(state){
    case OnScrollListener.SCROLL_STATE_FLING: 
        adapter.setLoad(false); 
        lastState = OnScrollListener.SCROLL_STATE_FLING;
        break;
    case OnScrollListener.SCROLL_STATE_IDLE: 
        adapter.setLoad(true);
        if(lastState == SCROLL_STATE_FLING){
            // load the images on screen
        }

        lastState = OnScrollListener.SCROLL_STATE_IDLE;
        break;
    case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
        adapter.setLoad(true);
        if(lastState == SCROLL_STATE_FLING){
            // load the images on screen
        }

        lastState = OnScrollListener.SCROLL_STATE_TOUCH_SCROLL;
        break;
    }
}

@Override
public void onDetach(){
    super.onDetach();
    if(this.adapter!=null)
        this.adapter.clearContext();

    Log.w(TAG, "DETACHEDDETACHEDDETACHEDDETACHEDDETACHEDDETACHED");
}

public void update(final int id, String name) {
    if(name!=null){
        getActivity().getSupportActionBar().setTitle(name);
    }

}

}

当用户与其他片段进行交互并且getActivity返回null时,将调用update方法。这是另一个片段正在调用的方法...

((MyFragment) pagerAdapter.getItem(1)).update(id, name);

我相信,当应用程序被销毁后又被创建,而不是仅仅将应用程序启动到默认片段,应用程序就会启动,然后viewpager导航到最后一个已知页面。这似乎很奇怪,应用程序不应该加载到默认片段吗?


27
Android的Fragment很烂!
Hamidreza Sadegh

13
上帝,我爱您的日志消息:D
oli.G,2015年

Answers:


120

您遇到了一个问题,因为您正在实例化并保留对片段的引用,而不是在片段之外 PagerAdapter.getItem,并且试图独立于ViewPager使用这些引用。正如Seraph所说,您确实可以保证在特定时间在ViewPager中实例化/添加了一个片段-这应该被视为实现细节。ViewPager会延迟加载其页面。默认情况下,它仅加载当前页面,而左侧和右侧分别加载该页面。

如果将应用程序置于后台,则会自动保存已添加到片段管理器中的片段。即使您的应用程序被杀死,您重新启动应用程序时也会恢复此信息。

现在考虑您已经查看了片段A,B和C的几个页面。您知道这些页面已添加到片段管理器中。因为你正在使用FragmentPagerAdapter而不是FragmentStatePagerAdapter,所以当您滚动到其他页面时,这些片段仍会添加(但可能是分离的)。

考虑一下,然后将应用程序后台化,然后将其终止。当您回来时,Android会记住您曾经在片段管理器中包含片段A,片段B和片段C,因此它会为您重新创建它们,然后添加它们。但是,现在添加到片段管理器的那些不是您在“活动”的片段列表中拥有的那些。

getPosition如果已经为该特定页面位置添加了片段,则FragmentPagerAdapter将不会尝试调用。实际上,由于永远不会删除由Android重新创建的片段,因此您不希望将其替换为getPosition。获取它的句柄也很难获得对其的引用,因为它是添加了您不知道的标签的。这是设计使然;不鼓励您将视图分页器正在管理的片段弄乱。您应该在一个片段中执行所有操作,与活动进行通信,并在必要时请求切换到特定页面。

现在,回到缺少活动的问题。pagerAdapter.getItem(1)).update(id, name)在所有这些都发生之后调用将返回您列表中的片段,该片段尚未添加到片段管理器中,因此它将没有活动引用。我建议您的更新方法应该修改某些共享的数据结构(可能由活动管理),然后当您移至特定页面时,它可以基于此更新的数据进行绘制。


1
我喜欢您的解决方案,因为它非常优雅,并且可能会重构我的代码,但是正如您所说的,我想将100%的逻辑和数据保留在片段中。您的解决方案几乎需要将所有数据保留在FragmentActivity中,然后仅使用每个Fragment来处理显示逻辑。如我所说,我更喜欢这样做,但是片段之间的交互非常繁琐,管理起来可能会很烦。无论如何,谢谢您的详细解释。你比我解释得更好。
莫里西2012年

28
简短说明:永远不要在适配器之外引用片段
passsy 2012年

2
那么,如果Activity实例未实例化FragmentPagerAdapter,那么该实例如何访问FragmentPagerAdapter实例?将来的实例不会只是重新实例化FragmentPagerAdapter及其所有片段实例吗?FragmentPagerAdapter是否应该实现所有片段接口来管理片段之间的通信?
Eric H.

声明“获取它的句柄也很困难,因为它是在其中添加了您不知道的标签的。这是设计使然;不鼓励您弄乱视图分页器正在管理的片段”。是错误的。通过调用获取参考非常容易,instantiateItem实际上您应该onCreate活动中进行参考。在此处查看详细信息:stackoverflow.com/questions/14035090/…–
morgwai

通常,在不进行加载的情况下自行实例化片段是不明智的,因为在“破坏并重新创建”的情况下,您可能最终会为实际重新创建并显示的片段处理一个单独的片段
hmac

108

我找到了对我有用的简单解决方案。

使您的片段适配器扩展FragmentStatePagerAdapter而不是FragmentPagerAdapter并重写onSave方法以返回null

@Override
public Parcelable saveState()
{
    return null;
}

这可以防止android重新创建片段


一天后,我找到了另一种更好的解决方案。

调用setRetainInstance(true)所有片段并将对它们的引用保存在某个地方。我在活动中的静态变量中执行了此操作,因为它被声明为singleTask,并且片段可以一直保持不变。

这样,Android不会重新创建片段,而是使用相同的实例。


4
谢谢,谢谢,非常感谢,我真的没有话要谢谢Mik,我从最近10天开始追逐此问题,并尝试了许多方法。但这四条神奇的线挽救了我的性命:)

7
静态引用片段和/或活动是一件非常冒险的事情,因为它很容易导致内存泄漏。当然,如果您小心一点,可以通过在不再需要时将它们设置为null来轻松处理它。
Android开发人员

这对我有用。在应用程序崩溃并重新启动后,片段及其各自的视图将保留其链接。谢谢!
swebal

我使用setRetainInstance(true)FragmentPagerAdapter。一切正常。但是,当我旋转设备时,适配器仍然具有碎片,但未显示碎片。片段的生命周期方法也不被调用。有人可以帮忙吗?
乔纳斯(Jonas)

1
你救了我的一天!至今已搜寻此错误3天。我有一个Viewpager,它在SingleTask活动中具有2个片段,并且启用了“不保留活动”标志。非常感谢!!!!
矩阵

29

我通过直接通过FragmentManager而不是像这样通过FragmentPagerAdapter访问片段来解决了这个问题。首先,我需要找出FragmentPagerAdapter自动生成的片段的标签...

private String getFragmentTag(int pos){
    return "android:switcher:"+R.id.viewpager+":"+pos;
}

然后,我只是获得对该片段的引用,并按需要执行我的操作...

Fragment f = this.getSupportFragmentManager().findFragmentByTag(getFragmentTag(1));
((MyFragmentInterface) f).update(id, name);
viewPager.setCurrentItem(1, true);

在片段内部,我设置了,setRetainInstance(false);以便可以将值手动添加到savedInstanceState包中。

@Override
public void onSaveInstanceState(Bundle outState) {
    if(this.my !=null)
        outState.putInt("myId", this.my.getId());

    super.onSaveInstanceState(outState);
}

然后在OnCreate中,我抓住该键并根据需要恢复片段的状态。一个简单的解决方案(至少对我而言)很难解决。


我有一个与您相似的问题,但我不太理解您的解释,您能否提供更多详细信息?我也有和将片段存储在列表中的适配器,但是当我从最近的应用程序中恢复我的应用程序崩溃时,因为有一些分离的片段。问题是在这里stackoverflow.com/questions/11631408/...
格奥尔基Gobozov

3
片段引用中的“我”是什么?
乔什2012年

我们都知道这种解决方案不是很好的方法,但是它是FragmentByTag在ViewPager中使用的最简单的方法。
Youngjae 2015年

这是一种不依赖于与内部分配标签的兼容性而访问片段的方法:stackoverflow.com/questions/14035090/…–
morgwai

26

全局工作测试解决方案。

getSupportFragmentManager()有时会保留空引用,并且View分页器不会找到新引用,因为它找到了对同一片段的引用。因此,getChildFragmentManager()克服这种使用以简单的方式解决问题。

不要这样做:

new PagerAdapter(getSupportFragmentManager(), fragments);

做这个:

new PagerAdapter(getChildFragmentManager() , fragments);


6
仅当您在Fragment而不是活动内部托管(并实例化)pagerAdapter时,才有可能这样做。在这种情况下,您是对的,默认情况下应使用childFragmentManager。默认情况下,使用SupportFragmentManager是错误的
Klitos G. 16-10-7

非常精确 解决了我的问题而不使用黑客。谢谢!我的Kotlin版本从FragmentStatePagerAdapter(activity!!.supportFragmentManager)看起来更容易FragmentStatePagerAdapter(childFragmentManager):)
ecth

7

不要尝试在ViewPager中的片段之间进行交互。您不能保证附有其他碎片,甚至不存在。您可以从活动中进行操作,而不必从片段更改操作栏标题。为此请使用标准接口模式:

public interface UpdateCallback
{
    void update(String name);
}

public class MyActivity extends FragmentActivity implements UpdateCallback
{
    @Override
    public void update(String name)
    {
        getSupportActionBar().setTitle(name);
    }

}

public class MyFragment extends Fragment
{
    private UpdateCallback callback;

    @Override
    public void onAttach(SupportActivity activity)
    {
        super.onAttach(activity);
        callback = (UpdateCallback) activity;
    }

    @Override
    public void onDetach()
    {
        super.onDetach();
        callback = null;
    }

    public void updateActionbar(String name)
    {
        if(callback != null)
            callback.update(name);
    }
}

当然现在将包括代码...我已经在记录这些方法了。在fragmentActivity中调用onStop时,将调用onDetach。onAttach在FragmentActivity的onCreate之前调用。
莫里西2012年

嗯,谢谢,但问题仍然存在。我改为将名称设置为回调。我不能在片段中加入逻辑吗?我的片段触发了服务和其他需要引用该活动的元素。我必须将所有应用程序逻辑移至主要活动,以避免出现问题。这似乎是不必要的。
莫里西2012年

好的,所以我只是对它进行了完整的测试,并注释掉了我所有的代码...除了用于更改标题的回调外。第一次启动应用程序时,我移至该屏幕,标题名称更改为np。加载多个应用程序之后,标题不再更改。看起来是在旧的活动实例上收到了回调。我想不出另一种解释
Maurycy 2012年

我将其缩小为fragmentManager和此问题的一个问题... code.google.com/p/android/issues/detail?id=19211。我仍然不知道如何解决这个问题
Maurycy 2012年

5

您可以在销毁viewpager时删除这些片段,以我为例,我将其从onDestroyView()片段中删除了:

@Override
public void onDestroyView() {

    if (getChildFragmentManager().getFragments() != null) {
        for (Fragment fragment : getChildFragmentManager().getFragments()) {
            getChildFragmentManager().beginTransaction().remove(fragment).commitAllowingStateLoss();
        }
    }

    super.onDestroyView();
}

谢谢,您的解决方案有效。还要求它ViewPager也基于childFragmentManager适配器(不是fragmentManager)。另一个变体也起作用:不使用onDestroyView,而是在ViewPager创建适配器之前除去子片段。
CoolMind

4

在寻找类似问题几个小时后,我认为还有另一种解决方案。这至少对我有用,我只需要更改几行。

这是我遇到的问题,我有一个与视图分页器一起使用的活动,该分页器使用带有两个Fragments的FragmentStatePagerAdapter。一切正常,直到我强制销毁活动(开发人员选项)或旋转屏幕。在方法getItem中创建两个片段之后,我确实保留对这两个片段的引用。

到那时,将再次创建该活动,并且一切正常,但是由于getItem不再被调用,我已经失去了对fragmetns的引用。

这是我在FragmentStatePagerAdapter内部解决此问题的方法:

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Object aux = super.instantiateItem(container, position);

        //Update the references to the Fragments we have on the view pager
        if(position==0){
            fragTabOne = (FragOffersList)aux;
        }
        else{
            fragTabTwo = (FragOffersList) aux;
        }

        return aux;
    }

如果适配器已经在内部对其进行引用,则不会再次调用getItem,并且不应更改该引用。相反,您可以通过查看另一个将为每个片段调用的方法InstantiateItem()来获取正在使用的片段。

希望对任何人有帮助。


如果您有很多碎片怎么办?您真的希望为每个参考保存参考吗?记忆不好吗?
Android开发人员

这一切都取决于您要实现的目标。通常,使用这些视图分页器时,一次最多不会包含三个片段。一个在中间,一个在每一侧。对于我想要的,我确实需要引用这些片段,但是我知道我只需要处理两个。
兰斯洛

0

由于FragmentManager会在调用onResume()方法后立即为您恢复片段,因此我将片段调出到活动中并将其自身添加到列表中。在我的实例中,我将所有这些存储在我的PagerAdapter实现中。每个片段都知道其位置,因为在创建时会将其添加到片段参数中。现在,只要需要在特定索引处操作片段,我所要做的就是使用适配器中的列表。

以下是自定义ViewPager的适配器的示例,该适配器将在片段移入焦点时增大片段,并在移出焦点时按比例缩小片段。除了Adapter和Fragment类之外,我在这里所需要做的就是使父活动能够引用adapter变量并被设置。

适配器

public class GrowPagerAdapter extends FragmentPagerAdapter implements OnPageChangeListener, OnScrollChangedListener {

public final String TAG = this.getClass().getSimpleName();

private final int COUNT = 4;

public static final float BASE_SIZE = 0.8f;
public static final float BASE_ALPHA = 0.8f;

private int mCurrentPage = 0;
private boolean mScrollingLeft;

private List<SummaryTabletFragment> mFragments;

public int getCurrentPage() {
    return mCurrentPage;
}

public void addFragment(SummaryTabletFragment fragment) {
    mFragments.add(fragment.getPosition(), fragment);
}

public GrowPagerAdapter(FragmentManager fm) {
    super(fm);

    mFragments = new ArrayList<SummaryTabletFragment>();
}

@Override
public int getCount() {
    return COUNT;
}

@Override
public Fragment getItem(int position) {
    return SummaryTabletFragment.newInstance(position);
}

@Override
public void onPageScrollStateChanged(int state) {}

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    adjustSize(position, positionOffset);
}

@Override
public void onPageSelected(int position) {
    mCurrentPage = position;
}

/**
 * Used to adjust the size of each view in the viewpager as the user
 * scrolls.  This provides the effect of children scaling down as they
 * are moved out and back to full size as they come into focus.
 * 
 * @param position
 * @param percent
 */
private void adjustSize(int position, float percent) {

    position += (mScrollingLeft ? 1 : 0);
    int secondary = position + (mScrollingLeft ? -1 : 1);
    int tertiary = position + (mScrollingLeft ? 1 : -1);

    float scaleUp = mScrollingLeft ? percent : 1.0f - percent;
    float scaleDown = mScrollingLeft ? 1.0f - percent : percent;

    float percentOut = scaleUp > BASE_ALPHA ? BASE_ALPHA : scaleUp;
    float percentIn = scaleDown > BASE_ALPHA ? BASE_ALPHA : scaleDown;

    if (scaleUp < BASE_SIZE)
        scaleUp = BASE_SIZE;

    if (scaleDown < BASE_SIZE)
        scaleDown = BASE_SIZE;

    // Adjust the fragments that are, or will be, on screen
    SummaryTabletFragment current = (position < mFragments.size()) ? mFragments.get(position) : null;
    SummaryTabletFragment next = (secondary < mFragments.size() && secondary > -1) ? mFragments.get(secondary) : null;
    SummaryTabletFragment afterNext = (tertiary < mFragments.size() && tertiary > -1) ? mFragments.get(tertiary) : null;

    if (current != null && next != null) {

        // Apply the adjustments to each fragment
        current.transitionFragment(percentIn, scaleUp);
        next.transitionFragment(percentOut, scaleDown);

        if (afterNext != null) {
            afterNext.transitionFragment(BASE_ALPHA, BASE_SIZE);
        }
    }
}

@Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {

    // Keep track of which direction we are scrolling
    mScrollingLeft = (oldl - l) < 0;
}
}

分段

public class SummaryTabletFragment extends BaseTabletFragment {

public final String TAG = this.getClass().getSimpleName();

private final float SCALE_SIZE = 0.8f;

private RelativeLayout mBackground, mCover;
private TextView mTitle;
private VerticalTextView mLeft, mRight;

private String mTitleText;
private Integer mColor;

private boolean mInit = false;
private Float mScale, mPercent;

private GrowPagerAdapter mAdapter;
private int mCurrentPosition = 0;

public String getTitleText() {
    return mTitleText;
}

public void setTitleText(String titleText) {
    this.mTitleText = titleText;
}

public static SummaryTabletFragment newInstance(int position) {

    SummaryTabletFragment fragment = new SummaryTabletFragment();
    fragment.setRetainInstance(true);

    Bundle args = new Bundle();
    args.putInt("position", position);
    fragment.setArguments(args);

    return fragment;
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    super.onCreateView(inflater, container, savedInstanceState);

    mRoot = inflater.inflate(R.layout.tablet_dummy_view, null);

    setupViews();
    configureView();

    return mRoot;
}

@Override
public void onViewStateRestored(Bundle savedInstanceState) {
    super.onViewStateRestored(savedInstanceState);

    if (savedInstanceState != null) {
        mColor = savedInstanceState.getInt("color", Color.BLACK);
    }

    configureView();
}

@Override
public void onSaveInstanceState(Bundle outState)  {

    outState.putInt("color", mColor);

    super.onSaveInstanceState(outState);
}

@Override
public int getPosition() {
    return getArguments().getInt("position", -1);
}

@Override
public void setPosition(int position) {
    getArguments().putInt("position", position);
}

public void onResume() {
    super.onResume();

    mAdapter = mActivity.getPagerAdapter();
    mAdapter.addFragment(this);
    mCurrentPosition = mAdapter.getCurrentPage();

    if ((getPosition() == (mCurrentPosition + 1) || getPosition() == (mCurrentPosition - 1)) && !mInit) {
        mInit = true;
        transitionFragment(GrowPagerAdapter.BASE_ALPHA, GrowPagerAdapter.BASE_SIZE);
        return;
    }

    if (getPosition() == mCurrentPosition && !mInit) {
        mInit = true;
        transitionFragment(0.00f, 1.0f);
    }
}

private void setupViews() {

    mCover = (RelativeLayout) mRoot.findViewById(R.id.cover);
    mLeft = (VerticalTextView) mRoot.findViewById(R.id.title_left);
    mRight = (VerticalTextView) mRoot.findViewById(R.id.title_right);
    mBackground = (RelativeLayout) mRoot.findViewById(R.id.root);
    mTitle = (TextView) mRoot.findViewById(R.id.title);
}

private void configureView() {

    Fonts.applyPrimaryBoldFont(mLeft, 15);
    Fonts.applyPrimaryBoldFont(mRight, 15);

    float[] size = UiUtils.getScreenMeasurements(mActivity);
    int width = (int) (size[0] * SCALE_SIZE);
    int height = (int) (size[1] * SCALE_SIZE);

    RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height);
    mBackground.setLayoutParams(params);

    if (mScale != null)
        transitionFragment(mPercent, mScale);

    setRandomBackground();

    setTitleText("Fragment " + getPosition());

    mTitle.setText(getTitleText().toUpperCase());
    mLeft.setText(getTitleText().toUpperCase());
    mRight.setText(getTitleText().toUpperCase());

    mLeft.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            mActivity.showNextPage();
        }
    });

    mRight.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            mActivity.showPrevPage();
        }
    });
}

private void setRandomBackground() {

    if (mColor == null) {
        Random r = new Random();
        mColor = Color.rgb(r.nextInt(255), r.nextInt(255), r.nextInt(255));
    }

    mBackground.setBackgroundColor(mColor);
}

public void transitionFragment(float percent, float scale) {

    this.mScale = scale;
    this.mPercent = percent;

    if (getView() != null && mCover != null) {

        getView().setScaleX(scale);
        getView().setScaleY(scale);

        mCover.setAlpha(percent);
        mCover.setVisibility((percent <= 0.05f) ? View.GONE : View.VISIBLE);
    }
}

@Override
public String getFragmentTitle() {
    return null;
}
}

0

我的解决方案:我几乎将每个View设置为 static。现在,我的应用程序可以完美交互。能够从任何地方调用静态方法可能不是一种好方法,但是为什么要尝试处理无效的代码呢?我在SO上阅读了很多问题及其答案,没有任何解决方案带来成功(对我而言)。

我知道它可能会泄漏内存并浪费堆,并且我的代码不适用于其他项目,但是我对此并不感到害怕-我在不同的设备和条件下测试了该应用程序,完全没有问题,Android平台似乎可以解决这个问题。用户界面每秒刷新一次,甚至在S2 ICS(4.0.3)设备上,该应用程序也可以处理数千个地理标记。


0

我遇到了同样的问题,但是我的ViewPager处于TopFragment内,该片段使用创建并设置了适配器setAdapter(new FragmentPagerAdapter(getChildFragmentManager()))

我通过onAttachFragment(Fragment childFragment)像这样覆盖TopFragment来解决此问题:

@Override
public void onAttachFragment(Fragment childFragment) {
    if (childFragment instanceof OnboardingDiamondsFragment) {
        mChildFragment = (ChildFragment) childFragment;
    }

    super.onAttachFragment(childFragment);
}

众所周知(请参阅上面的答案),当childFragmentManager重新创建自身时,它还会创建viewPager内部的片段。
重要的是,在那之后,他调用onAttachFragment,现在我们可以引用新的重新创建的片段了!

希望这可以帮助任何人像我一样获得这个老Q :)


0

我通过将片段保存在SparceArray中解决了这个问题:

public abstract class SaveFragmentsPagerAdapter extends FragmentPagerAdapter {

    SparseArray<Fragment> fragments = new SparseArray<>();

    public SaveFragmentsPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment fragment = (Fragment) super.instantiateItem(container, position);
        fragments.append(position, fragment);
        return fragment;
    }

    @Nullable
    public Fragment getFragmentByPosition(int position){
        return fragments.get(position);
    }

}

0

只是您知道...

这些类增加了很多麻烦,还有一个非常有趣的错误值得分享。

我正在使用ViewPager导航项目树(选择一个项目,然后View分页器将向右滚动动画,然后出现下一个分支,向后导航,然后ViewPager沿相反方向滚动以返回上一个节点) 。

当我将片段从FragmentStatePagerAdapter的末端推出并弹出时,就会出现问题。注意到项目已更改足够聪明,并且当项目已更改时足够聪明来创建和替换片段。但是当适配器大小更改时,它不够聪明,无法丢弃碎片状态,或者不够聪明,无法修剪内部保存的碎片状态。因此,当您弹出一个项目并将新的项目推到末尾时,新项目的片段会获得旧项目的片段的保存状态,这在我的代码中造成了严重破坏。我的片段中携带的数据可能需要大量工作才能从Internet重新获取,因此不保存状态确实不是一种选择。

我没有干净的解决方法。我用了这样的东西:

  public void onSaveInstanceState(Bundle outState) {
    IFragmentListener listener = (IFragmentListener)getActivity();
    if (listener!= null)
    {
        if (!listener.isStillInTheAdapter(this.getAdapterItem()))
        {
            return; // return empty state.
        }

    }
    super.onSaveInstanceState(outState);

    // normal saving of state for flips and 
    // paging out of the activity follows
    ....
  }

一个不完美的解决方案,因为新的片段实例仍然获得了saveState Bundle,但至少它不携带过时的数据。


0

由于人们不喜欢阅读评论,因此这里给出的答案大部分与我在这里写的内容相同:

造成此问题的根本原因是android系统不调用getItem获取实际显示的片段,而是instantiateItem。此方法首先尝试为中的给定选项卡查找和重用片段实例FragmentManager。仅当此查找失败时(此操作仅在第一次FragmentManager创建时才发生)才被getItem调用。出于明显的原因,例如,每当用户旋转设备时,都不要重新创建碎片(可能很重)。
为了解决这个问题,与其Fragment.instantiate在活动中创建片段,不应该在应该使用它们各自的构造函数真正创建片段的位置进行操作。pagerAdapter.instantiateItem并且所有这些调用都应被startUpdate/finishUpdate分别启动/提交片段事务的方法调用所包围。getItem

List<Fragment> fragments = new Vector<Fragment>();

@Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.myLayout);
        ViewPager viewPager = (ViewPager) findViewById(R.id.myViewPager);
        MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager());
        viewPager.setAdapter(adapter);
        ((TabLayout) findViewById(R.id.tabs)).setupWithViewPager(viewPager);

        adapter.startUpdate(viewPager);
        fragments.add(adapter.instantiateItem(viewPager, 0));
        fragments.add(adapter.instantiateItem(viewPager, 1));
        // and so on if you have more tabs...
        adapter.finishUpdate(viewPager);
}

class MyPagerAdapter extends FragmentPagerAdapter {

        public MyPagerAdapter(FragmentManager manager) {super(manager);}

        @Override public int getCount() {return 2;}

        @Override public Fragment getItem(int position) {
            if (position == 0) return new Fragment0();
            if (position == 1) return new Fragment1();
            return null;  // or throw some exception
        }

        @Override public CharSequence getPageTitle(int position) {
            if (position == 0) return getString(R.string.tab0);
            if (position == 1) return getString(R.string.tab1);
            return null;  // or throw some exception
        }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.