为避免再创造片段的屏幕旋转后的真实做法(官方片段开发指南为例) [英] Real approach for Avoiding Re-creation of Fragment after Screen Rotate (Official Fragment Developer Guide as example)

查看:205
本文介绍了为避免再创造片段的屏幕旋转后的真实做法(官方片段开发指南为例)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我发现为了避免再创造片段的画面旋转

,实际的做法

如果(集装箱== NULL){返回NULL; } 不可以真正避免了碎片重新创建的。 (如下图所示)


在哪里官方片段开发人员指南?

我们有关的官方指南是 http://developer.android.com/导/组件/ fragments.html 。该部分例如code 是在导向的底部。据我所知,全样本code是可以在样品的SDK的Andr​​oid 3.0(API 11)下。此外,我也做最小的改动样品code为它的API 10运行,并增加了一些调试信息,其中包括在这一问题的底部。

哪里是 R.id.a_item

您可能会发现在开发者指南示例的后续code存根:

 如果(指数== 0){
    ft.replace(R.id.details,详细说明);
} 其他 {
    ft.replace(R.id.a_item,详细说明);
}
 

我做了互联网上一些搜索并找到其他一些也涉及其中是 R.id.a_item 。检查样本中11 API后,我确信它是只是一个毫无意义的错字。有样品中没有这样的线,在所有


,以避免再创作片段的屏幕旋转后,真正的办法?

有在网上许多现有的讨论。但似乎没有一个真实的解决方案呢。

我添加了大量的调试消息到下面的code,以跟踪 DetailsFragment 类的生命周期。尝试(1)启动程序肖像模式,然后(2)将设备进入横向模式,(3)然后再将其肖像,(4)景观再次,(5)回肖像一遍,最后(6)退出它。我们将有以下调试消息:

(1)启动在人像模式

  

TitlesFragment.onCreate()套装= NULL

只有 TitlesFragment 创建。 DetailsFragment 还没有显示。

(2)转成风景模式

  

TitlesFragment.onCreate()包=包[{shownChoice = -1,机器人:view_state=android.util.SparseArray@4051d3a8,curChoice = 0}]
  DetailsFragment.onAttach()Activity=com.example.android.apis.app.FragmentLayout@4051d640
  DetailsFragment.onCreate()套装= NULL
  DetailsFragment.onCreateView()Activity=android.widget.FrameLayout@4050df68
  DetailsFragment.onActivityCreated()套装= NULL
  DetailsFragment.onStart()
  DetailsFragment.onResume()

首先, TitlesFragment 重新创建(与savedInstanceState包)。然后 DetailsFragment 是动态创建的(由 TitlesFragment.onActivityCreated(),要求 showDetails(),使用 FragmentTransaction )。

(3)返回的人像模式

  

DetailsFragment.onPause()
  DetailsFragment.onStop()
  DetailsFragment.onDestroyView()
  DetailsFragment.onDestroy()
  DetailsFragment.onDetach()
  DetailsFragment.onAttach()Activity=com.example.android.apis.app.FragmentLayout@40527f70
  DetailsFragment.onCreate()套装= NULL
  TitlesFragment.onCreate()包=包[{shownChoice = 0,安卓view_state=android.util.SparseArray@405144b0,curChoice = 0}]
  DetailsFragment.onCreateView()活动= NULL
  DetailsFragment.onActivityCreated()套装= NULL
  DetailsFragment.onStart()
  DetailsFragment.onResume()

下面是我们对有关首先在真正再创造回避的办法。

这是因为 DetailsFragment 是previously连接到布局陆/ fragment_layout.xml <的FrameLayout> 的ViewGroup 在横向模式。而且它有一个ID( R.id.details )。当屏幕旋转时,的ViewGroup ,这是的 DetailsFragment ,被保存到活动FragmentLayout的捆绑一个实例,在FragmentLayout的的onSaveInstanceState()。进入人像模式后, DetailsFragment 重新创建。但它的不可以需要在纵向模式下。

在样品(等许多其他建议),则 DetailsFragment 类使用如果(集装箱== NULL){返回NULL; } onCreateView(),以避免 DetailsFragment 显示在纵向模式下可达。然而,如在上面的调试消息,在 DetailsFragment 还活着的背景下,作为一个孤儿,让所有的生命周期方法调用。

(4)要风景再次模式

  

DetailsFragment.onPause()
  DetailsFragment.onStop()
  DetailsFragment.onDestroyView()
  DetailsFragment.onDestroy()
  DetailsFragment.onDetach()
  DetailsFragment.onAttach()Activity=com.example.android.apis.app.FragmentLayout@4052c7d8
  DetailsFragment.onCreate()套装= NULL
  TitlesFragment.onCreate()包=包[{shownChoice = 0,安卓view_state=android.util.SparseArray@40521b80,curChoice = 0}]
  DetailsFragment.onCreateView()Activity=android.widget.FrameLayout@40525270
  DetailsFragment.onActivityCreated()套装= NULL
  DetailsFragment.onStart()
  DetailsFragment.onResume()

在第5行通知, DetailsFragment 完成其生命周期状态,然后破坏和分离。

这进一步证明了如果(集装箱== NULL){返回NULL; } 方法 真正办法摆脱 DetailsFragment 实例。 (我认为垃圾回收器会毁了这个孩子晃来晃去,但它没有这是因为Android的确实允许悬空片段参考:添加一个片段,没有一个用户界面。)

据我了解,从第6行开始,它应该是一个新的 DetailsFragment 例如由 TitlesFragment ,因为它没有在(2)。但我无法解释为什么 DetailsFragment onAttach()的onCreate()方法之前叫做 TitlesFragment 的onCreate()

捆绑在 DetailsFragment 的onCreate()将证明这是一个新的实例。

据我了解,在previous晃来晃去 DetailsFragment 实例没有重新创建这个时间,因为它没有一个ID。所以它没有自动保存与视图层次到savedInstanceState捆绑。

(5)返回人像再次模式

  

DetailsFragment.onPause()
  DetailsFragment.onStop()
  DetailsFragment.onDestroyView()
  DetailsFragment.onDestroy()
  DetailsFragment.onDetach()
  DetailsFragment.onAttach()Activity=com.example.android.apis.app.FragmentLayout@4052d7d8
  DetailsFragment.onCreate()套装= NULL
  TitlesFragment.onCreate()包=包[{shownChoice = 0,安卓view_state=android.util.SparseArray@40534e30,curChoice = 0}]
  DetailsFragment.onCreateView()活动= NULL
  DetailsFragment.onActivityCreated()套装= NULL
  DetailsFragment.onStart()
  DetailsFragment.onResume()

请注意在这里,所有的生命周期回调的第一次回肖像的(3),除了与不同的活动ID (40527f70 VS 4052d7d8) view_state包(405144b0 VS 40534e30)。这是合理的。无论是FragmentLayout活动和实例状态包重新创建。

(6)的退出(按后退键)

  

我/的System.out(29845):DetailsFragment.onPause()   我/的System.out(29845):DetailsFragment.onStop()   我/的System.out(29845):DetailsFragment.onDestroyView()   我/的System.out(29845):DetailsFragment.onDestroy()   我/的System.out(29845):DetailsFragment.onDetach()

这将是完美的,如果我们可以删除 DetailsFragment FragmentLayout 的onDestroy ()。但 FragmentTransaction 删除()方法需要以前被称为的onSaveInstanceState() 。但是没有办法,以确定它是否是一个屏幕旋转或不的onSaveInstanceState()

这也不能删除 DetailsFragment FragmentLayout 的onSaveInstanceState( )反正。首先,如果 DetailsFragment 只是部分地被对话框遮挡,它会消失在背景。此外,在通过对话框遮挡,或交换活动的情况下,无论是的onCreate(捆绑) onRestoreInstanceState(捆绑)将会再次调用。因此,我们有没有在那里恢复片段(从包中检索数据)。


来源$ C ​​$ CS&放大器;文件

FragmentLayout.java

 包com.example.android.apis.app;

进口android.app.Activity;
进口android.content.Intent;
进口android.content.res.Configuration;
进口android.os.Bundle;
进口android.support.v4.app.Fragment;
进口android.support.v4.app.FragmentActivity;
进口android.support.v4.app.FragmentTransaction;
进口android.support.v4.app.ListFragment;
进口android.util.TypedValue;
进口android.view.LayoutInflater;
进口android.view.View;
进口android.view.ViewGroup;
进口android.widget.ArrayAdapter;
进口android.widget.ListView;
进口android.widget.ScrollView;
进口android.widget.TextView;

公共类FragmentLayout扩展FragmentActivity {

    私人最终静态类莎士比亚{
        公共静态最后的String []业权= {爱,恨,一,日};
        公共静态最后的String []对话= {
            爱爱爱爱爱,
            我恨我恨我恨我恨我恨,
            一壹壹壹壹,
            日日日日日};
    }

    @覆盖
    保护无效的onCreate(包savedInstanceState){
        super.onCreate(savedInstanceState);
        的setContentView(R.layout.fragment_layout);
    }

    公共静态类DetailsActivity扩展FragmentActivity {
        @覆盖
        保护无效的onCreate(包savedInstanceState){
            super.onCreate(savedInstanceState);

            如果(getResources()。getConfiguration()。方向
                    == Configuration.ORIENTATION_LANDSCAPE){
                //如果屏幕现在是在横向模式下,我们可以显示
                //对话框中,符合列表,所以我们不需要这个活动。
                完();
                返回;
            }

            如果(savedInstanceState == NULL){
                //在初始设置,插上的细节片段。
                DetailsFragment细节=新DetailsFragment();
                details.setArguments(getIntent()getExtras());
                。getSupportFragmentManager()的BeginTransaction()加(android.R.id.content,详情).commit()。
            }
        }
    }

    公共静态类TitlesFragment扩展ListFragment {
        布尔mDualPane;
        INT mCurCheckPosition = 0;
        INT mShownCheckPosition = -1;

        @覆盖
        公共无效的onCreate(包savedInstanceState){
            super.onCreate(savedInstanceState);
            的System.out.println(的getClass()。getSimpleName()+.onCreate()软件包=+
                    (?savedInstanceState == NULL空:savedInstanceState));
        }

        @覆盖
        公共无效onActivityCreated(包savedInstanceState){
            super.onActivityCreated(savedInstanceState);

            //填充列表,我们的静态职称的数组。
            setListAdapter(新ArrayAdapter<字符串>(getActivity()
                    android.R.layout.simple_list_item_1,Shakespeare.TITLES));
            // API 11:android.R.layout.simple_list_item_activated_1

            //检查,看看我们是否有在其中嵌入细节的框架
            //直接在含UI片段。
            查看detailsFrame = getActivity()findViewById(R.id.details)。
            mDualPane = detailsFrame = NULL和放大器;!&安培; detailsFrame.getVisibility()== View.VISIBLE;

            如果(savedInstanceState!= NULL){
                //恢复过去的状态检查的地位。
                mCurCheckPosition = savedInstanceState.getInt(curChoice,0);
                mShownCheckPosition = savedInstanceState.getInt(shownChoice,-1);
            }

            如果(mDualPane){
                //在双窗格模式,列表视图中突出显示选定的项目。
                。getListView()setChoiceMode(ListView.CHOICE_MODE_SINGLE);
                //确保我们的用户界面是在正确的状态。
                showDetails(mCurCheckPosition);
            }
        }

        @覆盖
        公共无效的onSaveInstanceState(包outState){
            super.onSaveInstanceState(outState);
            outState.putInt(curChoice,mCurCheckPosition);
            outState.putInt(shownChoice,mShownCheckPosition);
        }

        @覆盖
        公共无效onListItemClick(ListView的L,视图V,INT位置,长的id){
            showDetails(位置);
        }

        / **
         *辅助功能,以显示所选择的项目的详细信息,或者通过
         *显示就地片段在当前的用户界面,或开始
         *在其中显示整个新的活动。
         * /
        无效showDetails(INT指数){
            mCurCheckPosition =指数;

            如果(mDualPane){
                //我们可以显示一切就地与片段,更新
                //列表以高亮显示所选项目和显示数据。
                getListView()setItemChecked(指数,真正的)。

                如果(mShownCheckPosition!= mCurCheckPosition){
                    //如果我们目前没有显示出新片段
                    //位置,我们需要创建和安装一个新的。
                    DetailsFragment DF = DetailsFragment.newInstance(指数);

                    //执行事务,替换现有的片段
                    //这一个框架内。
                    FragmentTransaction英尺= getFragmentManager()的BeginTransaction()。
                    ft.replace(R.id.details,DF);
                    ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                    ft.commit();
                    mShownCheckPosition =指数;
                }

            } 其他 {
                //否则,我们需要推出新的活动,以显示
                //对话片段与选定的文本。
                意向意图=新的意图();
                intent.setClass(getActivity(),DetailsActivity.class);
                intent.putExtra(指数,指数);
                startActivity(意向);
            }
        }
    }

    公共静态类DetailsFragment扩展片段{
        / **
         *创建DetailsFragment的新实例,初始化为
         *显示在索引的文字。
         * /
        公共静态DetailsFragment的newInstance(INT指数){
            DetailsFragment F =新DetailsFragment();

            //供应指标输入作为参数。
            捆绑的args =新包();
            args.putInt(指数,指数);
            f.setArguments(参数);

            返回F;
        }

        @覆盖
        公共无效的onCreate(包savedInstanceState){
            super.onCreate(savedInstanceState);
            的System.out.println(的getClass()。getSimpleName()+.onCreate()软件包=+
                    (?savedInstanceState == NULL空:savedInstanceState));
        }
        @覆盖
        公共无效onAttach(活动活动){
            super.onAttach(活动);
            的System.out.println(的getClass()。getSimpleName()+.onAttach()活动=+
                    (活动== NULL空:活动?));
        }
        @覆盖
        公共无效onActivityCreated(包savedInstanceState){
            super.onActivityCreated(savedInstanceState);
            的System.out.println(的getClass()。getSimpleName()+.onActivityCreated()软件包=+
                    (?savedInstanceState == NULL空:savedInstanceState));
        }
        @覆盖
        公共无效的OnStart(){super.onStart();的System.out.println(的getClass()getSimpleName()+.onStart()); }
        @覆盖
        公共无效onResume(){super.onResume();的System.out.println(的getClass()getSimpleName()+.onResume()); }
        @覆盖
        公共无效的onPause(){super.onPause();的System.out.println(的getClass()getSimpleName()+.onPause()); }
        @覆盖
        公共无效的onStop(){super.onStop();的System.out.println(的getClass()getSimpleName()+.onStop()); }
        @覆盖
        公共无效onDestroyView(){super.onDestroyView();的System.out.println(的getClass()getSimpleName()+.onDestroyView()); }
        @覆盖
        公共无效的onDestroy(){super.onDestroy();的System.out.println(的getClass()getSimpleName()+.onDestroy()); }
        @覆盖
        公共无效onDetach(){super.onDetach();的System.out.println(的getClass()getSimpleName()+.onDetach()); }

        @覆盖
        公共查看onCreateView(LayoutInflater充气,容器的ViewGroup,
                捆绑savedInstanceState){
            的System.out.println(的getClass()。getSimpleName()+.onCreateView()活动=+
                    (集装箱== NULL空:容器?));

            如果(集装箱== NULL){
                //我们有不同的布局,并在他们这一个
                //片段的含框架不存在。片段
                //仍然可以从它的保存的状态创建的,但是有
                //没有理由去尝试创建视图,因为它的层次结构
                //将不被显示。请注意,这是没有必要 - 我们可以
                //只需运行code以下,我们将创建并返回
                //视图层次;它只是永远不会被使用。
                返回null;
            }

            滚动型滚轮=新的滚动型(getActivity());
            TextView的文本=新的TextView(getActivity());
            INT填充=(int)的TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                    。4,getActivity()getResources()getDisplayMetrics());
            text.setPadding(填充,填充,填充,填充);
            scroller.addView(文本);
            text.setText(Shakespeare.DIALOGUE [getArguments()调用getInt(索引,0)。]);
            返回卷轴;
        }
    }

}
 

布局/ fragment_layout.xml

 < XML版本=1.0编码=UTF-8&GT?;
<的FrameLayout的xmlns:机器人=htt​​p://schemas.android.com/apk/res/android
    机器人:layout_width =match_parent机器人:layout_height =match_parent>
    <片段类=com.example.android.apis.app.FragmentLayout $ TitlesFragment
            机器人:ID =@ + ID /标题
            机器人:layout_width =match_parent机器人:layout_height =match_parent/>
< /的FrameLayout>
 

布局陆/ fragment_layout.xml

 < XML版本=1.0编码=UTF-8&GT?;
< LinearLayout中的xmlns:机器人=htt​​p://schemas.android.com/apk/res/android
    机器人:方向=横向机器人:baselineAligned =假
    机器人:layout_width =match_parent机器人:layout_height =match_parent>

    <片段类=com.example.android.apis.app.FragmentLayout $ TitlesFragment
            机器人:ID =@ + ID /标题机器人:layout_weight =1
            机器人:layout_width =0px​​机器人:layout_height =match_parent/>

    <的FrameLayout机器人:ID =@ + ID /详细信息的android:layout_weight =1
            机器人:layout_width =0px​​机器人:layout_height =match_parent/>
    <  -  11 API:机器人:背景=机器人:ATTR / detailsElementBackground - >

< / LinearLayout中>
 

AndroidManifest.xml中

 < XML版本=1.0编码=UTF-8&GT?;
<舱单的xmlns:机器人=htt​​p://schemas.android.com/apk/res/android
    包=com.example.android.apis.app
    安卓版code =1
    机器人:VERSIONNAME =1.0>

    <用途-SDK
        安卓的minSdkVersion =4
        机器人:targetSdkVersion =17/>

    <应用
        机器人:allowBackup =真
        机器人:图标=@可绘制/ ic_launcher
        机器人:标签=@字符串/ APP_NAME
        机器人:主题=@风格/ AppTheme>
        <活动
            机器人:名称=com.example.android.apis.app.FragmentLayout
            机器人:标签=@字符串/ APP_NAME>
            <意向滤光器>
                <作用机器人:名称=android.intent.action.MAIN/>

                <类机器人:名称=android.intent.category.LAUNCHER/>
            &所述; /意图滤光器>
        < /活性GT;
        <活动
            机器人:名称=com.example.android.apis.app.FragmentLayout $ DetailsActivity
            机器人:标签=@字符串/ APP_NAME>
        < /活性GT;
    < /用途>

< /舱单>
 

解决方案

感谢@RogerGarzonNieto非常为察觉的在方向改变禁用自动活动再创造的方法。它是非常有用的。我相信我将不得不使用它在某些情况下,在进一步提高。

对于刚刚避开碎片再创作后,屏幕旋转时,我发现了一个更简单的方法,我们的可以的仍允许活动将重新创建像往常一样。

的onSaveInstanceState()

  @覆盖
保护无效的onSaveInstanceState(包outState){
    如果(isPortrait2Landscape()){
        remove_fragments();
    }
    super.onSaveInstanceState(outState);
}

私人布尔isPortrait2Landscape(){
    返回isDevicePortrait()&安培;&安培; (。getResources()getConfiguration()方向== Configuration.ORIENTATION_LANDSCAPE);
}
 

isDevicePortrait()是这样的:

 私人布尔isDevicePortrait(){
    返程(findViewById(R.id.A_View_Only_In_Portrait)!= NULL);
}
 

*请注意,我们的不能使用 getResources()。getConfiguration()。方向来确定设备是的目前字面上的肖像。这是因为资源对象改变的紧接着屏幕旋转 - 之前 的onSaveInstanceState()被称为!!

如果你不想使用 findViewById()来测试的方向(以任何理由,这不是那么整齐毕竟),保持一个全局变量私人诠释current_orientation; current_orientation = getResources()初始化它getConfiguration()方向; 的onCreate ()。这似乎更整洁。但是,我们应该知道的活动生命周期内的任何地方进行更改。

*请务必我们 remove_fragments() super.onSaveInstanceState()

(因为在我的情况,我取出碎片布局,并从活动,如果是在 super.onSaveInstanceState(),布局将已经保存到捆绑,然后将片段将来重新创建活动结束后重新创建。###)

###我已经证明了这一现象。但原因<一href="http://stackoverflow.com/questions/15555964/what-to-determine-a-fragment-restore-upon-activity-re-create">What确定后,活动一个片段恢复重新创建? 只是我的猜测。如果您有任何关于它的想法,请回答<一href="http://stackoverflow.com/questions/15555964/what-to-determine-a-fragment-restore-upon-activity-re-create">my另一个问题。谢谢!

i'm finding a Real approach for Avoiding Re-creation of Fragment after Screen Rotate

if (container == null) { return null; } is not really avoiding the Fragment to be re-created at all. (illustrated below)


Where is the Official Fragment Developer Guide?

The official guide we are concerning is at http://developer.android.com/guide/components/fragments.html. The partial example code is at the bottom of the guide. As far as i know, the full sample code is available in the "Samples for SDK" under Android 3.0 (API 11). Also, i have done minimal modification to the sample code for it to run in API 10- and added some debug messages, which is included at the bottom of this question.

Where is R.id.a_item?

You may find in the Developer Guide Example the follow code stub:

if (index == 0) {
    ft.replace(R.id.details, details);
} else {
    ft.replace(R.id.a_item, details);
}

i did some searches on the Internet and find some others are also concerning where is the R.id.a_item. After inspecting the sample in API 11, i am quite sure it is just a meaningless typo. There are no such lines in the sample at all.


Real approach for avoiding re-creation of Fragment after screen rotate?

There are many existing discussions over the web. But it seems that there is not a "real" solution yet.

I have added lots of debug messages into the code below to keep track of the lifecycle of the DetailsFragment class. Try to (1) initiate the program in portrait mode, then (2) turn the device into landscape mode, then (3) turn it back to portrait, (4) to landscape again, (5) back to portrait again, and finally (6) quit it. We will have the following debug messages:

(1) Initiate in portrait mode

TitlesFragment.onCreate() Bundle=null

Only TitlesFragment is created. DetailsFragment is not shown yet.

(2) Turn into landscape mode

TitlesFragment.onCreate() Bundle=Bundle[{shownChoice=-1, android:view_state=android.util.SparseArray@4051d3a8, curChoice=0}]
DetailsFragment.onAttach() Activity=com.example.android.apis.app.FragmentLayout@4051d640
DetailsFragment.onCreate() Bundle=null
DetailsFragment.onCreateView() Activity=android.widget.FrameLayout@4050df68
DetailsFragment.onActivityCreated() Bundle=null
DetailsFragment.onStart()
DetailsFragment.onResume()

First, TitlesFragment is re-created (with the savedInstanceState Bundle). Then DetailsFragment is created dynamically (by TitlesFragment.onActivityCreated(), calling showDetails(), using FragmentTransaction).

(3) Back to portrait mode

DetailsFragment.onPause()
DetailsFragment.onStop()
DetailsFragment.onDestroyView()
DetailsFragment.onDestroy()
DetailsFragment.onDetach()
DetailsFragment.onAttach() Activity=com.example.android.apis.app.FragmentLayout@40527f70
DetailsFragment.onCreate() Bundle=null
TitlesFragment.onCreate() Bundle=Bundle[{shownChoice=0, android:view_state=android.util.SparseArray@405144b0, curChoice=0}]
DetailsFragment.onCreateView() Activity=null
DetailsFragment.onActivityCreated() Bundle=null
DetailsFragment.onStart()
DetailsFragment.onResume()

Here is the first place that we are concerning about the real re-creation avoiding approach.

It is because the DetailsFragment was previously attached to the layout-land/fragment_layout.xml <FrameLayout> ViewGroup in landscape mode. And it is having an ID (R.id.details). When the screen rotates, the ViewGroup, which is an instance of the DetailsFragment, is saved into the Activity FragmentLayout's Bundle, in the FragmentLayout's onSaveInstanceState(). After entering portrait mode, the DetailsFragment is re-created. But it is not needed in portrait mode.

In the sample (and so as many others suggested), the DetailsFragment class uses if (container == null) { return null; } in onCreateView() to avoid the DetailsFragment showing up in portrait mode. However, as shown in the above debug messages, the DetailsFragment is still alive in the background, as an orphan, having all the lifecycle method calls.

(4) To landscape mode again

DetailsFragment.onPause()
DetailsFragment.onStop()
DetailsFragment.onDestroyView()
DetailsFragment.onDestroy()
DetailsFragment.onDetach()
DetailsFragment.onAttach() Activity=com.example.android.apis.app.FragmentLayout@4052c7d8
DetailsFragment.onCreate() Bundle=null
TitlesFragment.onCreate() Bundle=Bundle[{shownChoice=0, android:view_state=android.util.SparseArray@40521b80, curChoice=0}]
DetailsFragment.onCreateView() Activity=android.widget.FrameLayout@40525270
DetailsFragment.onActivityCreated() Bundle=null
DetailsFragment.onStart()
DetailsFragment.onResume()

Notice in the first 5 lines, the DetailsFragment completes its lifecycle states then destroy and detached.

This further proves the if (container == null) { return null; } method is not a real approach to get rid of the DetailsFragment instance. (i thought the Garbage Collector would destroy this dangling child but it didn't. It's because Android does allow a dangling Fragment. Ref: Adding a fragment without a UI.)

As far as i understand, starting from the 6th line, it should be a new DetailsFragment instance creating by the TitlesFragment, as it did in (2). But i cannot explain why the DetailsFragment's onAttach() and onCreate() methods are called before the TitlesFragment's onCreate().

But the null Bundle in DetailsFragment's onCreate() would prove it is a new instance.

To my understanding, the previous dangling DetailsFragment instance is not re-creating this time because it does not have an ID. So it did not auto-save with the view hierarchy into the savedInstanceState Bundle.

(5) Back to portrait mode again

DetailsFragment.onPause()
DetailsFragment.onStop()
DetailsFragment.onDestroyView()
DetailsFragment.onDestroy()
DetailsFragment.onDetach()
DetailsFragment.onAttach() Activity=com.example.android.apis.app.FragmentLayout@4052d7d8
DetailsFragment.onCreate() Bundle=null
TitlesFragment.onCreate() Bundle=Bundle[{shownChoice=0, android:view_state=android.util.SparseArray@40534e30, curChoice=0}]
DetailsFragment.onCreateView() Activity=null
DetailsFragment.onActivityCreated() Bundle=null
DetailsFragment.onStart()
DetailsFragment.onResume()

Notice here that all the lifecycle callbacks are identical to the first time back to portrait in (3), except with the different Activity ID (40527f70 vs 4052d7d8) and view_state Bundle (405144b0 vs 40534e30). This is reasonable. Both the FragmentLayout Activity and the Instance State Bundle are re-created.

(6) Quit (by BACK button)

I/System.out(29845): DetailsFragment.onPause() I/System.out(29845): DetailsFragment.onStop() I/System.out(29845): DetailsFragment.onDestroyView() I/System.out(29845): DetailsFragment.onDestroy() I/System.out(29845): DetailsFragment.onDetach()

It would be perfect if we can remove the DetailsFragment in FragmentLayout's onDestroy(). But the FragmentTransaction's remove() method needs to be called before the onSaveInstanceState(). However there is no way to determine if it is a screen rotate or not in onSaveInstanceState().

It is also not possible to remove the DetailsFragment in FragmentLayout's onSaveInstanceState() anyway. First, if the DetailsFragment is just partially obscured by a dialogue box, it will be disappeared in the background. In addition, in cases of obscured by dialogue box, or switching activities, neither onCreate(Bundle) nor onRestoreInstanceState(Bundle) will be called again. Thus we have no where to restore the Fragment (and retrieve data from Bundle).


Source codes & files

FragmentLayout.java

package com.example.android.apis.app;

import android.app.Activity;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.ListFragment;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.ScrollView;
import android.widget.TextView;

public class FragmentLayout extends FragmentActivity {

    private final static class Shakespeare {
        public static final String[] TITLES = { "Love", "Hate", "One", "Day" };
        public static final String[] DIALOGUE = {
            "Love Love Love Love Love",
            "Hate Hate Hate Hate Hate",
            "One One One One One",
            "Day Day Day Day Day" };
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.fragment_layout);
    }

    public static class DetailsActivity extends FragmentActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            if (getResources().getConfiguration().orientation
                    == Configuration.ORIENTATION_LANDSCAPE) {
                // If the screen is now in landscape mode, we can show the
                // dialog in-line with the list so we don't need this activity.
                finish();
                return;
            }

            if (savedInstanceState == null) {
                // During initial setup, plug in the details fragment.
                DetailsFragment details = new DetailsFragment();
                details.setArguments(getIntent().getExtras());
                getSupportFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
            }
        }
    }

    public static class TitlesFragment extends ListFragment {
        boolean mDualPane;
        int mCurCheckPosition = 0;
        int mShownCheckPosition = -1;

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            System.out.println(getClass().getSimpleName() + ".onCreate() Bundle=" + 
                    (savedInstanceState == null ? null : savedInstanceState));
        }

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

            // Populate list with our static array of titles.
            setListAdapter(new ArrayAdapter<String>(getActivity(),
                    android.R.layout.simple_list_item_1, Shakespeare.TITLES));
            // API 11:android.R.layout.simple_list_item_activated_1

            // Check to see if we have a frame in which to embed the details
            // fragment directly in the containing UI.
            View detailsFrame = getActivity().findViewById(R.id.details);
            mDualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;

            if (savedInstanceState != null) {
                // Restore last state for checked position.
                mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
                mShownCheckPosition = savedInstanceState.getInt("shownChoice", -1);
            }

            if (mDualPane) {
                // In dual-pane mode, the list view highlights the selected item.
                getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
                // Make sure our UI is in the correct state.
                showDetails(mCurCheckPosition);
            }
        }

        @Override
        public void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            outState.putInt("curChoice", mCurCheckPosition);
            outState.putInt("shownChoice", mShownCheckPosition);
        }

        @Override
        public void onListItemClick(ListView l, View v, int position, long id) {
            showDetails(position);
        }

        /**
         * Helper function to show the details of a selected item, either by
         * displaying a fragment in-place in the current UI, or starting a
         * whole new activity in which it is displayed.
         */
        void showDetails(int index) {
            mCurCheckPosition = index;

            if (mDualPane) {
                // We can display everything in-place with fragments, so update
                // the list to highlight the selected item and show the data.
                getListView().setItemChecked(index, true);

                if (mShownCheckPosition != mCurCheckPosition) {
                    // If we are not currently showing a fragment for the new
                    // position, we need to create and install a new one.
                    DetailsFragment df = DetailsFragment.newInstance(index);

                    // Execute a transaction, replacing any existing fragment
                    // with this one inside the frame.
                    FragmentTransaction ft = getFragmentManager().beginTransaction();
                    ft.replace(R.id.details, df);
                    ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                    ft.commit();
                    mShownCheckPosition = index;
                }

            } else {
                // Otherwise we need to launch a new activity to display
                // the dialog fragment with selected text.
                Intent intent = new Intent();
                intent.setClass(getActivity(), DetailsActivity.class);
                intent.putExtra("index", index);
                startActivity(intent);
            }
        }
    }

    public static class DetailsFragment extends Fragment {
        /**
         * Create a new instance of DetailsFragment, initialized to
         * show the text at 'index'.
         */
        public static DetailsFragment newInstance(int index) {
            DetailsFragment f = new DetailsFragment();

            // Supply index input as an argument.
            Bundle args = new Bundle();
            args.putInt("index", index);
            f.setArguments(args);

            return f;
        }

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            System.out.println(getClass().getSimpleName() + ".onCreate() Bundle=" + 
                    (savedInstanceState == null ? null : savedInstanceState));
        }
        @Override
        public void onAttach(Activity activity) {
            super.onAttach(activity);
            System.out.println(getClass().getSimpleName() + ".onAttach() Activity=" + 
                    (activity == null ? null : activity));
        }
        @Override
        public void onActivityCreated(Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
            System.out.println(getClass().getSimpleName() + ".onActivityCreated() Bundle=" + 
                    (savedInstanceState == null ? null : savedInstanceState));
        }
        @Override
        public void onStart() { super.onStart(); System.out.println(getClass().getSimpleName() + ".onStart()"); }
        @Override
        public void onResume() { super.onResume(); System.out.println(getClass().getSimpleName() + ".onResume()"); }
        @Override
        public void onPause() { super.onPause(); System.out.println(getClass().getSimpleName() + ".onPause()"); }
        @Override
        public void onStop() { super.onStop(); System.out.println(getClass().getSimpleName() + ".onStop()"); }
        @Override
        public void onDestroyView() { super.onDestroyView(); System.out.println(getClass().getSimpleName() + ".onDestroyView()"); }
        @Override
        public void onDestroy() { super.onDestroy(); System.out.println(getClass().getSimpleName() + ".onDestroy()"); }
        @Override
        public void onDetach() { super.onDetach(); System.out.println(getClass().getSimpleName() + ".onDetach()"); }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            System.out.println(getClass().getSimpleName() + ".onCreateView() Activity=" + 
                    (container == null ? null : container));

            if (container == null) {
                // We have different layouts, and in one of them this
                // fragment's containing frame doesn't exist.  The fragment
                // may still be created from its saved state, but there is
                // no reason to try to create its view hierarchy because it
                // won't be displayed.  Note this is not needed -- we could
                // just run the code below, where we would create and return
                // the view hierarchy; it would just never be used.
                return null;
            }

            ScrollView scroller = new ScrollView(getActivity());
            TextView text = new TextView(getActivity());
            int padding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                    4, getActivity().getResources().getDisplayMetrics());
            text.setPadding(padding, padding, padding, padding);
            scroller.addView(text);
            text.setText(Shakespeare.DIALOGUE[getArguments().getInt("index", 0)]);
            return scroller;
        }
    }

}

layout/fragment_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent">
    <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"
            android:id="@+id/titles"
            android:layout_width="match_parent" android:layout_height="match_parent" />
</FrameLayout>

layout-land/fragment_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:baselineAligned="false"
    android:layout_width="match_parent" android:layout_height="match_parent">

    <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"
            android:id="@+id/titles" android:layout_weight="1"
            android:layout_width="0px" android:layout_height="match_parent" />

    <FrameLayout android:id="@+id/details" android:layout_weight="1"
            android:layout_width="0px" android:layout_height="match_parent" />
    <!-- API 11:android:background="?android:attr/detailsElementBackground" -->

</LinearLayout>

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.android.apis.app"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="4"
        android:targetSdkVersion="17" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.android.apis.app.FragmentLayout"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="com.example.android.apis.app.FragmentLayout$DetailsActivity"
            android:label="@string/app_name" >
        </activity>
    </application>

</manifest>

解决方案

Thanks @RogerGarzonNieto very much for spotting the method that disable auto Activity re-creation upon orientation changes. It is very useful. I am sure i will have to use it in some situations in the further.

For just avoiding Fragments re-creation upon screen rotates, i have found a simpler method that we can still allow the Activity re-creates as usual.

In 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);
}

and the isDevicePortrait() would be like:

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

*Notice that we cannot use getResources().getConfiguration().orientation to determine if the device is currently literally Portrait. It is because the Resources object is changed RIGHT AFTER the screen rotates - EVEN BEFORE onSaveInstanceState() is called!!

If you do not want to use findViewById() to test orientation (for any reasons, and it's not so neat afterall), keep a global variable private int current_orientation; and initialise it by current_orientation = getResources().getConfiguration().orientation; in onCreate(). This seems neater. But we should be aware not to change it anywhere during the Activity lifecycle.

*Be sure we remove_fragments() before super.onSaveInstanceState().

(Because in my case, i remove the Fragments from the Layout, and from the Activity. If it is after super.onSaveInstanceState(), the Layout will already be saved into the Bundle. Then the Fragments will also be re-created after the Activity re-creates. ###)

### I have proved this phenomenon. But the reason of What to determine a Fragment restore upon Activity re-create? is just by my guess. If you have any ideas about it, please answer my another question. Thanks!

这篇关于为避免再创造片段的屏幕旋转后的真实做法(官方片段开发指南为例)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆