片段似乎非常适合将UI逻辑分离为某些模块 . 但随着 ViewPager
,它的生命周期对我来说仍然是迷雾 . 因此迫切需要大师的想法!
编辑
见下面的哑解决方案;-)
范围
主要活动有一个 ViewPager
与片段 . 这些片段可以为其他(子域)活动实现一些不同的逻辑,因此片段的数据通过活动内部的回调接口填充 . 第一次发布时一切正常,但是!...
问题
当重新创建活动时(例如,在方向更改时),所以 ViewPager
's fragments. The code (you' ll在下面找到)表示每次创建活动时我尝试创建一个与片段相同的新 ViewPager
片段适配器(可能这是问题)但FragmentManager已经将所有这些片段存储在某处(其中?)并为这些片段启动娱乐机制 . 因此,重新创建机制会调用"old" fragment _158754实现的方法 . 但是这个方法指向通过Activity的onCreate方法创建的新创建的片段 .
问题
也许我对它有很多了解 . 所以, please ,给我一两拳,并指出如何以正确的方式做到这一点 . 非常感谢!
代码
主要活动
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... */
}
解决方案
愚蠢的解决方案是使用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();
...
}
10 回答
那是什么
BasePagerAdapter
?您应该使用标准的寻呼机适配器之一 -FragmentPagerAdapter
或FragmentStatePagerAdapter
,具体取决于您是否希望ViewPager
不再需要的碎片保留(前者)或保存其状态(后者)并重新使用 - 如果需要再创建 .使用
ViewPager
的示例代码可以找到here确实,跨越活动实例的视图分页器中的片段管理有点复杂,因为框架中的
FragmentManager
负责保存状态并恢复寻呼机所做的任何活动片段 . 所有这些真正意味着初始化时适配器需要确保它与任何已恢复的片段重新连接 . 您可以查看FragmentPagerAdapter
或FragmentStatePagerAdapter
的代码以了解如何完成此操作 .当
FragmentPagerAdapter
将片段添加到FragmentManager时,它会根据片段放置的特定位置使用特殊标记 . 仅当该位置的片段不存在时才会调用FragmentPagerAdapter.getItem(int position)
. 旋转后,Android会注意到它已经为这个特定位置创建/保存了一个片段,所以它只是尝试用FragmentManager.findFragmentByTag()
重新连接它,而不是创建一个新的 . 使用FragmentPagerAdapter
时所有这些都是免费的,这就是为什么通常在getItem(int)
方法中使用片段初始化代码 .即使我们没有使用
FragmentPagerAdapter
,在Activity.onCreate(Bundle)
中每次创建一个新片段也不是一个好主意 . 正如您所注意到的,当片段添加到FragmentManager时,它将在旋转后为您重新创建,无需再次添加 . 在处理片段时,这样做是导致错误的常见原因 .处理片段时常用的方法是:
使用
FragmentPagerAdapter
时,我们将片段管理放弃到适配器,而不必执行上述步骤 . 默认情况下,它只会在当前位置的前面和后面预加载一个Fragment(虽然它不会破坏它们,除非你使用FragmentStatePagerAdapter
) . 这由ViewPager.setOffscreenPageLimit(int)控制 . 因此,不能保证在适配器外部的片段上直接调用方法是有效的,因为它们甚至可能不存在 .简而言之,您使用
putFragment
之后能够获得参考的解决方案并不是那么疯狂,并且与正常使用片段的方式不同(上图) . 否则很难获得参考,因为片段是由适配器添加的,而不是您个人添加的 . 只需确保offscreenPageLimit
足够高,可以随时加载您想要的碎片,因为您依赖它存在 . 这绕过了ViewPager的延迟加载功能,但似乎是您对应用程序的期望 .另一种方法是覆盖
FragmentPageAdapter.instantiateItem(View, int)
并在返回之前保存对超级调用返回的片段的引用(它具有查找片段的逻辑,如果已经存在) .要获得更全面的图片,请查看FragmentPagerAdapter(短)和ViewPager(长)的一些来源 .
如果有任何人遇到问题,他们的FragmentStatePagerAdapter没有正确恢复其片段的状态...即... FragmentStatePagerAdapter正在创建新的片段而不是从州恢复它们......
确保在致电
ViewPager.setAdapeter(fragmentStatePagerAdapter)
之前致电ViewPager.setOffscreenPageLimit()
在调用
ViewPager.setOffscreenPageLimit()
时... ViewPager将 immediately 查看其适配器并尝试获取其片段 . 这可能发生在ViewPager有机会从savedInstanceState恢复片段之前(从而创建新的片段,可以重新创建) .加:
在你上课之前 .
它不起作用做这样的事情:
要在方向更改后获取片段,您必须使用.getTag() .
为了更多的处理,我为我的PageAdapter编写了自己的ArrayList,以便在任何位置通过viewPagerId和FragmentClass获取片段:
所以只需创建一个包含片段的MyPageArrayList:
并将它们添加到viewPager:
在此之后,您可以通过使用其类来获取方向更改正确的片段:
我找到了另一个相对简单的解决方案 .
正如您在FragmentPagerAdapter source code中看到的那样,由
FragmentPagerAdapter
管理的片段存储在FragmentManager
下使用以下标签生成的标签:viewId
是container.getId()
,container
是你的ViewPager
实例 .index
是片段的位置 . 因此,您可以将对象ID保存到outState
:如果您想与此片段进行通信,可以从
FragmentManager
获取,例如:我想为一个稍微不同的案例提供一个替代解决方案,因为我的许多搜索答案一直引导我到这个线程 .
My case - 我正在动态创建/添加页面并将它们滑动到ViewPager中,但是当旋转(onConfigurationChange)时,我最终会得到一个新页面,因为当然会再次调用OnCreate . 但我想继续引用旋转之前创建的所有页面 .
Problem - 我没有为我创建的每个片段提供唯一标识符,因此引用的唯一方法是以某种方式存储在旋转/配置更改后要恢复的数组中的引用 .
Workaround - 关键概念是让Activity(显示片段)也管理对现有片段的引用数组,因为此活动可以利用onSaveInstanceState中的Bundles
因此,在此活动中,我声明一个私人成员来跟踪打开的页面
每次onSaveInstanceState都会在onCreate中调用和恢复时更新
...所以一旦存储,就可以检索到......
这些是对主要活动的必要更改,因此我需要FragmentPagerAdapter中的成员和方法才能使其工作,因此
一个相同的构造(如上面MainActivity中所示)
并且这种方法特别支持这种同步(如上面onSaveInstanceState中所使用的)
最后,在片段类中
为了使所有这些工作,首先有两个变化
然后将其添加到onCreate中,以便不破坏碎片
我仍然在围绕Fragments和Android生命周期进行整理,所以请注意,这种方法可能存在冗余/效率低下 . 但它适用于我,我希望可能对其他类似我的案例有帮助 .
我想出了这个简单而优雅的解决方案 . 它假定活动负责创建Fragments,而Adapter只是为它们提供服务 .
这是适配器的代码(这里没什么奇怪的,除了
mFragments
是Activity维护的片段列表这一事实)这个线程的整个问题是得到“旧”片段的引用,所以我在Activity的onCreate中使用了这个代码 .
当然,如果需要,您可以进一步微调此代码,例如确保片段是特定类的实例 .
我想提供一个扩展
antonyt
的wonderful answer的解决方案,并提及覆盖FragmentPageAdapter.instantiateItem(View, int)
来保存对创建Fragments
的引用,以便您以后可以对它们进行处理 . 这也适用于FragmentStatePagerAdapter
;请参阅说明了解详情 .这是一个简单的示例,说明如何获取
FragmentPagerAdapter
返回的Fragments
的引用,该引用不依赖于Fragments
上设置的内部tags
. 关键是覆盖instantiateItem()并在那里保存引用而不是getItem()
.or 如果您更喜欢使用
tags
而不是类成员变量/对Fragments
的引用,您也可以通过FragmentPagerAdapter
以相同方式获取tags
:注意:这不适用于FragmentStatePagerAdapter
,因为它在创建时未设置tags
它的Fragments
.请注意,此方法不依赖于模仿
FragmentPagerAdapter
设置的内部tag
,而是使用适当的API来检索它们 . 这种方式即使SupportLibrary
的未来版本中的tag
更改,您仍然是安全的 .Don't forget 取决于设计您正在尝试工作的
Activity
可能存在或者可能不存在,因此您必须在使用引用之前通过null
检查来解释这一点 .Also, if instead 你正在使用
FragmentStatePagerAdapter
,那么你不想保留对你的Fragments
的硬引用,因为你可能有很多这样的引用,硬引用会不必要地将它们保存在内存中 . 而是在WeakReference
变量中保存Fragment
引用而不是标准变量 . 像这样:我的解决方案非常粗鲁但有效:作为我从保留数据动态创建的片段,我只需在调用
super.onSaveInstanceState()
之前删除PageAdapter
中的所有片段,然后在创建活动时重新创建它们:您无法在
onDestroy()
中删除它们,否则会出现此异常:java.lang.IllegalStateException:
onSaveInstanceState
后无法执行此操作这里是页面适配器中的代码:
在创建片段后,我只保存当前页面并在
onCreate()
中恢复它 .