处理屏幕上的片段重复旋转(带示例代码)

时间:2013-03-19 10:01:46

标签: android android-fragments android-listfragment android-fragmentactivity android-framelayout

有一些类似的答案,但不是这种情况。


我的情况很简单。

我有一个具有两种不同布局的活动,一个在Portrait中,另一个在Landscape中。

肖像中,我使用<FrameLayout>并在其中添加Fragment 动态

横向中,我使用<fragment>,因此Fragment 静态(事实上这没关系)

首先以人像开头,然后我简单地添加了Fragment

ListFrag listFrag = new ListFrag();
getSupportFragmentManager().beginTransaction()
        .replace(R.id.FragmentContainer, listFrag).commit();

其中ListFrag extends ListFragment

然后我做一个屏幕旋转。 我发现listFrag正在横向模式下重新创建。 (其中我注意到onCreate()方法再次使用非空Bundle调用)

我尝试使用setRetainInstance(false)像@NPike在this post中说的那样。但默认情况下getRetainInstance()已经false。它没有按照the docs所说的那样做。有人可以解释一下吗?


我正在处理的片段是ListFragment,它在setListAdapter()onCreate()。所以if (container == null) return null; method不能在这里使用。 (或者我不知道如何申请)。

我从this post得到了一些提示。我应该在if (bundle != null) setListAdapter(null); else setListAdapter(new ...);中使用ListFragment吗?但是,当它被销毁/分离时,是否有更好的方法来删除/删除片段,而不是在创建时间内执行它? (以及if (container == null) return null;方法)


修改

我找到的唯一简洁方法是在getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentById(R.id.FragmentContainer)).commit();中执行onSaveInstanceState()。但它会引发另一个问题。

  1. 当屏幕被部分遮挡时,会弹出WhatsApp或TXT对话框,片段也会消失。 (这是相对较小的,只是视觉问题)

  2. 当屏幕旋转时,活动将被完全销毁并重新创建。所以我可以在onCreate(Bundle)onRestoreInstanceState(Bundle)中重新添​​加片段。但是在(1)的情况下,以及切换活动时,当用户返回我的Activity时,都不会调用onCreate(Bundle)onRestoreInstanceState(Bundle)。我无处可重新创建活动(并从Bundle中检索数据)。

  3. 对不起,我没有说清楚,我已做出决定,getSupportFragmentManager()...replace(...).commit();行仅在肖像模式下运行。


    示例代码

    我已经提取了简单的代码,以便更好地说明情况:)

    MainActivity.java

    package com.example.fragremovetrial;
    
    import android.os.Bundle;
    import android.support.v4.app.FragmentActivity;
    
    public class MainActivity extends FragmentActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            if (findViewById(R.id.FragmentContainer) != null) {
                System.out.println("-- Portrait --");       // Portrait
                ListFrag listFrag = new ListFrag();
                getSupportFragmentManager().beginTransaction()
                        .replace(R.id.FragmentContainer, listFrag).commit();
            } else {
                System.out.println("-- Landscape --");      // Landscape
            }
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            if (findViewById(R.id.FragmentContainer) != null) {
                System.out.println("getRetainInstance = " +
                        getSupportFragmentManager().findFragmentById(R.id.FragmentContainer).getRetainInstance());
            }
        }
    }
    

    布局/ activity_main.xml中

    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/FragmentContainer"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />
    

    layout-land / activity_main.xml (无所谓)

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
    </LinearLayout>
    

    ListFrag.java

    package com.example.fragremovetrial;
    
    import android.os.Bundle;
    import android.support.v4.app.ListFragment;
    import android.widget.ArrayAdapter;
    
    public class ListFrag extends ListFragment {
        private String[] MenuItems = { "Content A", "Contnet B" };
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            System.out.println("ListFrag.onCreate(): " + (savedInstanceState == null ? null : savedInstanceState));
    
            setListAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, MenuItems));
        }
    }
    

    注意我得到了以下

    调试消息

      

    - 肖像 -
      ListFrag.onCreate():null
      getRetainInstance = false
      
      (旋转端口 - &gt; Land)
      
      ListFrag.onCreate():Bundle [{android:view_state=android.util.SparseArray@4052dd28}]
       - 风景 -
      以前专注的视图在保存期间报告了ID 16908298,但在恢复期间无法找到   
      (旋转陆地 - >端口)
      
      ListFrag.onCreate():Bundle [{android:view_state=android.util.SparseArray@405166c8}]
       - 肖像 -
      ListFrag.onCreate():null
      getRetainInstance = false
      
      (旋转端口 - &gt; Land)
      
      ListFrag.onCreate():Bundle [{android:view_state=android.util.SparseArray@4050fb40}]
       - 风景 -
      以前专注的视图在保存期间报告了ID 16908298,但在恢复期间无法找到   
      (旋转陆地 - >端口)
      
      ListFrag.onCreate():Bundle [{android:view_state=android.util.SparseArray@40528c60}]
       - 肖像 -
      ListFrag.onCreate():null
      getRetainInstance = false

    当屏幕保持旋转时,创建的片段数量将无限增加,这是我无法解释的。 (请帮忙)

4 个答案:

答案 0 :(得分:2)

我终于提出了一个解决方案,以防止在重新创建活动时重新创建不需要的Fragment。这就像我在问题中提到的那样:

  

我找到的唯一简洁方法是在getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentById(R.id.FragmentContainer)).commit();中执行onSaveInstanceState()。但它会引发另一个问题......

......有一些改进。

onSaveInstanceState()

@Override
protected void onSaveInstanceState(Bundle outState) {
    if (isPortrait2Landscape()) {
        remove_fragments();
    }
    super.onSaveInstanceState(outState);
}

private boolean isPortrait2Landscape() {
    return isDevicePortrait() && (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE);
}

isDevicePortrait()就像:

private boolean isDevicePortrait() {
    return (findViewById(R.id.A_View_Only_In_Portrait) != null);
}

*请注意,我们 无法 使用getResources().getConfiguration().orientation来确定设备当前是字面纵向。这是因为Resources对象在屏幕旋转后更改 RIGHT AFTER - 以前 {{ 1}}被称为!!

如果我们不想使用onSaveInstanceState()来测试方向(出于任何原因,并且它不是那么整洁),请保留一个全局变量findViewById()并将其初始化为private int current_orientation; in current_orientation = getResources().getConfiguration().orientation;。这似乎更整洁。但我们应该注意 不要 在活动生命周期中的任何地方进行更改。

*确保我们onCreate() 之前 remove_fragments()

(因为在我的情况下,我从布局和活动中删除碎片。如果它在super.onSaveInstanceState()之后,布局将已经保存到Bundle中。然后碎片将<在活动重新创建后重新创建strong> 。###)

###我已经证明了这一现象。但What to determine a Fragment restore upon Activity re-create?的原因仅仅是我的猜测。如果您对此有任何想法,请回答my another question

答案 1 :(得分:1)

处理此问题的正确方法是将以下内容放在onCreate()

if (savedInstanceState == null) {
     // Do fragment transaction.
}

答案 2 :(得分:0)

onCreate()方法中,如果您处于纵向模式,则只应创建ListFrag。您可以通过检查纵向布局中仅包含的FrameLayout视图是否为null来执行此操作。

if (findViewById(R.id.yourFrameLayout) != null) {
    // you are in portrait mode
    ListFrag listFrag = new ListFrag();
    getSupportFragmentManager().beginTransaction()
            .replace(R.id.FragmentContainer, listFrag).commit();
} else {
    // you are in landscape mode
    // get your Fragment from the xml
}

最后,如果您从纵向布局切换到横向布局,则不希望创建另一个ListFrag,而是使用您在xml中指定的片段。

答案 3 :(得分:0)

@midnite,首先感谢你的问题,我有同样的问题,我工作了几天。现在我对这个问题做了一些棘手的解决 - 并希望与社区分享这个问题。 但如果有人有更好的解决方案,请写下评论。

所以,我有相同的情况 - 两个设备方向的不同布局。在肖像中,不需要DetailsFragment。

我只是在OnCreate中试图找到已创建的片段:

    fragment = (ToDoListFragment) getFragmentManager().findFragmentByTag(LISTFRAGMENT);
    frameDetailsFragment = (FrameLayout) findViewById(R.id.detailsFragment);
    detailsFragment = (DetailsFragment) getFragmentManager().findFragmentByTag(DETAILS_FRAGMENT);
    settingsFragment = (SettingsFragment) getFragmentManager().findFragmentByTag(SETTINGS_FRAGMENT);

之后我正在添加我的主要片段 -

    if (fragment == null){
        fragment = new ToDoListFragment();
        getFragmentManager().beginTransaction()
                .add(R.id.container, fragment, LISTFRAGMENT)
                .commit();
    }

然后清除我的碎片:

  if (getFragmentManager().getBackStackEntryCount() > 0 ) {
        getFragmentManager().popBackStackImmediate();
    }

最有趣的是 -

  destroyTmpFragments();

这是方法本身:

  private void destroyTmpFragments(){
    if (detailsFragment != null && !detailsFragment.isVisible()) {
        Log.d("ANT", "detailsFragment != null, Destroying");
        getFragmentManager().beginTransaction()
                .remove(detailsFragment)
                .commit();

        detailsFragment = null;
    }

    if (settingsFragment != null && !settingsFragment.isVisible()) {
        Log.d("ANT", "settingsFragment != null, Destroying");
        getFragmentManager().beginTransaction()
                .remove(settingsFragment)
                .commit();

        settingsFragment = null;
    }
}

如你所见,我手动清理FragmentManager为我保存的所有碎片(非常感谢他)。 在日志中,我有下一个生命周期调用:

 04-24 23:03:27.164 3204    3204    D   ANT MainActivity onCreate()
 04-24 23:03:27.184 3204    3204    I   ANT DetailsFragment :: onCreateView
 04-24 23:03:27.204 3204    3204    I   ANT DetailsFragment :: onActivityCreated
 04-24 23:03:27.204 3204    3204    I   ANT DetailsFragment :: onDestroy
 04-24 23:03:27.208 3204    3204    I   ANT DetailsFragment :: onDetach

因此在onActivityCreated中,您的视图会检查null - 以防片段不可见。它稍后会被删除(也许是因为cuz片段管理器的删除是异步的) 之后,在活动代码中,我们可以创建全新的Fragment实例,并使用适当的布局(fargmnet&#39;占位符),片段将能够找到它的视图,并且不会产生NullPointerException < / p>

但是,后备堆栈中的片段会被删除得相当快,而无需调用onActivityCreated(借助上述代码:

 if (getFragmentManager().getBackStackEntryCount() > 0 ) {
        getFragmentManager().popBackStackImmediate();
    }

最后,如果我在陆地方向,最后我会添加片段 -

 if (frameDetailsFragment != null){
        Log.i("ANT", "frameDetailsFragment != null");

        if (EntryPool.getPool().getEntries().size() > 0) {
            if (detailsFragment == null) {
                detailsFragment = DetailsFragment.newInstance(EntryPool.getPool().getEntries().get(0), 0);
            }

            getFragmentManager().beginTransaction()
                    .replace(R.id.detailsFragment, detailsFragment, DETAILS_FRAGMENT)
                    .commit();
        }
    }