FragmentStatePagerAdapter在屏幕旋转时保存片段状态吗?

5
我有3个片段需要放在ViewPager中。这些片段将保存从数据库检索到的动态信息。我知道在方向更改时,活动和片段会被销毁并重新创建。但是,根据其名称,我认为FragmentStatePagerAdapter将保存片段的状态。显然,我错了,因为每次我对片段进行操作,然后更改方向,片段都会恢复到在布局xml文件中的布局方式。

当我调试时,我注意到在方向更改时,适配器的getItem()方法从未被调用 - 这意味着它没有被重新创建。那么,为什么片段状态会恢复到其原始状态呢?

我如何使用FragmentStatePagerAdapter保存片段状态?

请注意,我一直在遵循此tutorial ,并使用他们的SmartFragmentStatePagerAdapter.java类来动态管理片段。

以下是我的示例代码。

PageLoader.java - 此接口允许MainActivity在运行时动态管理加载片段页面。

public interface PageLoader {
    void loadPage(int from, int target);
}

MainActivity.java

public class MainActivity extends AppCompatActivity implements PageLoader {
    MyPagerAdapter adapter;
    DirectionalViewPager pager;

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

        // Get the ViewPager and set it's PagerAdapter so that it can display items
        pager = (DirectionalViewPager) findViewById(R.id.vpPager);
        adapter = new MyPagerAdapter(getSupportFragmentManager());
        pager.setOffscreenPageLimit(5);
        pager.setAdapter(adapter);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int position = pager.getCurrentItem();
        if (position > 0) pager.setCurrentItem(position - 1);
        return true;
    }

    @Override
    public void loadPage(int from, int target) {
        PageLoader fragment = (PageLoader) adapter.getRegisteredFragment(target);
        fragment.loadPage(from, target);
    }
}

MyPagerAdapter.java

public class MyPagerAdapter extends SmartFragmentStatePagerAdapter {
    private static final int NUM_ITEMS = 4;

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

    // Returns total number of pages
    @Override
    public int getCount() {
        return NUM_ITEMS;
    }

    // Returns the fragment to display for that page
    @Override
    public Fragment getItem(int position) {
        switch (position) {
            case 0: // Fragment # 0 - This will show Frag1
                return Frag1.newInstance(position, Frag1.class.getSimpleName());
            case 1: // Fragment # 0 - This will show Frag1 different title
                return Frag1.newInstance(position, Frag1.class.getSimpleName());
            case 2: // Fragment # 1 - This will show Frag2
                return Frag2.newInstance(position, Frag2.class.getSimpleName());
            default:
                return Frag3.newInstance(position, Frag3.class.getSimpleName());
        }
    }

    // Returns the page title for the top indicator
    @Override
    public CharSequence getPageTitle(int position) {
        return "Page " + position;

    }
}

Frag1.java Frag2.java Frag3.java - 这些文件都是一样的,除了编号不同。

public class Frag1 extends Fragment implements PageLoader {
    // Store instance variables
    private String title;
    private int page;
    private TextView txtView;

    // newInstance constructor for creating fragment with arguments
    public static Frag1 newInstance(int page, String title) {
        Frag1 fragmentFirst = new Frag1();
        Bundle args = new Bundle();
        args.putInt("someInt", page);
        args.putString("someTitle", title);
        fragmentFirst.setArguments(args);
        return fragmentFirst;
    }

    // Store instance variables based on arguments passed
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        page = getArguments().getInt("someInt", 0);
        title = getArguments().getString("someTitle");
    }

    // Inflate the view for the fragment based on layout XML
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.frag1, container, false);
        txtView = (TextView) view.findViewById(R.id.txt_frag1);
        txtView.setText(page + " - " + title);

        Button btn = (Button) view.findViewById(R.id.btn_frag1);
        btn.setOnClickListener( new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                PageLoader activity = (PageLoader) getActivity();
                activity.loadPage(page, page+1);

            }
        });
        return view;
    }

    @Override
    public void loadPage(int from, int target) {
        txtView.setText(txtView.getText() + "\nThis message was created from" + from + " to " + target);
    }
}

activity_main.xml

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

    <com.example.someone.smartfragmentstatepageradapter.custom.DirectionalViewPager
        android:id="@+id/vpPager"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    </com.example.someone.smartfragmentstatepageradapter.custom.DirectionalViewPager>
</LinearLayout>

frag1.xml frag2.xml frag3.xml - 这些文件除了编号不同之外,其余都相同。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#cc2">

    <TextView
        android:id="@+id/txt_frag1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="txt_frag1"
        />
    <Button
        android:id="@+id/btn_frag1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="btn_frag1"
        android:textSize="26dp" />

</LinearLayout>

请告诉我如何使用FragmentStatePagerAdapter保存我的片段的“状态”。我今天从早上9点到晚上9点一直在搜索互联网...已经12个小时了...我真的需要一些帮助来解决这个问题。提前致谢!

1个回答

3

尝试这样做:

向您的片段添加另一个实例变量:

    private String text;   // this is part of saved state

loadPage中设置这个变量:

    @Override
    public void loadPage(int from, int target) {
        text = txtView.getText().toString() + "\nThis message was created from" + from + " to " + target;
        txtView.setText(text);
    }

重写onSaveInstanceState方法以保存该变量:

    @Override
    public void onSaveInstanceState(Bundle outState);

        outState.putString("text", text);
        super.onSaveInstanceState(outState);
    }

然后使用这个变量来恢复 TextView 的状态:

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

        if (savedInstanceState != null) {
            // not null means we are restoring the fragment
            text = savedInstanceState.getString("text");
        } else {
            text = "" + page + " - " + title;
        }
        View view = inflater.inflate(R.layout.frag1, container, false);
        txtView = (TextView) view.findViewById(R.id.txt_frag1);
        txtView.setText(text);

        Button btn = (Button) view.findViewById(R.id.btn_frag1);
        btn.setOnClickListener( new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                PageLoader activity = (PageLoader) getActivity();
                activity.loadPage(page, page+1);

            }
        });
        return view;
    }

任何时候,如果您希望在片段中的某些内容保持不变,例如配置更改时,这就是您需要跟踪状态并保存和恢复它的方法。

这是在片段和活动中使用onSaveInstanceState的位置。

这是您将覆盖以保存任何必要状态的方法。您在片段中更改的任何内容,如果希望在配置更改时重新创建,则必须保存并在onCreateonCreateView期间进行恢复。

因此,如果您尝试恢复在loadPage中创建的文本,则应创建一个类级别的字符串以存储文本,在loadPage中设置它,在onSaveInstanceState重写中保存它,然后从savedInstanceState参数中在onCreateView中恢复它。

现在的问题是:您会注意到在配置更改后,适配器上的getItem未被调用。但是您是否注意到您的片段仍然存在(即使它不是您离开时的状态)?请记住,活动有一个FragmentManager来管理片段及其事务。当活动进行配置更改时,它会保存其状态。FragmentManager和所有活动片段都是该状态的一部分。然后以这样一种方式恢复片段,即不调用adapter.getItem。
结果发现,SmartFragmentPagerAdapter并不那么聪明。它无法在配置更改后重新创建其registeredFragments数组,因此实际上并不是非常有用。我建议您不要使用它。
那么,当ViewPager将片段标签用于自己的用途时,如何向离屏片段发送事件呢?
我使用的技术是定义事件监听器接口,并让片段作为侦听器在活动中注册。当我触发事件时,是通过调用活动上的方法来通知其活动侦听器。我在this answer中提供了一个相当完整的示例。

“FragmentManager和所有活动的片段都是该状态的一部分。然后以这样的方式恢复片段,以便不调用adapter.getItem。” - 你能澄清一下吗?如果所有活动的片段都是该状态的一部分,那么为什么片段状态不会随着活动一起恢复呢? - chaser
进一步阅读:https://inthecheesefactory.com/blog/fragment-state-saving-best-practices/en - 我引用:“但是,由Fragment的生命周期创建的那些View会发生什么?没问题。Android就是这样设计的。在这种情况下,在Fragment内部调用了View状态保存/恢复。”因此,他们认为片段中的所有视图都应该能够恢复自己的状态...当片段在ViewPager中时显然不是这种情况?我真的很迷茫。 - chaser
请查看我的更新答案。正如之前提到的,保存/恢复是在内部调用的,但是您仍然需要告诉片段要保存和恢复什么。他们提供了框架,但您仍然需要跟踪状态、保存状态,然后使用保存的状态重新创建视图。希望我的代码示例能够说明这一点。 - kris larson
我猜我感到困惑是因为活动中的某些视图可以保留其状态(例如edittext)...我想知道它们在片段中是否应该具有类似的行为。这是另一个问题,所以我可能需要再做一些研究并将其作为另一个问题提出。但是谢谢你的解决方案确实解决了我的问题。因此,为了有效地保留片段中的视图状态,我需要跟踪所有更改作为成员属性,并在onSavedInstanceState和onCreateView期间进行回调? - chaser
基本上是这样的。这很令人困惑,因为平台将对哪些状态负责并没有清晰的文档记录,例如您所提到的EditTextTextView。很多东西只能通过经验来学习。而且要弄清楚您的片段中哪些属性/变量属于需要持久化的状态也不是一件简单的事情。这只是设计Android应用所需付出的努力的一部分。 - kris larson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接