我无法更新ViewPager中的内容。
FragmentPagerAdapter类中的方法instantiateItem()和getItem()的正确用法是什么?
我只使用getItem()实例化并返回我的片段:
@Override
public Fragment getItem(int position) {
return new MyFragment(context, paramters);
}
这很好用。除了我无法改变内容。
所以我发现了这个:ViewPager PagerAdapter not updating the View
“我的方法是对instantiateItem()方法中的任何实例化视图使用setTag()方法”
现在我想实现instantiateItem()来做到这一点。但是我不知道我要返回什么(类型是Object)以及与getItem(int position)的关系是什么?
我读了reference:
public abstract Fragment getItem(int position)
返回与指定位置关联的片段。
public Object instantiateItem(ViewGroup container,int position)
创建给定位置的页面。适配器负责将视图添加到此处给出的容器,但它必须确保在从finishUpdate(ViewGroup)返回时完成此操作。 参数
container包含将在其中显示页面的View。 position要实例化的页面位置。
返回
返回表示新页面的Object。这不需要是View,但可以是页面的其他容器。
但我仍然没有得到它。
这是我的代码。我正在使用支持包v4。
ViewPagerTest
public class ViewPagerTest extends FragmentActivity {
private ViewPager pager;
private MyFragmentAdapter adapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.pager1);
pager = (ViewPager)findViewById(R.id.slider);
String[] data = {"page1", "page2", "page3", "page4", "page5", "page6"};
adapter = new MyFragmentAdapter(getSupportFragmentManager(), 6, this, data);
pager.setAdapter(adapter);
((Button)findViewById(R.id.button)).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
reload();
}
});
}
private void reload() {
String[] data = {"changed1", "changed2", "changed3", "changed4", "changed5", "changed6"};
//adapter = new MyFragmentAdapter(getSupportFragmentManager(), 6, this, data);
adapter.setData(data);
adapter.notifyDataSetChanged();
pager.invalidate();
//pager.setCurrentItem(0);
}
}
MyFragmentAdapter
class MyFragmentAdapter extends FragmentPagerAdapter {
private int slideCount;
private Context context;
private String[] data;
public MyFragmentAdapter(FragmentManager fm, int slideCount, Context context, String[] data) {
super(fm);
this.slideCount = slideCount;
this.context = context;
this.data = data;
}
@Override
public Fragment getItem(int position) {
return new MyFragment(data[position], context);
}
@Override
public int getCount() {
return slideCount;
}
public void setData(String[] data) {
this.data = data;
}
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
MyFragment
public final class MyFragment extends Fragment {
private String text;
public MyFragment(String text, Context context) {
this.text = text;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.slide, null);
((TextView)view.findViewById(R.id.text)).setText(text);
return view;
}
}
这也是有类似问题的人,没有答案 http://www.mail-archive.com/android-developers@googlegroups.com/msg200477.html
答案 0 :(得分:574)
使用FragmentPagerAdapter或FragmentStatePagerAdapter时,最好只处理getItem()
而不是触及instantiateItem()
。 PagerAdapter上的instantiateItem()
- destroyItem()
- isViewFromObject()
接口是FragmentPagerAdapter用来实现更简单的getItem()
接口的低级接口。
在开始讨论之前,我应该澄清一下
如果要切换显示的实际片段,则需要避免使用FragmentPagerAdapter并使用 FragmentStatePagerAdapter 强>
这个答案的早期版本错误地使用了FragmentPagerAdapter作为它的例子 - 这是行不通的,因为FragmentPagerAdapter在第一次显示之后永远不会破坏它。
我不建议您关联的帖子中提供的setTag()
和findViewWithTag()
解决方法。正如您所发现的那样,使用setTag()
和findViewWithTag()
不能使用片段,所以这不是一个很好的匹配。
正确的解决方案是覆盖getItemPosition()
。调用notifyDataSetChanged()
时,ViewPager会在其适配器中的所有项目上调用getItemPosition()
,以查看是否需要将它们移动到其他位置或删除。
默认情况下,getItemPosition()
返回POSITION_UNCHANGED
,这表示“此对象很好,不要销毁或删除它”。返回POSITION_NONE
修正了问题,而是说:“此对象不再是我正在显示的项目,请将其删除。”因此它具有删除和重新创建适配器中每个项目的效果。
这是一个完全合法的修复!此修复使notifyDataSetChanged的行为类似于常规适配器而没有视图回收。如果您实施此修复程序并且性能令人满意,那么您将参加比赛。完成工作。
如果您需要更好的性能,可以使用更高级的getItemPosition()
实施。以下是寻呼机从字符串列表中创建片段的示例:
ViewPager pager = /* get my ViewPager */;
// assume this actually has stuff in it
final ArrayList<String> titles = new ArrayList<String>();
FragmentManager fm = getSupportFragmentManager();
pager.setAdapter(new FragmentStatePagerAdapter(fm) {
public int getCount() {
return titles.size();
}
public Fragment getItem(int position) {
MyFragment fragment = new MyFragment();
fragment.setTitle(titles.get(position));
return fragment;
}
public int getItemPosition(Object item) {
MyFragment fragment = (MyFragment)item;
String title = fragment.getTitle();
int position = titles.indexOf(title);
if (position >= 0) {
return position;
} else {
return POSITION_NONE;
}
}
});
通过此实现,仅显示显示新标题的片段。显示仍然在列表中的标题的任何片段将被移动到列表中的新位置,并且具有不再在列表中的标题的片段将被销毁。
如果片段尚未重新创建,但仍需要更新,该怎么办?生命片段的更新最好由片段本身处理。毕竟这是拥有片段的优势 - 它是它自己的控制器。片段可以将侦听器或观察者添加到onCreate()
中的另一个对象,然后在onDestroy()
中将其删除,从而自行管理更新。您不必像在ListView或其他AdapterView类型的适配器中那样将所有更新代码放在getItem()
中。
最后一件事 - 只是因为FragmentPagerAdapter不破坏片段并不意味着getItemPosition在FragmentPagerAdapter中完全无用。您仍然可以使用此回调来重新排序ViewPager中的片段。但它永远不会从FragmentManager中完全删除它们。
答案 1 :(得分:134)
不是从POSITION_NONE
返回getItemPosition()
并导致全视图重新播放,而是执行此操作:
//call this method to update fragments in ViewPager dynamically
public void update(UpdateData xyzData) {
this.updateData = xyzData;
notifyDataSetChanged();
}
@Override
public int getItemPosition(Object object) {
if (object instanceof UpdateableFragment) {
((UpdateableFragment) object).update(updateData);
}
//don't return POSITION_NONE, avoid fragment recreation.
return super.getItemPosition(object);
}
您的片段应该实现UpdateableFragment
interface:
public class SomeFragment extends Fragment implements
UpdateableFragment{
@Override
public void update(UpdateData xyzData) {
// this method will be called for every fragment in viewpager
// so check if update is for this fragment
if(forMe(xyzData)) {
// do whatever you want to update your UI
}
}
}
和界面:
public interface UpdateableFragment {
public void update(UpdateData xyzData);
}
您的数据类:
public class UpdateData {
//whatever you want here
}
答案 2 :(得分:20)
对于那些仍然遇到我之前遇到的同样问题的人,当我有一个包含7个片段的ViewPager时。这些片段的默认值是从api服务加载英语内容但这里的问题是我想从设置活动中更改语言并在完成之后 设置活动我希望主活动中的ViewPager刷新片段以匹配来自用户的语言选择并加载阿拉伯语内容如果用户在这里选择了阿拉伯语,那么我第一次做的工作
1-您必须使用如上所述的FragmentStatePagerAdapter。
2-on mainActivity i覆盖onResume并执行以下操作
if (!(mPagerAdapter == null)) {
mPagerAdapter.notifyDataSetChanged();
}
3-i覆盖了mPagerAdapter中的getItemPosition()并使其返回POSITION_NONE。
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
像魅力一样工作
答案 3 :(得分:14)
我遇到了这个问题并且今天终于解决了,所以我写下了我所学到的知识,希望对于那些不熟悉Android ViewPager
并且像我一样更新的人有所帮助。我在API级别17中使用FragmentStatePagerAdapter
,目前只有2个片段。我认为必定有些不正确的地方,请指正,谢谢。
必须将序列化数据加载到内存中。这可以使用CursorLoader
/ AsyncTask
/ Thread
来完成。它是否自动加载取决于您的代码。如果您使用CursorLoader
,则会自动加载,因为有一个注册数据观察者。
调用viewpager.setAdapter(pageradapter)
后,将不断调用适配器的getCount()
来构建片段。因此,如果正在加载数据,getCount()
可以返回0,因此您不需要为没有显示的数据创建虚拟片段。
加载数据后,由于getCount()
仍为0,适配器不会自动构建片段,因此我们可以设置实际加载的数据编号由getCount()
返回,然后调用适配器的notifyDataSetChanged()
。 ViewPager
开始按内存中的数据创建片段(仅前2个片段)。它在返回notifyDataSetChanged()
之前完成。然后ViewPager
拥有您需要的正确片段。
如果数据库和内存中的数据都已更新(直写),或者只更新内存中的数据(写回),或者只更新数据库中的数据。在最后两种情况下,如果数据没有自动从数据库加载到内存(如上所述)。
ViewPager
和寻呼机适配器只处理内存中的数据。
因此,当内存中的数据更新时,我们只需要调用适配器的notifyDataSetChanged()
。由于片段已经创建,因此在onItemPosition()
返回之前将调用适配器的notifyDataSetChanged()
。在getItemPosition()
中无需做任何事情。然后更新数据。
答案 4 :(得分:12)
在代码destroyDrawingCache()
之后的ViewPager上尝试notifyDataSetChanged()
。
答案 5 :(得分:10)
在尝试上述所有解决方案以解决此问题并在其他类似问题(如this,this和this上尝试了许多解决方案时,经过几个小时的挫折,这些问题都与我失败了解决此问题并使ViewPager
销毁旧版Fragment
并使用新pager
填充Fragment
。我已经解决了以下问题:
1)让ViewPager
课程扩展FragmentPagerAdapter
,如下所示:
public class myPagerAdapter extends FragmentPagerAdapter {
2)为存储ViewPager
和title
的{{1}}创建一个项目,如下所示:
fragment
3)让public class PagerItem {
private String mTitle;
private Fragment mFragment;
public PagerItem(String mTitle, Fragment mFragment) {
this.mTitle = mTitle;
this.mFragment = mFragment;
}
public String getTitle() {
return mTitle;
}
public Fragment getFragment() {
return mFragment;
}
public void setTitle(String mTitle) {
this.mTitle = mTitle;
}
public void setFragment(Fragment mFragment) {
this.mFragment = mFragment;
}
}
的构造函数将我的ViewPager
实例存储在我的FragmentManager
中,如下所示:
class
4)创建一种方法,通过将private FragmentManager mFragmentManager;
private ArrayList<PagerItem> mPagerItems;
public MyPagerAdapter(FragmentManager fragmentManager, ArrayList<PagerItem> pagerItems) {
super(fragmentManager);
mFragmentManager = fragmentManager;
mPagerItems = pagerItems;
}
本身的所有先前adapter
直接删除到新数据来重新设置fragment
数据让fragmentManager
再次从新列表中设置新的adapter
,如下所示:
fragment
5)从容器public void setPagerItems(ArrayList<PagerItem> pagerItems) {
if (mPagerItems != null)
for (int i = 0; i < mPagerItems.size(); i++) {
mFragmentManager.beginTransaction().remove(mPagerItems.get(i).getFragment()).commit();
}
mPagerItems = pagerItems;
}
或Activity
不要使用新数据重新初始化适配器。使用以下新数据通过方法Fragment
设置新数据:
setPagerItems
我希望它有所帮助。
答案 6 :(得分:7)
由于某种原因,没有一个答案对我有用,所以我必须覆盖restoreState方法而不在我的fragmentStatePagerAdapter中调用super。代码:
private class MyAdapter extends FragmentStatePagerAdapter {
// [Rest of implementation]
@Override
public void restoreState(Parcelable state, ClassLoader loader) {}
}
答案 7 :(得分:3)
我稍微修改了Bill Phillips提供的解决方案以满足我的需求
private class PagerAdapter extends FragmentStatePagerAdapter{
Bundle oBundle;
FragmentManager oFragmentManager;
ArrayList<Fragment> oPooledFragments;
public PagerAdapter(FragmentManager fm) {
super(fm);
oFragmentManager=fm;
}
@Override
public int getItemPosition(Object object) {
Fragment oFragment=(Fragment)object;
oPooledFragments=new ArrayList<>(oFragmentManager.getFragments());
if(oPooledFragments.contains(oFragment))
return POSITION_NONE;
else
return POSITION_UNCHANGED;
}
}
以便getItemPosition()
仅针对POSITION_NONE
调用FragmentManager
时当前位于getItemPosition
的片段返回FragmentStatePager
。
(请注意,此ViewPager
和与其关联的{{1}}包含在不在活动中的片段中
答案 8 :(得分:2)
我有类似的问题,但不想信任现有的解决方案(硬编码标签名称等),我不能让M-WaJeEh的解决方案适合我。这是我的解决方案:
我保留对数组中getItem中创建的片段的引用。只要活动没有因为configurationChange或缺少内存或其他原因而被破坏(或者&gt;回到活动时,片段返回到它们的最后状态而没有再次调用'getItem',因此没有更新数组)。
为了避免这个问题,我实现了instantiateItem(ViewGroup,int)并在那里更新我的数组,如下所示:
@Override
public Object instantiateItem(ViewGroup container, int position) {
Object o = super.instantiateItem(container, position);
if(o instanceof FragmentX){
myFragments[0] = (FragmentX)o;
}else if(o instanceof FragmentY){
myFragments[1] = (FragmentY)o;
}else if(o instanceof FragmentZ){
myFragments[2] = (FragmentZ)o;
}
return o;
}
所以,一方面我很高兴我找到了一个适合我的解决方案并希望与你分享,但我也想问一下其他人是否尝试过类似的东西以及是否有任何理由我不应该那样做吗?到目前为止它对我来说非常好......
答案 9 :(得分:1)
我使用EventBus库来更新Fragment
中的ViewPager
内容。逻辑很简单,就像document of EventBus how to do一样。无需控制FragmentPagerAdapter
实例。代码在这里:
1:定义事件
定义更新所需的消息。
public class UpdateCountEvent {
public final int count;
public UpdateCountEvent(int count) {
this.count = count;
}
}
2.Prepare订阅者
在片段中写下需要更新的代码。
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
public void onEvent(UpdateCountEvent event) {//get update message
Toast.makeText(getActivity(), event.count, Toast.LENGTH_SHORT).show();
}
3.Post events
将以下代码写在需要更新参数
的其他Activity
或其他Fragment
中
//input update message
EventBus.getDefault().post(new UpdateCountEvent(count));
答案 10 :(得分:1)
如果您想使用FragmentStatePagerAdapter,请查看https://code.google.com/p/android/issues/detail?can=2&start=0&num=100&q=&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars&groupby=&sort=&id=37990。 FragmentStatePagerAdapter存在问题,可能会或可能不会对您的用例造成影响。
此外,链接也没有什么解决方案..这可能适合您的要求。
答案 11 :(得分:1)
我遇到了同样的问题,我搜索了太多次。 stackoverflow或谷歌给出的任何答案都不是我的问题的解决方案。我的问题很简单。我有一个列表,我用viewpager显示这个列表。当我向列表的头部添加一个新元素时,我刷新了viewpager nothings已更改。我的最终解决方案非常容易,任何人都可以使用。当一个新元素添加到列表并想要刷新列表时。首先将viewpager适配器设置为null,然后重新创建适配器并将i设置为viewpager。
myVPager.setAdapter(null);
myFragmentAdapter = new MyFragmentAdapter(getSupportFragmentManager(),newList);
myVPager.setAdapter(myFragmentAdapter);
确保您的适配器必须扩展FragmentStatePagerAdapter
答案 12 :(得分:1)
使用 ViewPager2
和 FragmentStateAdapter
:
ViewPager2
支持动态更新数据。
docs 中有一条关于如何使其工作的重要说明:
<块引用>注意:DiffUtil 实用程序类依赖于通过 ID 识别项目。 如果您使用 ViewPager2 对可变集合进行分页,则还必须覆盖 getItemId() 和 containsItem()。(重点是我的)
基于 ViewPager2
documentation 和 Android 的 Github sample project,我们需要采取几个步骤:
设置 FragmentStateAdapter
并覆盖以下方法:getItemCount
、createFragment
、getItemId
和 containsItem
(注意:{{1} FragmentStatePagerAdapter
)
将适配器连接到 ViewPager2
使用 ViewPager2
将调度列表更新为 ViewPager2
(不需要使用 DiffUtil
,如示例项目中所示)
示例:
DiffUtil
答案 13 :(得分:0)
我一直在尝试这么多不同的方法,没有人真正解决我的问题。以下是我通过大家提供的各种解决方案解决问题的方法。谢谢大家。
class PagerAdapter extends FragmentPagerAdapter {
public boolean flag_refresh=false;
public PagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int page) {
FragmentsMain f;
f=new FragmentsMain();
f.page=page;
return f;
}
@Override
public int getCount() {
return 4;
}
@Override
public int getItemPosition(Object item) {
int page= ((FragmentsMain)item).page;
if (page == 0 && flag_refresh) {
flag_refresh=false;
return POSITION_NONE;
} else {
return super.getItemPosition(item);
}
}
@Override
public void destroyItem(View container, int position, Object object) {
((ViewPager) container).removeView((View) object);
}
}
我只想在onResume()之后刷新第0页。
adapter=new PagerAdapter(getSupportFragmentManager());
pager.setAdapter(adapter);
@Override
protected void onResume() {
super.onResume();
if (adapter!=null) {
adapter.flag_refresh=true;
adapter.notifyDataSetChanged();
}
}
在我的FragmentsMain中,有一个公共整数&#34; page&#34;,它可以告诉我它是否是我要刷新的页面。
public class FragmentsMain extends Fragment {
private Cursor cursor;
private static Context context;
public int page=-1;
答案 14 :(得分:0)
我知道派对迟到了。我已在TabLayout#setupWithViewPager(myViewPager);
FragmentPagerAdapter#notifyDataSetChanged();
来解决问题
答案 15 :(得分:0)
这个解决方案对每个人都不起作用,但就我而言,我的ViewPager中的每个片段都是一个不同的类,一次只存在其中一个。
有了这个限制,这个解决方案是安全的,应该可以安全地用于生产。
private void updateFragment(Item item) {
List<Fragment> fragments = getSupportFragmentManager().getFragments();
for (Fragment fragment : fragments) {
if (fragment instanceof MyItemFragment && fragment.isVisible()) {
((MyItemFragment) fragment).update(item);
}
}
}
如果您有相同片段的多个版本,则可以使用相同的策略调用这些片段上的方法,以确定它是否是您要更新的片段。
答案 16 :(得分:0)
这可能对某人有所帮助 - 在我的情况下,在插入新页面时,查看寻呼机要求两次现有片段的位置,但不要求新项目的位置,导致不正确的行为和数据不显示。
复制FragmentStatePagerAdapter的来源(好像很久没有更新)。
覆盖notifyDataSetChanged()
@Override
public void notifyDataSetChanged() {
mFragments.clear();
super.notifyDataSetChanged();
}
为destroyItem()添加一个完整性检查以防止崩溃:
if (position < mFragments.size()) {
mFragments.set(position, null);
}
答案 17 :(得分:0)
我已经完成了上面的所有答案和其他一些帖子,但仍然无法找到适合我的内容(使用不同的片段类型以及动态添加和删除标签)。 FWIW以下方法对我有用(如果其他人有同样的问题)。
public class MyFragmentStatePageAdapter extends FragmentStatePagerAdapter {
private static final String TAB1_TITLE = "Tab 1";
private static final String TAB2_TITLE = "Tab 2";
private static final String TAB3_TITLE = "Tab 3";
private ArrayList<String> titles = new ArrayList<>();
private Map<Fragment, Integer> fragmentPositions = new HashMap<>();
public MyFragmentStatePageAdapter(FragmentManager fm) {
super(fm);
}
public void update(boolean showTab1, boolean showTab2, boolean showTab3) {
titles.clear();
if (showTab1) {
titles.add(TAB1_TITLE);
}
if (showTab2) {
titles.add(TAB2_TITLE);
}
if (showTab3) {
titles.add(TAB3_TITLE);
}
notifyDataSetChanged();
}
@Override
public int getCount() {
return titles.size();
}
@Override
public Fragment getItem(int position) {
Fragment fragment = null;
String tabName = titles.get(position);
if (tabName.equals(TAB1_TITLE)) {
fragment = Tab1Fragment.newInstance();
} else if (tabName.equals(TAB2_TITLE)) {
fragment = Tab2Fragment.newInstance();
} else if (tabName.equals(TAB3_TITLE)) {
fragment = Tab3Fragmen.newInstance();
}
((BaseFragment)fragment).setTitle(tabName);
fragmentPositions.put(fragment, position);
return fragment;
}
@Override
public CharSequence getPageTitle(int position) {
return titles.get(position);
}
@Override
public int getItemPosition(Object item) {
BaseFragment fragment = (BaseFragment)item;
String title = fragment.getTitle();
int position = titles.indexOf(title);
Integer fragmentPosition = fragmentPositions.get(item);
if (fragmentPosition != null && position == fragmentPosition) {
return POSITION_UNCHANGED;
} else {
return POSITION_NONE;
}
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
fragmentPositions.remove(object);
}
}
答案 18 :(得分:0)
使用FragmentStatePagerAdapter而不是FragmentPagerAdapter 如果要在索引的基础上重新创建或重新加载片段 例如,如果要重新加载FirstFragment以外的片段,可以像这样检查实例和返回位置
public int getItemPosition(Object item) {
if(item instanceof FirstFragment){
return 0;
}
return POSITION_NONE;
}
答案 19 :(得分:0)
这是我的实现,其中包含来自@Bill Phillips的信息 除数据已更改外,大多数时候都会获得片段缓存。很简单,而且似乎工作正常。
MyFragmentStatePagerAdapter.java
private boolean mIsUpdating = false;
public void setIsUpdating(boolean mIsUpdating) {
this.mIsUpdating = mIsUpdating;
}
@Override
public int getItemPosition(@NonNull Object object) {
if (mIsUpdating) {
return POSITION_NONE;
}
else {
return super.getItemPosition(object);
}
}
MyActivity.java
mAdapter.setIsUpdating(true);
mAdapter.notifyDataSetChanged();
mAdapter.setIsUpdating(false);
答案 20 :(得分:0)
您需要更改InstantiateItem的mFragments元素getItemPosition。
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
int newPosition = getItemPosition(f);
if (newPosition == POSITION_UNCHANGED) {
return f;
} else if (newPosition == POSITION_NONE) {
mFragments.set(position, null);
} else {
mFragments.set(newPosition, f);
}
}
}
基于AndroidX FragmentStatePagerAdapter.java,因为在调用notifyDataSetChanged()时mFragments
的元素位置不会改变。
您可以运行此项目以确认工作方式。 https://github.com/cuichanghao/infivt