第一次后未显示 Viewpager 片段
Viewpager fragments not shown after the first time
我有一个包含 3 个片段(A、B、C)的 activity。片段 A 由一个 ViewPager
和 2 ListFragments
组成。用户可以点击任何列表片段中的项目,然后转到片段 B。
在片段A中我做的是:
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
pagerAdapter = new PagerAdapter(getActivity().getSupportFragmentManager());
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragmentA, container, false);
vpPager = (ViewPager)view.findViewById(R.id.vpPager);
vpPager.setOffscreenPageLimit(2);
vpPager.setAdapter(pagerAdapter);
vpPager.addOnPageChangeListener(this);
return view;
}
而PagerAdapter
如下:
private class PagerAdapter extends FragmentPagerAdapter {
private final ListFragment1 lf1 = ListFragment1 .newInstance();
private final ListFragment2 lf2 = ListFragment2 .newInstance();
public PagerAdapter(android.support.v4.app.FragmentManager fm) {
super(fm);
}
@Override
public android.support.v4.app.Fragment getItem(int position) {
switch (position) {
case 0: return lf1;
case 1: return lf2;
default: return null;
}
}
}
第一次显示 activity 时,viewpager 列表片段显示正确。
2 个 viewpager 片段从数据库加载数据,我只这样做一次(在创建片段时)。
用户可以点击一个项目并显示片段 B。如果用户按下后退键,将显示片段 A。但是列表片段没有显示(它们的一个实例仍然存在)。
难道视图已经被销毁了,尽管实例存在?
这里有什么问题?有没有更好的方法?
编辑
如果我在寻呼机适配器中使用 newInstance
,我会得到一个 IllegalStateException: not attached to activity
。这是因为我启动了一个异步任务如下:
@Override
public void onPageSelected(int position) {
Fragment fragment = pagerAdapter.getItem(position);
if (fragment instanceof IPagedFragment) {
((IPagedFragment) fragment).onShown();
}
}
而onShown
是:
@Override
public void onShown() {
myTask= new MyTask();
myTask.execute((Void)null);
}
我什么时候可以开始任务,这样我就可以 100% 确定片段已附加到 activity 并且视图已创建(我需要从布局中获取列表视图等).
尝试覆盖 FragmentPagerAdapter 中的 getItemPosition
方法:
@Override
public int getItemPosition(Object object) {
return PagerAdapter.POSITION_NONE;
}
您不应在 FragmentPagerAdapter
中保留对片段的引用。您应该始终在 getItem()
调用中调用 newInstance
,例如:
@Override
public android.support.v4.app.Fragment getItem(int position) {
switch (position) {
case 0: return ListFragment1.newInstance();
case 1: return ListFragment2.newInstance();
default: return null;
}
}
您从数据库加载的数据应该存储在片段本身中。适配器将恢复片段的状态 (setOffscreenPageLimit(2)
)。
您正在丢失片段,因为项目(片段)由您提供的 FragmentManager
实例化,并且它会根据标签创建片段。所以它可能会创建一个您已经保留的片段的新实例,只是带有不同的标签。
查看FragmentPagerAdapter
源代码(检查instantiateItem()
方法):
https://android.googlesource.com/platform/frameworks/support/+/refs/heads/master/v13/java/android/support/v13/app/FragmentPagerAdapter.java
另请参阅此答案:
keep instances of fragments inside FragmentPagerAdapter
您正在使用 Activity FragmentManager 创建 ListFragment1 和 ListFragment2,而您应该使用 Fragment FragmentManager。因此,将 pagerAdapter = new PagerAdapter(getActivity().getSupportFragmentManager());
修改为 pagerAdapter = new PagerAdapter(getChildFragmentManager());
。这样,view pager 的片段将'bound' 到托管viewpager 的片段。此外,您不应该在 viewpager 中保留对片段的任何引用:这是 Android 已经管理的内容。尝试:
private class PagerAdapter extends FragmentPagerAdapter {
public PagerAdapter(android.support.v4.app.FragmentManager fm) {
super(fm);
}
@Override
public android.support.v4.app.Fragment getItem(int position) {
switch (position) {
case 0: return ListFragment1.newInstance();
case 1: return ListFragment2.newInstance();
default: return null;
}
}
}
顺便说一句,vpPager.setOffscreenPageLimit(2);
没用,因为你只有 2 页,而且这是我从未使用过的方法,即使我有很多片段要管理,因为它需要内存。
关于您的更新:删除与处理片段的 ViewPager 相关的任何逻辑。如果您需要在 Fragment 中启动 AsyncTask,您可以使用 Fragment 生命周期的方法之一:onResume()、onCreateView() 等。
class IPagedFragment extends Fragment {
public void onResume() {
super.onResume();
myTask= new MyTask();
myTask.execute((Void)null);
}
}
请删除 private final ListFragment1 lf1 = ListFragment1 .newInstance();
。相信我,这不是一个好主意,因为你对你的片段有很强的参考。
我构建了一个简单的项目,您可以将其用作参考实现。您可以从我的 dropbox.
下载源代码
您必须像下面那样使用 ChildFragmentManager
。
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
pagerAdapter = new PagerAdapter(getChildFragmentManager()); //here used child fragment manager
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragmentA, container, false);
vpPager = (ViewPager)view.findViewById(R.id.vpPager);
vpPager.setOffscreenPageLimit(2);
vpPager.setAdapter(pagerAdapter);
vpPager.addOnPageChangeListener(this);
return view;
}
它在我的代码中使用 viewpager 和 fragment 时很有魅力。
- 在 PagerAdapter class 覆盖方法 setPrimaryItem,
当寻呼机发生变化时调用它,我会试一试。
我会创建类似的东西:
private class PagerAdapter extends FragmentPagerAdapter {
private final ListFragment1 lf1 = ListFragment1 .newInstance();
private final ListFragment2 lf2 = ListFragment2 .newInstance();
public PagerAdapter(android.support.v4.app.FragmentManager fm) {
super(fm);
}
@Override
public android.support.v4.app.Fragment getItem(int position) {
switch (position) {
case 0: return lf1;
case 1: return lf2;
default: return null;
}
}
@Override
public int getCount() {
return 2;
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
if (position == 0)
lf1.updateUI(); //Refresh what you need on this fragment
else if (position == 1)
lf2.updateUI();
}
}
- 你也不见了
getCount()
。
- 我不确定屏幕外有什么用,但这可能不是问题。
vpPager.setOffscreenPageLimit(2)
- 还有一件事,我也会删除
vpPager.addOnPageChangeListener(this)
,这没有用,它可能会给您带来一些问题。
无论你需要做什么,你都可以在没有它的情况下完成它,通过覆盖分页,你可能 "ruin" 一些标准分页(因为没有调用 super)
如果上述任何解决方案都不起作用,您可以通过向寻呼机视图实例发送(延迟)适配器的附加 notifyDataSetChanged 调用来尝试解决方法:
vpPager.post(new Runnable() {
@Override
public void run() {
pagerAdapter.notifyDataSetChanged();
}
});
或
vpPager.postDelayed(new Runnable() {
@Override
public void run() {
pagerAdapter.notifyDataSetChanged();
}
}, 100 /* you have to find out the best delay time by trying/adjusting */);
刚才折腾了一整天,用getChildFragmentManager()
解决了
将其作为参数传递给 pagerAdapter。它将起作用。
在片段使用中使用 pagerAdapter 时:
PagerAdapter adapter = new PagerAdapter(getChildFragmentManager());
如果 activity 使用 getFragmentManager()
PagerAdapter adapter = new PagerAdapter(getFragmentManager());
使用 getChildFragmentManager() 而不是 supportFragmentManager()
如果你用 Kotlin 体验过,就会像这样。
val fragmentAdapter = FragmentPageAdapter(childFragmentManager)
我有一个包含 3 个片段(A、B、C)的 activity。片段 A 由一个 ViewPager
和 2 ListFragments
组成。用户可以点击任何列表片段中的项目,然后转到片段 B。
在片段A中我做的是:
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
pagerAdapter = new PagerAdapter(getActivity().getSupportFragmentManager());
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragmentA, container, false);
vpPager = (ViewPager)view.findViewById(R.id.vpPager);
vpPager.setOffscreenPageLimit(2);
vpPager.setAdapter(pagerAdapter);
vpPager.addOnPageChangeListener(this);
return view;
}
而PagerAdapter
如下:
private class PagerAdapter extends FragmentPagerAdapter {
private final ListFragment1 lf1 = ListFragment1 .newInstance();
private final ListFragment2 lf2 = ListFragment2 .newInstance();
public PagerAdapter(android.support.v4.app.FragmentManager fm) {
super(fm);
}
@Override
public android.support.v4.app.Fragment getItem(int position) {
switch (position) {
case 0: return lf1;
case 1: return lf2;
default: return null;
}
}
}
第一次显示 activity 时,viewpager 列表片段显示正确。
2 个 viewpager 片段从数据库加载数据,我只这样做一次(在创建片段时)。
用户可以点击一个项目并显示片段 B。如果用户按下后退键,将显示片段 A。但是列表片段没有显示(它们的一个实例仍然存在)。
难道视图已经被销毁了,尽管实例存在?
这里有什么问题?有没有更好的方法?
编辑
如果我在寻呼机适配器中使用 newInstance
,我会得到一个 IllegalStateException: not attached to activity
。这是因为我启动了一个异步任务如下:
@Override
public void onPageSelected(int position) {
Fragment fragment = pagerAdapter.getItem(position);
if (fragment instanceof IPagedFragment) {
((IPagedFragment) fragment).onShown();
}
}
而onShown
是:
@Override
public void onShown() {
myTask= new MyTask();
myTask.execute((Void)null);
}
我什么时候可以开始任务,这样我就可以 100% 确定片段已附加到 activity 并且视图已创建(我需要从布局中获取列表视图等).
尝试覆盖 FragmentPagerAdapter 中的 getItemPosition
方法:
@Override
public int getItemPosition(Object object) {
return PagerAdapter.POSITION_NONE;
}
您不应在 FragmentPagerAdapter
中保留对片段的引用。您应该始终在 getItem()
调用中调用 newInstance
,例如:
@Override
public android.support.v4.app.Fragment getItem(int position) {
switch (position) {
case 0: return ListFragment1.newInstance();
case 1: return ListFragment2.newInstance();
default: return null;
}
}
您从数据库加载的数据应该存储在片段本身中。适配器将恢复片段的状态 (setOffscreenPageLimit(2)
)。
您正在丢失片段,因为项目(片段)由您提供的 FragmentManager
实例化,并且它会根据标签创建片段。所以它可能会创建一个您已经保留的片段的新实例,只是带有不同的标签。
查看FragmentPagerAdapter
源代码(检查instantiateItem()
方法):
https://android.googlesource.com/platform/frameworks/support/+/refs/heads/master/v13/java/android/support/v13/app/FragmentPagerAdapter.java
另请参阅此答案: keep instances of fragments inside FragmentPagerAdapter
您正在使用 Activity FragmentManager 创建 ListFragment1 和 ListFragment2,而您应该使用 Fragment FragmentManager。因此,将 pagerAdapter = new PagerAdapter(getActivity().getSupportFragmentManager());
修改为 pagerAdapter = new PagerAdapter(getChildFragmentManager());
。这样,view pager 的片段将'bound' 到托管viewpager 的片段。此外,您不应该在 viewpager 中保留对片段的任何引用:这是 Android 已经管理的内容。尝试:
private class PagerAdapter extends FragmentPagerAdapter {
public PagerAdapter(android.support.v4.app.FragmentManager fm) {
super(fm);
}
@Override
public android.support.v4.app.Fragment getItem(int position) {
switch (position) {
case 0: return ListFragment1.newInstance();
case 1: return ListFragment2.newInstance();
default: return null;
}
}
}
顺便说一句,vpPager.setOffscreenPageLimit(2);
没用,因为你只有 2 页,而且这是我从未使用过的方法,即使我有很多片段要管理,因为它需要内存。
关于您的更新:删除与处理片段的 ViewPager 相关的任何逻辑。如果您需要在 Fragment 中启动 AsyncTask,您可以使用 Fragment 生命周期的方法之一:onResume()、onCreateView() 等。
class IPagedFragment extends Fragment {
public void onResume() {
super.onResume();
myTask= new MyTask();
myTask.execute((Void)null);
}
}
请删除 private final ListFragment1 lf1 = ListFragment1 .newInstance();
。相信我,这不是一个好主意,因为你对你的片段有很强的参考。
我构建了一个简单的项目,您可以将其用作参考实现。您可以从我的 dropbox.
下载源代码您必须像下面那样使用 ChildFragmentManager
。
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
pagerAdapter = new PagerAdapter(getChildFragmentManager()); //here used child fragment manager
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragmentA, container, false);
vpPager = (ViewPager)view.findViewById(R.id.vpPager);
vpPager.setOffscreenPageLimit(2);
vpPager.setAdapter(pagerAdapter);
vpPager.addOnPageChangeListener(this);
return view;
}
它在我的代码中使用 viewpager 和 fragment 时很有魅力。
- 在 PagerAdapter class 覆盖方法 setPrimaryItem, 当寻呼机发生变化时调用它,我会试一试。
我会创建类似的东西:
private class PagerAdapter extends FragmentPagerAdapter {
private final ListFragment1 lf1 = ListFragment1 .newInstance();
private final ListFragment2 lf2 = ListFragment2 .newInstance();
public PagerAdapter(android.support.v4.app.FragmentManager fm) {
super(fm);
}
@Override
public android.support.v4.app.Fragment getItem(int position) {
switch (position) {
case 0: return lf1;
case 1: return lf2;
default: return null;
}
}
@Override
public int getCount() {
return 2;
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
if (position == 0)
lf1.updateUI(); //Refresh what you need on this fragment
else if (position == 1)
lf2.updateUI();
}
}
- 你也不见了
getCount()
。 - 我不确定屏幕外有什么用,但这可能不是问题。
vpPager.setOffscreenPageLimit(2)
- 还有一件事,我也会删除
vpPager.addOnPageChangeListener(this)
,这没有用,它可能会给您带来一些问题。 无论你需要做什么,你都可以在没有它的情况下完成它,通过覆盖分页,你可能 "ruin" 一些标准分页(因为没有调用 super)
如果上述任何解决方案都不起作用,您可以通过向寻呼机视图实例发送(延迟)适配器的附加 notifyDataSetChanged 调用来尝试解决方法:
vpPager.post(new Runnable() {
@Override
public void run() {
pagerAdapter.notifyDataSetChanged();
}
});
或
vpPager.postDelayed(new Runnable() {
@Override
public void run() {
pagerAdapter.notifyDataSetChanged();
}
}, 100 /* you have to find out the best delay time by trying/adjusting */);
刚才折腾了一整天,用getChildFragmentManager()
解决了
将其作为参数传递给 pagerAdapter。它将起作用。
在片段使用中使用 pagerAdapter 时:
PagerAdapter adapter = new PagerAdapter(getChildFragmentManager());
如果 activity 使用 getFragmentManager()
PagerAdapter adapter = new PagerAdapter(getFragmentManager());
使用 getChildFragmentManager() 而不是 supportFragmentManager()
如果你用 Kotlin 体验过,就会像这样。
val fragmentAdapter = FragmentPageAdapter(childFragmentManager)