片段似乎是非常好的UI界面逻辑分成一些模块。但是随着ViewPager它的生命周期对我来说还是朦胧的。所以大师的想法是非常需要的!
编辑
看到下面的愚蠢的解决方案;-)
范围
主要活动有一个ViewPager片段。这些片段可以为其他(子域)活动实现一些不同的逻辑,所以片段的数据通过活动内的回调接口填充。而一切正常的第一次发射,但!!
问题
当活动被重新创建(例如在方向改变时),所以做ViewPager的碎片。代码(你会发现下面)说,每当创建活动,我尝试创建一个新的ViewPager片段适配器相同的片段(也许这是问题),但FragmentManager已经有所有这些片段存储的地方(?)和启动这些娱乐机制。因此,娱乐机制调用onAttach,onCreateView等“旧”片段与我的回调接口调用启动数据通过活动的实施方法。但是这个方法指向通过Activity的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 page
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 adapte
mPager = (ViewPager) findViewById(R.id.viewpager_pager);
mPagerAdapter = new BasePagerAdapter(screens, getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
// set title indicato
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 afte
@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... */
}
方案
愚蠢的解决方案是用putFragment将片段保存在onSaveInstanceState(主机Activity)中,并通过getFragment将它们放入onCreate中。但我仍然有一种奇怪的感觉,事情不应该这样...请参阅下面的代码:
@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)保存到创建的引用Fragments,所以你可以对他们以后做的工作。这也应该与FragmentStatePagerAdapter; 请参阅说明的细节。
下面是一个简单的例子,如何得到一个不依赖于内部设置的Fragments返回的引用。关键是重写和保存在那里的引用,而不是在。
FragmentPagerAdaptertagsFragmentsinstantiateItem()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 FragmentMange
// 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() o
// 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();
}
}
}
或者如果你喜欢使用tags而不是类成员变量/引用,Fragments你也可以以相同的方式抓取tags设置FragmentPagerAdapter:注意:这不适用于创建它时FragmentStatePagerAdapter不设置。tagsFragments
@Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
// get the tags set by FragmentPagerAdapte
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 late
return createdFragment;
}
请注意,此方法不依赖于模拟内部tag集合FragmentPagerAdapter,而是使用适当的API来检索它们。这样即使tag未来版本的变化SupportLibrary仍然安全。
不要忘记,根据你的设计Activity,Fragments你正在努力的工作可能还可能不存在,所以你必须null在使用你的参考之前通过检查进行解释。
另外,如果你正在使用FragmentStatePagerAdapter,那么你不想保留对你的强硬引用,Fragments因为你可能有很多这样的引用,而且硬引用会不必要地把它们留在内存中。而应将其保存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...
}
当FragmentPagerAdapter向FragmentManager添加一个片段时,它将使用一个基于该片段将被放置的特定位置的特殊标签。FragmentPagerAdapter.getItem(int position)仅当该位置的片段不存在时才被调用。在旋转之后,Android会注意到它已经为这个特定的位置创建/保存了一个片段,所以它只是试图与它重新连接FragmentManager.findFragmentByTag(),而不是创建一个新片段。所有这些在使用时都是免费的,这FragmentPagerAdapter也是为什么通常在getItem(int)方法中使用片段初始化代码的原因。
即使我们不使用a 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();
}
...
}
当使用a时FragmentPagerAdapter,我们放弃片段管理到适配器,并且不必执行上述步骤。默认情况下,它只会在当前位置的前面和后面预加载一个片段(尽管除非您正在使用,否则不会将其破坏FragmentStatePagerAdapter)。这由ViewPager.setOffscreenPageLimit(int)控制。因此,直接调用适配器外部片段的方法并不保证是有效的,因为它们甚至可能不存在。
长话短说,你putFragment以后能够得到一个参考的解决方案并不是那么疯狂,而不是像正常的使用片段的方式(上面)那样。这是很难得到一个参考,否则因为片段是由适配器添加,而不是你个人。只要确保offscreenPageLimit足够高,随时加载你想要的碎片,因为你依靠它存在。这绕过了ViewPager的延迟加载功能,但似乎是您对应用程序的要求。
另一种方法是FragmentPageAdapter.instantiateItem(View, int)在返回超级调用之前覆盖并保存对超级调用返回的片段的引用(如果已经存在,则具有查找片段的逻辑)。
要获得更完整的图片,请查看FragmentPagerAdapter(short)和ViewPager(long)的一些源代码。