片段似乎非常适合将UI逻辑分离到一些模块中。但是ViewPager的生命周期对我来说仍然是模糊的。所以我们非常需要大师的思想!

Edit

参见下面的哑巴解决方案;-)

范围

主活动有一个带有片段的ViewPager。这些片段可以为其他(子主)活动实现稍微不同的逻辑,因此片段的数据是通过活动内部的回调接口填充的。第一次发射时一切正常,但是……

问题

When the activity gets recreated (e.g. on orientation change) so do the ViewPager's fragments. The code (you'll find below) says that every time the activity is created I try to create a new ViewPager fragments adapter the same as fragments (maybe this is the problem) but FragmentManager already has all these fragments stored somewhere (where?) and starts the recreation mechanism for those. So the recreation mechanism calls the "old" fragment's onAttach, onCreateView, etc. with my callback interface call for initiating data via the Activity's implemented method. But this method points to the newly created fragment which is created via the Activity's onCreate method.

问题

也许我使用了错误的模式,但即使是Android 3 Pro书也没有太多关于它的内容。所以,请给我一记重拳告诉我怎么做才是正确的。很多谢谢!

Code

主要活动

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<String, Fragment> screens = new LinkedHashMap<String, Fragment>();
    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又名助手

public class BasePagerActivity extends FragmentActivity {

BasePagerAdapter mPagerAdapter;
ViewPager mPager;
}

适配器

public class BasePagerAdapter extends FragmentPagerAdapter implements TitleProvider {

private Map<String, Fragment> mScreens;

public BasePagerAdapter(Map<String, Fragment> 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
}

}

片段

public class MessagesFragment extends ListFragment {

private boolean mIsLastMessages;

private List<Message> 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<Message>();
    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();
    ...
}

当前回答

我想提供一个解决方案,扩展antonyt的精彩答案,并提到重写FragmentPageAdapter。instantiateItem(View, int)来保存对已创建片段的引用,以便稍后对它们进行处理。这应该也适用于FragmentStatePagerAdapter;详情见注释。


下面是一个简单的例子,说明如何获得FragmentPagerAdapter返回的fragment的引用,它不依赖于在Fragments上设置的内部标记。关键是重写instantiateItem()并将引用保存在那里,而不是保存在getItem()中。

public class SomeActivity extends Activity {
    private FragmentA m1stFragment;
    private FragmentB m2ndFragment;

    // other code in your Activity...

    private class CustomPagerAdapter extends FragmentPagerAdapter {
        // other code in your custom FragmentPagerAdapter...

        public CustomPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            // Do NOT try to save references to the Fragments in getItem(),
            // because getItem() is not always called. If the Fragment
            // was already created then it will be retrieved from the FragmentManger
            // and not here (i.e. getItem() won't be called again).
            switch (position) {
                case 0:
                    return new FragmentA();
                case 1:
                    return new FragmentB();
                default:
                    // This should never happen. Always account for each position above
                    return null;
            }
        }

        // Here we can finally safely save a reference to the created
        // Fragment, no matter where it came from (either getItem() or
        // FragmentManger). Simply save the returned Fragment from
        // super.instantiateItem() into an appropriate reference depending
        // on the ViewPager position.
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
            // save the appropriate reference depending on position
            switch (position) {
                case 0:
                    m1stFragment = (FragmentA) createdFragment;
                    break;
                case 1:
                    m2ndFragment = (FragmentB) createdFragment;
                    break;
            }
            return createdFragment;
        }
    }

    public void someMethod() {
        // do work on the referenced Fragments, but first check if they
        // even exist yet, otherwise you'll get an NPE.

        if (m1stFragment != null) {
            // m1stFragment.doWork();
        }

        if (m2ndFragment != null) {
            // m2ndFragment.doSomeWorkToo();
        }
    }
}

或者如果你更喜欢使用标签而不是类成员变量/片段引用,你也可以用同样的方式获取FragmentPagerAdapter设置的标签: 注意:这并不适用于FragmentStatePagerAdapter,因为它在创建fragment时不设置标签。

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
    // get the tags set by FragmentPagerAdapter
    switch (position) {
        case 0:
            String firstTag = createdFragment.getTag();
            break;
        case 1:
            String secondTag = createdFragment.getTag();
            break;
    }
    // ... save the tags somewhere so you can reference them later
    return createdFragment;
}

注意,这个方法并不依赖于模仿FragmentPagerAdapter设置的内部标记,而是使用适当的api来检索它们。这样,即使标签在未来的SupportLibrary版本中发生了变化,您仍然是安全的。


不要忘记,根据Activity的设计,你要处理的Fragments可能存在,也可能不存在,所以你必须在使用引用之前做空检查。

同样,如果你正在使用FragmentStatePagerAdapter,那么你不希望保留对片段的硬引用,因为你可能有很多片段,而硬引用将不必要地将它们保存在内存中。将Fragment引用保存在WeakReference变量中,而不是保存在标准变量中。是这样的:

WeakReference<Fragment> m1stFragment = new WeakReference<Fragment>(createdFragment);
// ...and access them like so
Fragment firstFragment = m1stFragment.get();
if (firstFragment != null) {
    // reference hasn't been cleared yet; do work...
}

其他回答

BasePagerAdapter是什么?你应该使用一个标准的寻页适配器——FragmentPagerAdapter或FragmentStatePagerAdapter,这取决于你是否想要ViewPager不再需要的片段被保留(前者)或保存它们的状态(后者)并在再次需要时重新创建。

使用ViewPager的示例代码可以在这里找到

的确,跨活动实例管理视图寻呼机中的片段有点复杂,因为框架中的FragmentManager负责保存状态并恢复寻呼机所生成的任何活动片段。这实际上意味着,适配器在初始化时需要确保它与任何恢复的片段重新连接。您可以查看FragmentPagerAdapter或FragmentStatePagerAdapter的代码,以了解这是如何实现的。

对于你的问题,我找到了另一个相对简单的解决方法。

正如你从FragmentPagerAdapter源代码中看到的,由FragmentPagerAdapter管理的片段存储在FragmentManager的标签下,使用:

String tag="android:switcher:" + viewId + ":" + index;

viewId是container. getid(),容器是你的ViewPager实例。索引是片段的位置。因此,你可以保存对象id到outState:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("viewpagerid" , mViewPager.getId() );
}

@Override
    protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_main);
    if (savedInstanceState != null)
        viewpagerid=savedInstanceState.getInt("viewpagerid", -1 );  

    MyFragmentPagerAdapter titleAdapter = new MyFragmentPagerAdapter (getSupportFragmentManager() , this);        
    mViewPager = (ViewPager) findViewById(R.id.pager);
    if (viewpagerid != -1 ){
        mViewPager.setId(viewpagerid);
    }else{
        viewpagerid=mViewPager.getId();
    }
    mViewPager.setAdapter(titleAdapter);

如果你想与这个片段通信,你可以从FragmentManager中获取,比如:

getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewpagerid + ":0")

我想为一个可能略有不同的情况提供一个替代解决方案,因为我对答案的许多搜索一直将我引向这个主题。

我的情况 -我动态创建/添加页面,并将它们滑动到ViewPager,但当旋转(onConfigurationChange),我最终会有一个新页面,因为当然OnCreate再次被调用。但是我想保持对旋转之前创建的所有页面的引用。

问题 -我没有为我创建的每个片段提供唯一的标识符,所以引用的唯一方法是以某种方式将引用存储在数组中,以便在旋转/配置更改后恢复。

解决方案 -关键的概念是让Activity(显示片段)也管理现有片段的引用数组,因为这个Activity可以利用onSaveInstanceState中的Bundles

public class MainActivity extends FragmentActivity

因此,在这个Activity中,我声明了一个私有成员来跟踪打开的页面

private List<Fragment> retainedPages = new ArrayList<Fragment>();

每次onSaveInstanceState被调用时都会更新,并在onCreate中恢复

@Override
protected void onSaveInstanceState(Bundle outState) {
    retainedPages = _adapter.exportList();
    outState.putSerializable("retainedPages", (Serializable) retainedPages);
    super.onSaveInstanceState(outState);
}

...所以一旦它被存储,它就可以被检索…

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

    if (savedInstanceState != null) {
        retainedPages = (List<Fragment>) savedInstanceState.getSerializable("retainedPages");
    }
    _mViewPager = (CustomViewPager) findViewById(R.id.viewPager);
    _adapter = new ViewPagerAdapter(getApplicationContext(), getSupportFragmentManager());
    if (retainedPages.size() > 0) {
        _adapter.importList(retainedPages);
    }
    _mViewPager.setAdapter(_adapter);
    _mViewPager.setCurrentItem(_adapter.getCount()-1);
}

这些是对主活动的必要更改,因此我需要FragmentPagerAdapter中的成员和方法来工作,因此在

public class ViewPagerAdapter extends FragmentPagerAdapter

一个相同的构造(如上面的MainActivity中所示)

private List<Fragment> _pages = new ArrayList<Fragment>();

这种同步(如上面在onSaveInstanceState中使用的那样)是由方法特别支持的

public List<Fragment> exportList() {
    return _pages;
}

public void importList(List<Fragment> savedPages) {
    _pages = savedPages;
}

最后,在fragment类中

public class CustomFragment extends Fragment

为了使这一切顺利进行,有两个改变,首先

public class CustomFragment extends Fragment implements Serializable

然后将其添加到onCreate,这样Fragments就不会被破坏

setRetainInstance(true);

我仍然在思考Fragments和Android生命周期,所以这里需要注意的是,这种方法可能存在冗余/低效率。但它对我有用,我希望它能对其他有类似情况的人有所帮助。

我想出了这个简单而优雅的解决方案。它假设活动负责创建片段,而适配器只是为它们服务。

这是适配器的代码(这里没有什么奇怪的,除了mFragments是由Activity维护的片段列表)

class MyFragmentPagerAdapter extends FragmentStatePagerAdapter {

    public MyFragmentPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        return mFragments.get(position);
    }

    @Override
    public int getCount() {
        return mFragments.size();
    }

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        TabFragment fragment = (TabFragment)mFragments.get(position);
        return fragment.getTitle();
    }
} 

这个线程的整个问题是获得“旧”片段的引用,所以我在活动的onCreate中使用这段代码。

    if (savedInstanceState!=null) {
        if (getSupportFragmentManager().getFragments()!=null) {
            for (Fragment fragment : getSupportFragmentManager().getFragments()) {
                mFragments.add(fragment);
            }
        }
    }

当然,如果需要,您还可以进一步优化此代码,例如确保片段是特定类的实例。

我的解决方案是非常粗鲁的,但工作:作为我的片段动态创建从保留的数据,我只是删除所有片段从PageAdapter之前调用super.onSaveInstanceState(),然后重新创建他们的活动创建:

@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putInt("viewpagerpos", mViewPager.getCurrentItem() );
    mSectionsPagerAdapter.removeAllfragments();
    super.onSaveInstanceState(outState);
}

你不能在onDestroy()中删除它们,否则你会得到这个异常:

java.lang.IllegalStateException: onSaveInstanceState后不能执行此操作

下面是页面适配器中的代码:

public void removeAllfragments()
{
    if ( mFragmentList != null ) {
        for ( Fragment fragment : mFragmentList ) {
            mFm.beginTransaction().remove(fragment).commit();
        }
        mFragmentList.clear();
        notifyDataSetChanged();
    }
}

我只保存当前页面并在onCreate()中恢复它,在片段创建之后。

if (savedInstanceState != null)
    mViewPager.setCurrentItem( savedInstanceState.getInt("viewpagerpos", 0 ) );