【问题标题】:android fragment- How to save states of views in a fragment when another fragment is pushed on top of itandroid片段-当另一个片段被推到它上面时如何保存片段中的视图状态
【发布时间】:2011-10-10 20:27:50
【问题描述】:

在 android 中,一个片段(比如FragA)被添加到后台堆栈,另一个片段(比如FragB)到达顶部。现在回击FragA 到达顶部并调用onCreateView()。现在,FragAFragB 被推到它上面之前,我有一个特定的状态。

我的问题是如何将FragA 恢复到以前的状态?有没有办法保存状态(比如在 Bundle 中),如果有,我应该重写哪个方法?

【问题讨论】:

    标签: android android-fragments


    【解决方案1】:

    fragment guide FragmentList 示例中你可以找到:

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

    以后可以这样使用:

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (savedInstanceState != null) {
            // Restore last state for checked position.
            mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
        }
    }
    

    我是 Fragments 的初学者,但这似乎可以解决您的问题;) OnActivityCreated 在 Fragment 从回栈返回后被调用。

    【讨论】:

    • 我无法让它工作 savedInstanceState 始终为空。我正在通过 xml 布局添加片段。必须将 mCurCheckPosition 更改为 static 然后它才能工作,但感觉很hacky。
    • 它不调用 onSaveInstanceState - 为什么会这样?所以,这种方法行不通。
    • 如果我们想在从同一个 Activity 中的另一个片段返回时保留片段状态,这种方法真的有效吗? onSaveInstanceState() 仅在 Activity onPause/onStop 事件上调用。根据文档:“也像活动一样,您可以使用 Bundle 保留片段的状态,以防活动的进程被杀死并且您需要在重新创建活动时恢复片段状态。您可以在期间保存状态片段的 onSaveInstanceState() 回调并在 onCreate()、onCreateView() 或 onActivityCreated() 期间恢复它。"
    • 记录在案,这种方法是错误的,应该没有任何地方接近它所拥有的赞成票。 onSaveInstanceState 仅在其相应的活动也正在关闭时才被调用。
    • onSaveInstanceState() 仅在发生配置更改且活动被破坏时调用,此答案是错误的
    【解决方案2】:

    Fragment 的onSaveInstanceState(Bundle outState) 永远不会被调用,除非 Fragment 的活动在其自身和附加的 Fragment 上调用它。因此,在某些东西(通常是旋转)强制活动到SaveInstanceState 并稍后恢复它之前,不会调用此方法。 但是如果你只有一个activity并且里面有大量的fragment(大量使用replace)并且应用程序只运行在一个方向上activity的onSaveInstanceState(Bundle outState)可能很长时间都不会被调用。

    我知道三种可能的解决方法。

    第一个:

    使用片段的参数来保存重要数据:

    public class FragmentA extends Fragment {
        private static final String PERSISTENT_VARIABLE_BUNDLE_KEY = "persistentVariable";
    
        private EditText persistentVariableEdit;
    
        public FragmentA() {
            setArguments(new Bundle());
        }
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.fragment_a, null);
    
            persistentVariableEdit = (EditText) view.findViewById(R.id.editText);
    
            TextView proofTextView = (TextView) view.findViewById(R.id.textView);
    
            Bundle mySavedInstanceState = getArguments();
            String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY);
    
            proofTextView.setText(persistentVariable);
    
    
            view.findViewById(R.id.btnPushFragmentB).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    getFragmentManager()
                            .beginTransaction()
                            .replace(R.id.frameLayout, new FragmentB())
                            .addToBackStack(null)
                            .commit();
                }
            });
    
            return view;
        }
    
        @Override
        public void onPause() {
            super.onPause();
            String persistentVariable = persistentVariableEdit.getText().toString();
    
            getArguments().putString(PERSISTENT_VARIABLE_BUNDLE_KEY, persistentVariable);
        }
    }
    

    第二种但不那么迂腐的方式—— 在单例中保存变量

    第三个 - 不要 replace() 碎片,而是 add()/show()/hide() 它们。

    【讨论】:

    • 永远不会调用Fragment.onSaveInstanceState() 时的最佳解决方案。只需将您自己的数据保存到参数中,包括列表视图中的项目或仅它们的 ID(如果您有其他集中数据管理器)。无需保存列表视图的位置 - 已自动保存和恢复。
    • 我尝试在我的应用程序中使用您的示例,但是:String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY); 始终是null。有什么问题?
    • 使用片段的getArguments() 绝对是要走的路,包括 ViewPager 中的嵌套片段。我使用 1 个活动并交换了许多片段进/出,这非常有效。这是一个简单的测试,供您审查任何建议的解决方案:1)从片段 A 到片段 B; 2)两次改变设备方向; 3) 按设备上的返回按钮。
    • 我尝试了第一种方法,但它对我不起作用。 getArguments() 始终返回 null。这也是有意义的,因为片段被替换并且在 onCreate() 中设置了一个新的 Bundle,所以旧的 Bundle 丢失了。我错过了什么,我错了吗?
    • @Zvi,我在 1.5 年前写了这段代码,不记得所有细节,但我记得,replace 不会重新创建片段,只有当你从你的代码。在这种情况下,显然,构造函数调用和setArguments(new Bundle()); 覆盖旧的Bundle。所以确保你只创建了一次片段,然后使用这个实例而不是每次都创建新的。
    【解决方案3】:

    只需为您的视图充气一次。

    示例如下:

    public class AFragment extends Fragment {
    
    private View mRootView;
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        if(mRootView==null){
            mRootView = inflater.inflate(R.id.fragment_a, container, false);
            //......
        }
        return mRootView;
    }
    

    }

    【讨论】:

    • 还应该将现有片段保存在数组或其他东西中
    • 我听说保留对片段的 rootView 的引用是不好的做法——可能导致泄漏 [需要引用]?
    • @giraffe.guru Fragment 指的是它的根视图不会那样做。虽然某些 GC-root 元素 的引用会像全局静态属性一样,是非 ui 线程变量。 Fragment 实例不是 GC-root,因此它可以被垃圾回收。它的根视图也是如此。
    • 你和我一样。
    • 甜蜜又简单。
    【解决方案4】:

    请注意,如果您使用 ViewPager 处理 Fragments,则非常简单。你只需要调用这个方法:setOffscreenPageLimit()

    根据文档:

    设置在空闲状态下视图层次结构中当前页面任一侧应保留的页面数。超出此限制的页面将在需要时从适配器重新创建。

    Similar issue here

    【讨论】:

    • 这是不同的。 setOffScreenPageLimit 的作用类似于缓存(表示 ViewPager 在给定时刻必须处理多少页),但不用于保存 Fragment 的状态。
    • 在我的情况下,它与 setOffscreenPageLimit() 一起工作 - 虽然片段被破坏,但视图状态被保存和恢复。
    • 谢谢,也帮了我。
    • 多年后,这仍然很重要。虽然它并没有真正回答问题,但它解决了一个问题
    【解决方案5】:

    我曾处理过一个与此非常相似的问题。因为我知道我会经常返回到前一个片段,所以我检查了片段.isAdded() 是否为真,如果是,我不执行transaction.replace(),而是执行transaction.show()。如果片段已经在堆栈中,这可以防止重新创建片段 - 不需要保存状态。

    Fragment target = <my fragment>;
    FragmentTransaction transaction = getFragmentManager().beginTransaction();
    transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
    if(target.isAdded()) {
        transaction.show(target);
    } else {
        transaction.addToBackStack(button_id + "stack_item");
        transaction.replace(R.id.page_fragment, target);
    }
    transaction.commit();
    

    要记住的另一件事是,虽然这保留了片段本身的自然顺序,但您可能仍需要处理在方向(配置)更改时销毁和重新创建的活动本身。在 AndroidManifest.xml 中为您的节点解决此问题:

    android:configChanges="orientation|screenSize"
    

    在 Android 3.0 及更高版本中,screenSize 显然是必需的。

    祝你好运

    【讨论】:

    • transaction.addToBackStack(button_id + "stack_item");//这行是做什么的。这里的button_id是什么?
    • button_id 只是一个虚构的变量。传递给 addToBackStack 的字符串参数只是 backstack 状态的可选名称 - 如果您只管理单个 backstack,则可以将其设置为 null。
    • ,我遇到了类似的片段问题,请您查看一下-stackoverflow.com/questions/22468977/…
    • 永远不要在清单中添加android:configChanges=everythinYouCanThinkOf|moreThingsYouFoundOnTheInternets。而是学习如何保存和恢复状态,例如从这里:speakerdeck.com/cyrilmottier/…
    • 然后,一旦您了解了保存状态是多么疯狂笨拙,并且所提出的解决方案在您的特定情况下最干净地解决了问题,请继续使用它,而不必担心那些不同意的人的反对意见;-)
    【解决方案6】:

    如果您正在像这样处理 android manifest 中指定的片段活动中的配置更改

    <activity
        android:name=".courses.posts.EditPostActivity"
        android:configChanges="keyboardHidden|orientation"
        android:screenOrientation="unspecified" />
    

    那么片段的onSaveInstanceState 将不会被调用,savedInstanceState 对象将始终为空。

    【讨论】:

      【解决方案7】:

      我找到的最佳解决方案如下:

      onSavedInstanceState():总是在活动即将关闭时在片段内部调用(将活动从一个移动到另一个或配置更改)。 因此,如果我们在同一个活动上调用多个片段,那么我们必须使用以下方法:

      使用片段的 OnDestroyView() 并将整个对象保存在该方法中。 然后 OnActivityCreated():检查对象是否为空(因为这个方法每次都会调用)。现在在这里恢复一个对象的状态。

      它总是有效的!

      【讨论】:

        【解决方案8】:

        我不认为onSaveInstanceState 是一个好的解决方案。它仅用于已被破坏的活动。

        从android 3.0开始,Fragmen已经被FragmentManager管理,条件是:一个activity映射多个fragment,当fragment在backStack中被添加(不是replace:它会重新创建)时,视图会被销毁。回到最后一个时,它会像以前一样显示。

        所以我认为 fragmentManger 和 transaction 足以处理它。

        【讨论】:

          【解决方案9】:

          我对包含列表视图的片段使用了混合方法。它似乎很高效,因为我不替换当前片段,而是添加新片段并隐藏当前片段。我在托管我的片段的活动中有以下方法:

          public void addFragment(Fragment currentFragment, Fragment targetFragment, String tag) {
              FragmentManager fragmentManager = getSupportFragmentManager();
              FragmentTransaction transaction = fragmentManager.beginTransaction();
              transaction.setCustomAnimations(0,0,0,0);
              transaction.hide(currentFragment);
              // use a fragment tag, so that later on we can find the currently displayed fragment
              transaction.add(R.id.frame_layout, targetFragment, tag)
                      .addToBackStack(tag)
                      .commit();
          }
          

          每当单击/点击列表项(因此我需要启动/显示详细信息片段)时,我都会在我的片段(包含列表视图)中使用此方法:

          FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
          SearchFragment currentFragment = (SearchFragment) fragmentManager.findFragmentByTag(getFragmentTags()[0]);
          DetailsFragment detailsFragment = DetailsFragment.newInstance("some object containing some details");
          ((MainActivity) getActivity()).addFragment(currentFragment, detailsFragment, "Details");
          

          getFragmentTags() 返回一个字符串数组,当我添加新片段时,我将其用作不同片段的标签(请参阅上面addFragment 方法中的transaction.add 方法)。

          在包含列表视图的片段中,我在其 onPause() 方法中执行此操作:

          @Override
          public void onPause() {
              // keep the list view's state in memory ("save" it) 
              // before adding a new fragment or replacing current fragment with a new one
              ListView lv =  (ListView) getActivity().findViewById(R.id.listView);
              mListViewState = lv.onSaveInstanceState();
              super.onPause();
          }
          

          然后在fragment的onCreateView中(其实是在onCreateView中调用的一个方法中),我恢复状态:

          // Restore previous state (including selected item index and scroll position)
          if(mListViewState != null) {
              Log.d(TAG, "Restoring the listview's state.");
              lv.onRestoreInstanceState(mListViewState);
          }
          

          【讨论】:

            【解决方案10】:

            最后在尝试了许多这些复杂的解决方案后,因为我只需要在我的 Fragment 中保存/恢复一个值(EditText 的内容),虽然它可能不是最优雅的解决方案,但创建一个 SharedPreference 和在那里存储我的状态对我有用

            【讨论】:

              【解决方案11】:

              在一个活动中保持不同片段中字段值的简单方法

              创建片段实例并添加而不是替换和删除

                  FragA  fa= new FragA();
                  FragB  fb= new FragB();
                  FragC  fc= new FragB();
                  fragmentManager = getSupportFragmentManager();
                  fragmentTransaction = fragmentManager.beginTransaction();
                  fragmentTransaction.add(R.id.fragmnt_container, fa);
                  fragmentTransaction.add(R.id.fragmnt_container, fb);
                  fragmentTransaction.add(R.id.fragmnt_container, fc);
                  fragmentTransaction.show(fa);
                  fragmentTransaction.hide(fb);
                  fragmentTransaction.hide(fc);
                  fragmentTransaction.commit();
              

              然后只显示和隐藏片段,而不是再次添加和删除它们

                  fragmentTransaction = fragmentManager.beginTransaction();
                  fragmentTransaction.hide(fa);
                  fragmentTransaction.show(fb);
                  fragmentTransaction.hide(fc);
                  fragmentTransaction.commit()
              

              ;

              【讨论】:

                【解决方案12】:
                private ViewPager viewPager;
                viewPager = (ViewPager) findViewById(R.id.pager);
                mAdapter = new TabsPagerAdapter(getSupportFragmentManager());
                viewPager.setAdapter(mAdapter);
                viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
                
                        @Override
                        public void onPageSelected(int position) {
                            // on changing the page
                            // make respected tab selected
                            actionBar.setSelectedNavigationItem(position);
                        }
                
                        @Override
                        public void onPageScrolled(int arg0, float arg1, int arg2) {
                        }
                
                        @Override
                        public void onPageScrollStateChanged(int arg0) {
                        }
                    });
                }
                
                @Override
                public void onTabReselected(Tab tab, FragmentTransaction ft) {
                }
                
                @Override
                public void onTabSelected(Tab tab, FragmentTransaction ft) {
                    // on tab selected
                    // show respected fragment view
                    viewPager.setCurrentItem(tab.getPosition());
                }
                
                @Override
                public void onTabUnselected(Tab tab, FragmentTransaction ft) {
                }
                

                【讨论】:

                • 请考虑包含一些关于您的答案的信息,而不是简单地发布代码。我们试图提供的不仅仅是“修复”,而是帮助人们学习。您应该解释原始代码中的问题、您所做的不同之处以及您的更改为何有效。
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2023-03-08
                • 1970-01-01
                • 2018-08-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多