ViewPager和fragments-存储fragment状态的正确方式是什么?

9 浏览
0 Comments

ViewPager和fragments-存储fragment状态的正确方式是什么?

碎片似乎非常适合将UI逻辑分离成一些模块。但是与ViewPager一起使用时,它的生命周期对我来说仍然模糊不清。所以,我非常需要专家的意见!

编辑

请看下面的愚蠢解决方案;-)

范围

主活动有一个带有碎片的ViewPager。这些碎片可以为其他(子主要)活动实现略微不同的逻辑,因此通过活动内的回调接口填充碎片的数据。而且在第一次启动时一切都正常工作,但是!...

问题

当活动重新创建(例如方向更改)时,ViewPager的碎片也会重新创建。下面的代码(您将在下面找到)表示每次创建活动时,我都尝试创建一个与碎片相同的新的ViewPager碎片适配器(也许这就是问题所在),但是FragmentManager已经在某个地方存储了所有这些碎片(在哪里?)并且为这些碎片启动了重新创建机制。因此,重新创建机制调用了“旧”碎片的onAttach、onCreateView等方法,并通过活动的实现方法调用我的回调接口以初始化数据。但是,该方法指向通过活动的onCreate方法创建的新碎片。

问题

也许我使用了错误的模式,但是即使是《Android 3 Pro》书中也没有太多相关内容。所以,请给我一个或两个建议,指出如何正确地做。非常感谢!

代码

主活动

public class DashboardActivity extends BasePagerActivity implements OnMessageListActionListener {
private MessagesFragment mMessagesFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);
    setContentView(R.layout.viewpager_container);
    new DefaultToolbar(this);
    // create fragments to use
    mMessagesFragment = new MessagesFragment();
    mStreamsFragment = new StreamsFragment();
    // set titles and fragments for view pager
    Map screens = new LinkedHashMap();
    screens.put(getApplicationContext().getString(R.string.dashboard_title_dumb), new DumbFragment());
    screens.put(getApplicationContext().getString(R.string.dashboard_title_messages), mMessagesFragment);
    // instantiate view pager via adapter
    mPager = (ViewPager) findViewById(R.id.viewpager_pager);
    mPagerAdapter = new BasePagerAdapter(screens, getSupportFragmentManager());
    mPager.setAdapter(mPagerAdapter);
    // set title indicator
    TitlePageIndicator indicator = (TitlePageIndicator) findViewById(R.id.viewpager_titles);
    indicator.setViewPager(mPager, 1);
}
/* set of fragments callback interface implementations */
@Override
public void onMessageInitialisation() {
    Logger.d("Dash onMessageInitialisation");
    if (mMessagesFragment != null)
        mMessagesFragment.loadLastMessages();
}
@Override
public void onMessageSelected(Message selectedMessage) {
    Intent intent = new Intent(this, StreamActivity.class);
    intent.putExtra(Message.class.getName(), selectedMessage);
    startActivity(intent);
}

BasePagerActivity aka helper

public class BasePagerActivity extends FragmentActivity {
BasePagerAdapter mPagerAdapter;
ViewPager mPager;
}

Adapter

public class BasePagerAdapter extends FragmentPagerAdapter implements TitleProvider {
private Map mScreens;
public BasePagerAdapter(Map screenMap, FragmentManager fm) {
    super(fm);
    this.mScreens = screenMap;
}
@Override
public Fragment getItem(int position) {
    return mScreens.values().toArray(new Fragment[mScreens.size()])[position];
}
@Override
public int getCount() {
    return mScreens.size();
}
@Override
public String getTitle(int position) {
    return mScreens.keySet().toArray(new String[mScreens.size()])[position];
}
// hack. we don't want to destroy our fragments and re-initiate them after
@Override
public void destroyItem(View container, int position, Object object) {
    // TODO Auto-generated method stub
}
}

Fragment

public class MessagesFragment extends ListFragment {
private boolean mIsLastMessages;
private List mMessagesList;
private MessageArrayAdapter mAdapter;
private LoadMessagesTask mLoadMessagesTask;
private OnMessageListActionListener mListener;
// define callback interface
public interface OnMessageListActionListener {
    public void onMessageInitialisation();
    public void onMessageSelected(Message selectedMessage);
}
@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    // setting callback
    mListener = (OnMessageListActionListener) activity;
    mIsLastMessages = activity instanceof DashboardActivity;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    inflater.inflate(R.layout.fragment_listview, container);
    mProgressView = inflater.inflate(R.layout.listrow_progress, null);
    mEmptyView = inflater.inflate(R.layout.fragment_nodata, null);
    return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    // instantiate loading task
    mLoadMessagesTask = new LoadMessagesTask();
    // instantiate list of messages
    mMessagesList = new ArrayList();
    mAdapter = new MessageArrayAdapter(getActivity(), mMessagesList);
    setListAdapter(mAdapter);
}
@Override
public void onResume() {
    mListener.onMessageInitialisation();
    super.onResume();
}
public void onListItemClick(ListView l, View v, int position, long id) {
    Message selectedMessage = (Message) getListAdapter().getItem(position);
    mListener.onMessageSelected(selectedMessage);
    super.onListItemClick(l, v, position, id);
}
/* public methods to load messages from host acitivity, etc... */
}

解决方案

愚蠢的解决方案是在保存实例状态(宿主活动的onSaveInstanceState方法中)使用putFragment保存碎片,并在onCreate中使用getFragment获取它们。但是我仍然有一种奇怪的感觉,似乎事情不应该这样工作...请参见下面的代码:

    @Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    getSupportFragmentManager()
            .putFragment(outState, MessagesFragment.class.getName(), mMessagesFragment);
}
protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);
    ...
    // create fragments to use
    if (savedInstanceState != null) {
        mMessagesFragment = (MessagesFragment) getSupportFragmentManager().getFragment(
                savedInstanceState, MessagesFragment.class.getName());
                StreamsFragment.class.getName());
    }
    if (mMessagesFragment == null)
        mMessagesFragment = new MessagesFragment();
    ...
}

0
0 Comments

ViewPager和fragment - 存储fragment状态的正确方法是什么?

问题的出现的原因是,使用FragmentPagerAdapter时,fragment是由FragmentManager管理的,并且存储在FragmentManager下的标签中。这个标签是使用以下方式生成的:String tag = "android:switcher:" + viewId + ":" + index;其中,viewId是container.getId(),而container是ViewPager的实例,index是fragment的位置。因此,可以将对象id保存到outState中,以便在需要时恢复。

解决方法是,在onSaveInstanceState方法中,将ViewPager的id保存到outState中,并在onCreate方法中根据savedInstanceState恢复viewpagerid。然后,根据viewpagerid可以通过FragmentManager来获取fragment。但是需要注意的是,这个方法依赖于内部的tag命名约定,所以并不是一个好的解决方法。

相反,可以尝试使用不依赖于内部tag的解决方法。具体可以参考这个答案

0
0 Comments

ViewPager和片段——存储片段状态的正确方法是什么?

在这个问题中,出现了两个主要的原因。首先,使用FragmentPagerAdapter或FragmentStatePagerAdapter时,无法直接访问已创建的片段实例。其次,当屏幕旋转或Activity和Adapter恢复时,需要能够获取已存在的片段实例。

为了解决这个问题,可以通过重写FragmentPagerAdapter或FragmentStatePagerAdapter的instantiateItem方法来保存对已创建片段的引用。通过保存这些引用,可以在以后对它们进行操作。以下是解决方法的示例代码:

public class SomeActivity extends Activity {
    private FragmentA m1stFragment;
    private FragmentB m2ndFragment;
    private class CustomPagerAdapter extends FragmentPagerAdapter {
        public CustomPagerAdapter(FragmentManager fm) {
            super(fm);
        }
        public Fragment getItem(int position) {
            // 返回相应位置的片段实例
        }
        public Object instantiateItem(ViewGroup container, int position) {
            Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
            // 根据位置保存对应的片段实例引用
            return createdFragment;
        }
    }
    public void someMethod() {
        // 检查片段实例是否存在,然后对其进行操作
    }
}

另一种解决方法是通过获取FragmentPagerAdapter设置的标签来保存对片段的引用。以下是代码示例:

public Object instantiateItem(ViewGroup container, int position) {
    Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
    // 获取FragmentPagerAdapter设置的标签
    // ...保存标签以供以后引用
    return createdFragment;
}

需要注意的是,这种方法不依赖于模拟FragmentPagerAdapter设置的内部标签,而是使用适当的API来检索它们。这样,即使在SupportLibrary的未来版本中,标签发生变化,你仍然是安全的。

另外,记住根据Activity的设计,你要操作的片段可能还不存在,所以在使用引用之前,需要进行空值检查。

如果你使用的是FragmentStatePagerAdapter,那么你不应该保留对片段的硬引用,因为可能会有很多片段,硬引用会不必要地使它们保留在内存中。而是应该将片段引用保存在WeakReference变量中。以下是示例代码:

WeakReference m1stFragment = new WeakReference(createdFragment);
// ...然后像这样访问它们
Fragment firstFragment = m1stFragment.get();
if (firstFragment != null) {
    // 引用尚未被清除;进行操作...
}

总之,重写instantiateItem方法是解决这个问题的正确方法。这样做可以帮助处理屏幕旋转,以及在Activity和Adapter恢复时检索现有的片段实例。通过保存对片段的引用,可以在以后对它们进行操作。这种方法不依赖于模拟FragmentPagerAdapter设置的内部标签,而是使用适当的API来检索它们。

0
0 Comments

ViewPager和片段 - 存储片段状态的正确方法

当FragmentPagerAdapter将片段添加到FragmentManager时,它使用基于片段将被放置的特定位置的特殊标记。仅当该位置的片段不存在时,才会调用FragmentPagerAdapter.getItem(int位置)。旋转后,Android会注意到它已经为该特定位置创建/保存了一个片段,因此它只是尝试重新连接它与FragmentManager.findFragmentByTag()而不是创建一个新的。当使用FragmentPagerAdapter时,所有这些都是免费的,这就是为什么在getItem(int)方法中放置片段初始化代码的常见方式。

即使我们不使用FragmentPagerAdapter,在Activity.onCreate(Bundle)中每次都创建一个新片段也不是一个好主意。正如你所注意到的,当片段添加到FragmentManager时,它将在旋转后为您重新创建,没有必要再次添加它。这样做是使用片段时常见的错误原因之一。

在使用片段时,通常的方法是这样的:

protected void onCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

...

CustomFragment fragment;

if(savedInstanceState!= null){

fragment =(CustomFragment)getSupportFragmentManager()。findFragmentByTag(“customtag”);

} else {

fragment = new CustomFragment();

getSupportFragmentManager()。beginTransaction()。add(R.id.container,fragment,“customtag”)。commit();

}

...

}

当使用FragmentPagerAdapter时,我们将片段管理交给适配器,不需要执行上述步骤。默认情况下,它只会预加载当前位置前后的一个片段(除非使用FragmentStatePagerAdapter,否则不会销毁它们)。这由ViewPager.setOffscreenPageLimit(int)控制。因此,直接在适配器之外调用片段的方法不能保证有效,因为它们可能甚至不存在。

长话短说,你使用putFragment的解决方案以便以后能够获得一个引用并不是那么疯狂,也不像通常使用片段的方式(上面)。否则很难获得引用,因为片段是由适配器添加的,而不是你个人添加的。只需确保offscreenPageLimit足够高以始终加载所需的片段,因为您依赖它的存在。这绕过了ViewPager的延迟加载功能,但似乎是您的应用程序所需要的。

另一种方法是覆盖FragmentPageAdapter.instantiateItem(View,int)并在super调用返回碎片之前保存对返回的碎片的引用(如果已经存在,则具有查找碎片的逻辑)。

为了更全面地了解情况,请查看FragmentPagerAdapter(短)和ViewPager(长)的一些源代码。

最后一部分很喜欢。为片段创建了一个缓存,并将缓存逻辑放在了FragmentPageAdapter.instantiateItem(View,int)中。最后解决了一个长期存在的问题,该问题只在旋转/配置更改时出现,让我发疯...

这解决了我在旋转更改上遇到的问题。也许我只是看不见,但是这有没有记录?即使用标记从先前状态中恢复?这可能是一个明显的答案,但我对Android开发还相当新。

在我的情况下,我总是将OffscreenPageLimit设置为3,并且这是我单个PagerAdapter拥有的最大片段。我只是想知道这是否消耗更多RAM...有时在SherlockFragment中使用getSherlockActivity()时仍会出现NPE。

抑郁症 - 这是要添加片段的容器的ID(例如FrameLayout)。

顺便说一句,对我来说,是FragmentPageAdapter.instantiateItem(ViewGroup,int),而不是FragmentPageAdapter.instantiateItem(View,int)。

顺便问一下,当片段从屏幕上滑出时,是否知道使用哪个(如果有)片段生命周期方法?是onDetach(),还是其他什么?

我想我找到了答案:没有!在PageViewer上滑动两个面板之间时,不会调用任何生命周期回调。正在滑动之间的两个片段“已恢复”。真是个让人沮丧的事情!你知道还有其他的回调可以指示该转换吗?

您可以在ViewPager上设置一个OnPageChangeListener来监听页面更改。您还可以重写Fragment.setUserVisibleHint(每当片段在ViewPager中可见时,使用“true”调用此方法,在消失时使用“false”)。

在我的情况下,我有一个托管ViewPager和FragmentPageAdapter的片段。这个片段用addToBackStack()替换了先前的片段。第一次启动时,我可以看到子片段的布局,即在ViewPager内部。我按返回键,然后再次启动此片段,我看不到任何布局,直到滑动到末尾,然后返回到第一页。我能看到的是,即使片段被移除,适配器仍然没有释放引用。有任何想法/帮助吗?

你能看一下关于ViewPager的这个问题吗?谢谢

关于覆盖FragmentPageAdapter.instantiateItem(ViewGroup,int)的部分值得单独一个标题或其他东西,因为它非常有帮助。如果您需要保存对返回的Fragments的引用,这是一个完美的位置。您还可以在此方法中获取对Fragment的标记(FragmentPagerAdapter自动创建的标记)的引用。

我想知道是否覆盖FragmentPageAdapter.instantiateItem(ViewGroup,int)的部分可能是个坏主意。原则上,当活动停止时,片段可能随时被销毁,并在需要时重新创建,而ViewPagerAdapter和活动仍然存在。我认为无法保证这不会失败,如果现在不是,也许在将来的Android版本中。

如果我使用一个使用ViewPager和FragmentPageAdapter的库,并且无法访问它们,我在我的片段中有什么可以做的,以确保在方向更改后恢复状态?

0