为什么' PagerAdapter::notifyDataSetChanged '不更新视图?

我正在使用兼容性库中的ViewPager。我已经成功地让它显示几个视图,我可以通过页面。

但是,我很难弄清楚如何用一组新的视图更新ViewPager。

我尝试了各种各样的方法,比如调用mAdapter.notifyDataSetChanged()mViewPager.invalidate(),甚至在每次我想使用新的数据列表时创建一个全新的适配器。

没有任何帮助,textviews保持不变,从原始数据。

< >强更新: 我做了一个小测试项目,我几乎能够更新视图。我将粘贴下面的类

然而,似乎没有更新的是第二个视图,“B”仍然存在,它应该在按下更新按钮后显示“Y”。

public class ViewPagerBugActivity extends Activity {


private ViewPager myViewPager;
private List<String> data;


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


data = new ArrayList<String>();
data.add("A");
data.add("B");
data.add("C");


myViewPager = (ViewPager) findViewById(R.id.my_view_pager);
myViewPager.setAdapter(new MyViewPagerAdapter(this, data));


Button updateButton = (Button) findViewById(R.id.update_button);
updateButton.setOnClickListener(new OnClickListener() {


@Override
public void onClick(View v) {
updateViewPager();
}
});
}


private void updateViewPager() {
data.clear();
data.add("X");
data.add("Y");
data.add("Z");
myViewPager.getAdapter().notifyDataSetChanged();
}


private class MyViewPagerAdapter extends PagerAdapter {


private List<String> data;
private Context ctx;


public MyViewPagerAdapter(Context ctx, List<String> data) {
this.ctx = ctx;
this.data = data;
}


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


@Override
public Object instantiateItem(View collection, int position) {
TextView view = new TextView(ctx);
view.setText(data.get(position));
((ViewPager)collection).addView(view);
return view;
}


@Override
public void destroyItem(View collection, int position, Object view) {
((ViewPager) collection).removeView((View) view);
}


@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}


@Override
public Parcelable saveState() {
return null;
}


@Override
public void restoreState(Parcelable arg0, ClassLoader arg1) {
}


@Override
public void startUpdate(View arg0) {
}


@Override
public void finishUpdate(View arg0) {
}
}
}
428313 次浏览

有几种方法可以实现这一点。

第一种方法更简单,但效率更低。

像这样覆盖getItemPosition在你的PagerAdapter:

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

这样,当您调用notifyDataSetChanged()时,视图寻呼机将删除所有视图并重新加载它们。这样就得到了装填效果。

第二个选项由阿尔瓦罗·路易斯·布斯塔曼特建议(以前是alvarolb)是在实例化一个新视图时到instantiateItem()中的setTag()方法。然后,您可以使用findViewWithTag()来查找要更新的视图,而不是使用notifyDataSetChanged()

结论

如果你有很多视图,或者想要支持修改任何特定的项目和/或视图(快速在任何时候),那么第二种方法(标记)是非常灵活和高性能的,因为它可以防止重新创建所有的修改视图。
(alvarolb的原始研究值得称赞)

但如果你的应用只有一个“刷新”;功能(甚至不允许单个项目更改),或者只有少数项目,使用第一种方法,因为它节省了开发时间。

我只是把这个答案贴出来,以防其他人觉得有用。为了做完全相同的事情,我只是从兼容性库中获取了ViewPager和PagerAdapter的源代码,并在我的代码中编译它(您需要整理所有错误并导入,但这肯定是可以做到的)。

然后,在CustomViewPager中,创建一个名为updateViewAt(int position)的方法。视图本身可以从ViewPager类中定义的ArrayList mItems中获得(您需要在实例化项目时为视图设置一个Id,并将这个Id与updateViewAt()方法中的位置进行比较)。然后可以根据需要更新视图。

我遇到过类似的问题,我有四个页面,其中一个页面更新了其他三个页面的视图。我能够更新widget (SeekBars, TextViews等)上的页面相邻的当前页面。最后两个页面在调用mTabsAdapter.getItem(position)时将有未初始化的小部件。

为了解决我的问题,我在调用getItem(position)之前使用了setSelectedPage(index)。这将实例化页面,使我能够更改每个页面上的值和小部件。

在所有的更新之后,我将使用setSelectedPage(position)后面跟着notifyDataSetChanged()

您可以在主更新页面的ListView中看到轻微的闪烁,但并不明显。我还没有完全测试它,但它确实解决了我眼前的问题。

我不认为PagerAdapter有任何漏洞。问题是,理解它是如何工作的有点复杂。看看这里解释的解决方案,从我的角度来看,有一个误解,因此实例化视图的使用很差。

在过去的几天里,我一直在使用PagerAdapterViewPager,我发现了以下内容:

PagerAdapter上的notifyDataSetChanged()方法将只通知ViewPager底层页面已更改。例如,如果您动态地创建/删除页面(从列表中添加或删除项目),ViewPager应该负责这一点。在这种情况下,我认为ViewPager决定是否应该删除一个新的视图或使用getItemPosition()getCount()方法实例化。

我认为ViewPager,在一个notifyDataSetChanged()调用之后,它的子视图,并检查他们与getItemPosition()的位置。如果对于子视图,该方法返回POSITION_NONE,则ViewPager将理解该视图已被删除,调用destroyItem()并删除该视图。

通过这种方式,如果您只想更新页面的内容,那么重写getItemPosition()以总是返回POSITION_NONE是完全错误的,因为每次调用notifyDatasetChanged()时,以前创建的视图将被销毁,而新的视图将被创建。对于一些# eyz3似乎没有什么问题,但是当您有复杂的视图时,比如从数据库中填充的listview,这可能是一个真正的问题和资源浪费。

因此,有几种方法可以有效地更改视图的内容,而不必再次删除和实例化视图。这取决于你想解决的问题。# EYZ4

假设您有100个页面,包含100个# eyz0,并且您只想定期更新一个值。使用前面解释的方法,这意味着您在每次更新时删除并实例化100个# eyz0。这没有道理……

在对这个问题进行了大量的搜索之后,我找到了一个非常好的解决方案,我认为这是正确的方法。实际上,instantiateItem只在视图被实例化时被调用,除非视图被销毁,否则不会再次被调用(这就是当你重写getItemPosition函数以返回POSITION_NONE时发生的情况)。相反,您要做的是保存创建的视图,并在适配器中更新它们,生成一个get函数以便其他人可以更新它,或一个set函数更新适配器(我最喜欢的)。

所以,在你的MyViewPagerAdapter中添加一个变量:

private View updatableView;

在你的instantiateItem:

 public Object instantiateItem(View collection, int position) {
updatableView = new TextView(ctx); //My change is here
view.setText(data.get(position));
((ViewPager)collection).addView(view);
return view;
}

通过这种方式,你可以创建一个函数来更新你的视图:

public updateText(String txt)
{
((TextView)updatableView).setText(txt);
}

希望这能有所帮助!

在更新视图之前触发mTabsAdapter.onTabChanged(mTabHost.getCurrentTabTag());。这是可行的。

alvarolb给出的这个问题的答案绝对是最好的方法。基于他的回答,实现这一点的一个简单方法是简单地按位置存储活动视图:

SparseArray<View> views = new SparseArray<View>();


@Override
public Object instantiateItem(View container, int position) {
View root = <build your view here>;
((ViewPager) container).addView(root);
views.put(position, root);
return root;
}


@Override
public void destroyItem(View collection, int position, Object o) {
View view = (View)o;
((ViewPager) collection).removeView(view);
views.remove(position);
view = null;
}

然后,一旦覆盖了notifyDataSetChanged方法,您就可以刷新视图…

@Override
public void notifyDataSetChanged() {
int key = 0;
for(int i = 0; i < views.size(); i++) {
key = views.keyAt(i);
View view = views.get(key);
<refresh view with new data>
}
super.notifyDataSetChanged();
}

实际上,您可以使用instantiateItemnotifyDataSetChanged中的类似代码来刷新视图。在我的代码中,我使用了完全相同的方法。

一个更简单的方法:使用FragmentPagerAdapter,并将页面视图包装到片段中。它们确实会更新

我实际上使用notifyDataSetChanged()ViewPagerCirclePageIndicator之后,我调用destroyDrawingCache()ViewPager和它工作。其他的解决方法对我都不起作用。

为了防止任何人使用基于FragmentStatePagerAdapter的适配器(这将让ViewPager创建显示所需的最小页面,对于我的情况最多2个页面),@rui。araujo在适配器中覆盖getItemPosition的答案不会造成重大浪费,但仍然可以改进。

在伪代码中:

public int getItemPosition(Object object) {
YourFragment f = (YourFragment) object;
YourData d = f.data;
logger.info("validate item position on page index: " + d.pageNo);


int dataObjIdx = this.dataPages.indexOf(d);


if (dataObjIdx < 0 || dataObjIdx != d.pageNo) {
logger.info("data changed, discard this fragment.");
return POSITION_NONE;
}


return POSITION_UNCHANGED;
}

而不是返回POSITION_NONE并再次创建所有片段,您可以执行我在这里建议的操作:动态更新ViewPager ?

< p > 1。首先,您必须在Pageradapter类中设置getItemposition方法 2.您必须阅读您的视图寻呼机的确切位置 3.然后将该位置作为新位置的数据位置发送 4.在setonPageChange监听器

中写入更新按钮的点击监听器

我修改了程序代码,只设置了特定的位置元素

public class MyActivity extends Activity {


private ViewPager myViewPager;
private List<String> data;
public int location=0;
public Button updateButton;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);


data = new ArrayList<String>();
data.add("A");
data.add("B");
data.add("C");
data.add("D");
data.add("E");
data.add("F");


myViewPager = (ViewPager) findViewById(R.id.pager);
myViewPager.setAdapter(new MyViewPagerAdapter(this, data));


updateButton = (Button) findViewById(R.id.update);


myViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int i, float v, int i2) {
//Toast.makeText(MyActivity.this, i+"  Is Selected  "+data.size(), Toast.LENGTH_SHORT).show();
}


@Override
public void onPageSelected( int i) {
// here you will get the position of selected page
final int k = i;
updateViewPager(k);


}


@Override
public void onPageScrollStateChanged(int i) {


}
});
}


private void updateViewPager(final int i) {
updateButton.setOnClickListener(new OnClickListener() {


@Override
public void onClick(View v) {


Toast.makeText(MyActivity.this, i+"  Is Selected  "+data.size(), Toast.LENGTH_SHORT).show();
data.set(i, "Replaced "+i);
myViewPager.getAdapter().notifyDataSetChanged();
}
});


}


private class MyViewPagerAdapter extends PagerAdapter {


private List<String> data;
private Context ctx;


public MyViewPagerAdapter(Context ctx, List<String> data) {
this.ctx = ctx;
this.data = data;
}


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


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


@Override
public Object instantiateItem(View collection, int position) {


TextView view = new TextView(ctx);
view.setText(data.get(position));
((ViewPager)collection).addView(view);
return view;
}


@Override
public void destroyItem(View collection, int position, Object view) {
((ViewPager) collection).removeView((View) view);
}


@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}


@Override
public Parcelable saveState() {
return null;
}


@Override
public void restoreState(Parcelable arg0, ClassLoader arg1) {
}


@Override
public void startUpdate(View arg0) {
}


@Override
public void finishUpdate(View arg0) {
}
}
}
所有这些解决方法对我没有帮助。于是我找到了一个可行的解决方案: 你可以每次都setAdapter,但这是不够的。 您应该在更改适配器之前执行以下操作
FragmentManager fragmentManager = slideShowPagerAdapter.getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
List<Fragment> fragments = fragmentManager.getFragments();
for (Fragment f : fragments) {
transaction.remove(f);
}
transaction.commit();

在这之后:

viewPager.setAdapter(adapter);

在OP提出他的问题两年半之后,这个问题仍然,嗯,仍然是一个问题。显然谷歌在这方面的优先级不是特别高,所以我没有找到解决方案,而是找到了一个变通办法。对我来说,最大的突破是找到了问题的真正原因(见这篇文章中公认的答案)。一旦问题明显是任何活动页面都没有正确刷新,我的解决方法就很明显了:

在我的片段(几页)中:

  • 我把所有的代码从onCreateView中填充表单,并把它放在一个名为PopulateForm的函数中,该函数可以从任何地方调用,而不是由框架调用。该函数尝试使用getView获取当前视图,如果该值为空,则返回。重要的是,PopulateForm只包含显示的代码-所有其他代码,创建FocusChange监听器和类似的仍然在OnCreate
  • 创建一个布尔值,用于指示表单必须重新加载。我的是mbReloadForm
  • 如果设置了mbReloadForm,重写OnResume()来调用PopulateForm()。

在我的活动中,我做页面的加载:

  • 在更改任何内容之前,请转到第0页。我使用FragmentStatePagerAdapter,所以我知道最多会有两三个页面受到影响。更改到第0页可以确保我只在第0页、第1页和第2页出现问题。
  • 在清除旧的列表之前,取它的size()。这样您就可以知道有多少页面受到了错误的影响。如果>为3,则将其减少为3 -如果您使用的是不同的PagerAdapter,则必须查看必须处理多少页面(可能是全部?)
  • 重新加载数据并调用pageAdapter.notifyDataSetChanged()
  • 现在,对于每个受影响的页面,通过使用page . getchildat (i)查看页面是否处于活动状态——这将告诉您是否有一个视图。如果是,调用page . populateview()。如果不是,设置ReloadForm标志。

在此之后,当您重新加载第二组页面时,该错误仍然会导致一些页面显示旧数据。但是,现在它们将被刷新,您将看到新的数据-您的用户不会知道页面是不正确的,因为这种刷新将在他们看到页面之前发生。

希望这能帮助到一些人!

经过数小时的挫折,同时尝试所有上述解决方案来克服这个问题,也尝试了其他类似问题的许多解决方案,如,这些都失败了,我解决这个问题,并使ViewPager破坏旧的Fragment,用新的Fragments填充pager。我解决的问题如下:

使ViewPager类扩展FragmentPagerAdapter,如下所示:

 public class myPagerAdapter extends FragmentPagerAdapter {

ViewPager创建一个项目,存储titlefragment,如下所示:

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中,如下所示:

private FragmentManager mFragmentManager;
private ArrayList<PagerItem> mPagerItems;


public MyPagerAdapter(FragmentManager fragmentManager, ArrayList<PagerItem> pagerItems) {
super(fragmentManager);
mFragmentManager = fragmentManager;
mPagerItems = pagerItems;
}

创建一个方法,通过删除fragmentManager本身中所有之前的fragment来重新设置adapter数据与新数据,使adapter再次从新列表中设置新的fragment,如下所示:

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;
}

容器中的ActivityFragment不使用新数据重新初始化适配器。使用方法setPagerItems设置新数据,如下所示:

ArrayList<PagerItem> pagerItems = new ArrayList<PagerItem>();
pagerItems.add(new PagerItem("Fragment1", new MyFragment1()));
pagerItems.add(new PagerItem("Fragment2", new MyFragment2()));


mPagerAdapter.setPagerItems(pagerItems);
mPagerAdapter.notifyDataSetChanged();

我希望这能有所帮助。

我认为我已经创建了一种简单的方法来通知数据集的更改:

首先,稍微改变一下instantiateItem函数的工作方式:

    @Override
public Object instantiateItem(final ViewGroup container, final int position) {
final View rootView = mInflater.inflate(...,container, false);
rootView.setTag(position);
updateView(rootView, position);
container.addView(rootView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
mViewPager.setObjectForPosition(rootView, position);
return rootView;
}

对于“updateView”,用你想要填充的所有数据填充视图(setText,setBitmapImage,…)

验证destroyView是这样工作的:

    @Override
public void destroyItem(final ViewGroup container, final int position, final Object obj) {
final View viewToRemove = (View) obj;
mViewPager.removeView(viewToRemove);
}

现在,假设你需要更改数据,然后调用PagerAdapter上的下一个函数:

    public void notifyDataSetChanged(final ViewPager viewPager, final NotifyLocation fromPos,
final NotifyLocation toPos) {
final int offscreenPageLimit = viewPager.getOffscreenPageLimit();
final int fromPosInt = fromPos == NotifyLocation.CENTER ? mSelectedPhotoIndex
: fromPos == NotifyLocation.MOST_LEFT ? mSelectedPhotoIndex - offscreenPageLimit
: mSelectedPhotoIndex + offscreenPageLimit;
final int toPosInt = toPos == NotifyLocation.CENTER ? mSelectedPhotoIndex
: toPos == NotifyLocation.MOST_LEFT ? mSelectedPhotoIndex - offscreenPageLimit
: mSelectedPhotoIndex + offscreenPageLimit;
if (fromPosInt <= toPosInt) {
notifyDataSetChanged();
for (int i = fromPosInt; i <= toPosInt; ++i) {
final View pageView = viewPager.findViewWithTag(i);
mPagerAdapter.updateView(pageView, i);
}
}
}


public enum NotifyLocation {
MOST_LEFT, CENTER, MOST_RIGHT
}

例如,如果你希望通知viewPager所显示的所有视图,有一些东西发生了变化,你可以调用:

notifyDataSetChanged(mViewPager,NotifyLocation.MOST_LEFT,NotifyLocation.MOST_RIGHT);

就是这样。

FragmentPagerAdapter改为FragmentStatePagerAdapter

覆盖getItemPosition()方法并返回POSITION_NONE

最终,它将侦听视图分页器上的notifyDataSetChanged()

对我有用的是viewPager.getAdapter().notifyDataSetChanged();

在适配器中,将用于更新视图的代码放在getItemPosition中,如下所示

@Override
public int getItemPosition(Object object) {


if (object instanceof YourViewInViewPagerClass) {
YourViewInViewPagerClass view = (YourViewInViewPagerClass)object;
view.setData(data);
}


return super.getItemPosition(object);
}

可能不是最正确的方法,但它起作用了(return POSITION_NONE技巧导致了我的崩溃,所以不是一个选择)

无论如何,在KitKat+上,adapter.notifyDataSetChanged()似乎足以导致新视图显示,只要你有setOffscreenPageLimit足够高。我可以通过执行viewPager.setOffscreenPageLimit(2)来获得所需的行为。

我也有同样的问题。对我来说,它可以扩展FragmentStatePagerAdapter,并覆盖以下方法:

@Override
public Parcelable saveState() {
return null;
}


@Override
public void restoreState(Parcelable state, ClassLoader loader) {


}

你可以动态更新所有的片段,你可以在三个步骤中看到。

在适配器中:

public class MyPagerAdapter extends FragmentPagerAdapter {
private static int NUM_ITEMS = 3;
private Map<Integer, String> mFragmentTags;
private FragmentManager mFragmentManager;


public MyPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
mFragmentManager = fragmentManager;
mFragmentTags = new HashMap<Integer, String>();
}


// Returns total number of pages
@Override
public int getCount() {
return NUM_ITEMS;
}


// Returns the fragment to display for that page
@Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return FirstFragment.newInstance();
case 1:
return SecondFragment.newInstance();
case 2:
return ThirdFragment.newInstance();
default:
return null;
}
}


// Returns the page title for the top indicator
@Override
public CharSequence getPageTitle(int position) {
return "Page " + position;
}


@Override
public Object instantiateItem(ViewGroup container, int position) {
Object object = super.instantiateItem(container, position);
if (object instanceof Fragment) {
Fragment fragment = (Fragment) object;
String tag = fragment.getTag();
mFragmentTags.put(position, tag);
}
return object;
}


public Fragment getFragment(int position) {
Fragment fragment = null;
String tag = mFragmentTags.get(position);
if (tag != null) {
fragment = mFragmentManager.findFragmentByTag(tag);
}
return fragment;
}}

现在在你的活动中:

public class MainActivity extends AppCompatActivity implements ViewPager.OnPageChangeListener{


MyPagerAdapter mAdapterViewPager;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewPager viewPager = (ViewPager) findViewById(R.id.vpPager);
mAdapterViewPager = new MyPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(mAdapterViewPager);
viewPager.addOnPageChangeListener(this);
}


@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {


}


@Override
public void onPageSelected(int position) {


Fragment fragment = mAdapterViewPager.getFragment(position);
if (fragment != null) {
fragment.onResume();
}
}


@Override
public void onPageScrollStateChanged(int state) {


}}

最后在你的片段中,是这样的:

public class YourFragment extends Fragment {


// newInstance constructor for creating fragment with arguments
public static YourFragment newInstance() {


return new YourFragment();
}


// Store instance variables based on arguments passed
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}


// Inflate the view for the fragment based on layout XML
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment, container, false);
}




@Override
public void onResume() {
super.onResume();


//to refresh your view
refresh();


}}

您可以看到完整的代码在这里

谢谢布斯塔曼特。

总是返回POSITION_NONE是一种简单但效率有点低的方法,因为这会触发所有已经实例化的页面的实例化。

我已经创建了图书馆ArrayPagerAdapter来动态更改PagerAdapters中的项。

在内部,这个库的适配器仅在必要时才在getItemPosiition()上返回POSITION_NONE

您可以使用此库动态更改项,如下面所示。

@Override
protected void onCreate(Bundle savedInstanceState) {
/** ... **/
adapter = new MyStatePagerAdapter(getSupportFragmentManager()
, new String[]{"1", "2", "3"});
((ViewPager)findViewById(R.id.view_pager)).setAdapter(adapter);
adapter.add("4");
adapter.remove(0);
}


class MyPagerAdapter extends ArrayViewPagerAdapter<String> {


public MyPagerAdapter(String[] data) {
super(data);
}


@Override
public View getView(LayoutInflater inflater, ViewGroup container, String item, int position) {
View v = inflater.inflate(R.layout.item_page, container, false);
((TextView) v.findViewById(R.id.item_txt)).setText(item);
return v;
}
}

Thils库还支持由Fragments创建的页面。

这是一个可怕的问题,我很乐意提出一个极好的解决方案;简单、高效、有效!

如下所示,代码显示使用标志指示返回POSITION_NONE

public class ViewPagerAdapter extends PagerAdapter
{
// Members
private boolean mForceReinstantiateItem = false;


// This is used to overcome terrible bug that Google isn't fixing
// We know that getItemPosition() is called right after notifyDataSetChanged()
// Therefore, the fix is to return POSITION_NONE right after the notifyDataSetChanged() was called - but only once
@Override
public int getItemPosition(Object object)
{
if (mForceReinstantiateItem)
{
mForceReinstantiateItem = false;
return POSITION_NONE;
}
else
{
return super.getItemPosition(object);
}
}


public void setData(ArrayList<DisplayContent> newContent)
{
mDisplayContent = newContent;
mForceReinstantiateItem = true;
notifyDataSetChanged();
}


}

我把我自己的解决方案留在这里,这是一个变通办法,因为似乎问题是FragmentPagerAdapter不清理之前的fragments,你可以添加到ViewPager,在片段管理器。所以,在添加FragmentPagerAdapter之前,我创建了一个方法来执行:

(在我的情况下,我从来没有添加超过3个片段,但你可以使用例如getFragmentManager().getBackStackEntryCount()和检查所有的fragments

/**
* this method is solving a bug in FragmentPagerAdapter which don't delete in the fragment manager any previous fragments in a ViewPager.
*
* @param containerId
*/
public void cleanBackStack(long containerId) {
FragmentTransaction transaction = getFragmentManager().beginTransaction();
for (int i = 0; i < 3; ++i) {
String tag = "android:switcher:" + containerId + ":" + i;
Fragment f = getFragmentManager().findFragmentByTag(tag);
if (f != null) {
transaction.remove(f);
}
}
transaction.commit();
}

我知道这是一种变通方法,因为如果框架创建标签的方式发生变化,它将停止工作。

(目前# EYZ0)

那么使用方法是在得到容器之后:

ViewPager viewPager = (ViewPager) view.findViewById(R.id.view_pager);
cleanBackStack(viewPager.getId());

在我的情况下,每次片段输入onCreateView,我重新启动值,所以处理null或空值如下:

if (var == null) {
var = new Something();
}


if (list.isEmpty()) {
list = new ArrayList<MyItem>();
}


if (myAdapter == null) {
myAdapter = new CustomAdapter();
recyclerView.setAdapter(myAdapter);
}


// etc...

Fragment类可以在每次更改时进入onCreateView,并在运行时从UI返回到视图。

感谢瑞。阿劳霍和阿尔瓦罗路易斯布斯塔曼特。首先,我尝试使用rui。阿劳霍的方法,因为很简单。它可以工作,但当数据发生变化时,页面会明显重绘。这很糟糕,所以我试着用布斯塔曼特的方法。它是完美的。代码如下:

@Override
protected void onStart() {
super.onStart();
}


private class TabPagerAdapter extends PagerAdapter {
@Override
public int getCount() {
return 4;
}


@Override
public boolean isViewFromObject(final View view, final Object object) {
return view.equals(object);
}


@Override
public void destroyItem(final View container, final int position, final Object object) {
((ViewPager) container).removeView((View) object);
}


@Override
public Object instantiateItem(final ViewGroup container, final int position) {
final View view = LayoutInflater.from(
getBaseContext()).inflate(R.layout.activity_approval, null, false);
container.addView(view);
ListView listView = (ListView) view.findViewById(R.id.list_view);
view.setTag(position);
new ShowContentListTask(listView, position).execute();
return view;
}
}

当数据发生变化时:

for (int i = 0; i < 4; i++) {
View view = contentViewPager.findViewWithTag(i);
if (view != null) {
ListView listView = (ListView) view.findViewById(R.id.list_view);
new ShowContentListTask(listView, i).execute();
}
}

我使用Tablayout与ViewPagerAdapter。为了在片段之间传递数据或在片段之间进行通信,使用下面的代码,它工作得非常好,并在片段出现时刷新它。第二段点击按钮里面写下面的代码。

b.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {


String text=e1.getText().toString(); // get the text from EditText


// move from one fragment to another fragment on button click
TabLayout tablayout = (TabLayout) getActivity().findViewById(R.id.tab_layout); // here tab_layout is the id of TabLayout which is there in parent Activity/Fragment
if (tablayout.getTabAt(1).isSelected()) { // here 1 is the index number of second fragment i-e current Fragment


LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getContext());
Intent i = new Intent("EDIT_TAG_REFRESH");
i.putExtra("MyTextValue",text);
lbm.sendBroadcast(i);


}
tablayout.getTabAt(0).select(); // here 0 is the index number of first fragment i-e to which fragment it has to moeve


}
});

下面是必须在第一个片段(在我的情况下)i-e中接收片段的代码。

MyReceiver r;
Context context;
String newValue;
public void refresh() {
//your code in refresh.
Log.i("Refresh", "YES");
}
public void onPause() {
super.onPause();


LocalBroadcastManager.getInstance(context).unregisterReceiver(r);
}
public void onResume() {
super.onResume();
r = new MyReceiver();
LocalBroadcastManager.getInstance(getActivity()).registerReceiver(r,
new IntentFilter("EDIT_TAG_REFRESH"));
} // this code has to be written before onCreateview()




// below code can be written any where in the fragment
private class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
PostRequestFragment.this.refresh();
String action = intent.getAction();
newValue=intent.getStringExtra("MyTextValue");
t1.setText(newValue); // upon Referesh set the text
}
}
我发现这个问题的决定很有趣。 我们可以使用FragmentStatePagerAdapter (< em > android.support.v4.app.FragmentStatePagerAdapter < / em >),而不是使用FragmentPagerAdapter,它保留在内存中所有的片段,当我们每次选择它时,它都会重新加载片段

两个适配器的实现是相同的。因此,我们只需要在延长FragmentStatePagerAdapter上更改延长FragmentPagerAdapter

我想,我有ViewPager的逻辑。

如果我需要刷新一组页面并根据新的数据集显示它们,我调用notifyDataSetChanged ()。 然后,ViewPager对取得()进行多次调用,将Fragment作为对象传递给那里。这个片段可以来自旧数据集(我想要丢弃的数据集),也可以来自新数据集(我想要显示的数据集)。所以,我重写取得(),在那里我必须以某种方式确定我的片段是来自旧数据集还是来自新数据集。< / p > 在我的例子中,我有一个2窗格布局,左边窗格上有一个顶部项目的列表,右边是一个滑动视图(ViewPager)。因此,我在我的PagerAdapter和每个实例化的页面片段中存储了一个到我当前顶部项目的链接。 当列表中选中的顶部项发生变化时,我将新的顶部项存储在PagerAdapter中,并调用notifyDataSetChanged ()。在覆盖的取得()中,我比较了适配器的顶部项目和片段的顶部项目。 只有当它们不相等时,我才返回POSITION_NONE。 然后,PagerAdapter重新实例化所有返回POSITION_NONE.

的片段

存储top item id而不是引用可能是一个更好的主意。

下面的代码片段有点示意图,但我从实际工作的代码中改编了它。

public class SomeFragment extends Fragment {
private TopItem topItem;
}


public class SomePagerAdapter extends FragmentStatePagerAdapter {
private TopItem topItem;


public void changeTopItem(TopItem newTopItem) {
topItem = newTopItem;
notifyDataSetChanged();
}


@Override
public int getItemPosition(Object object) {
if (((SomeFragment) object).getTopItemId() != topItem.getId()) {
return POSITION_NONE;
}
return super.getItemPosition(object);
}
}

感谢之前所有的研究人员!

对我有用的是重写finishUpdate方法,它在转换结束时被调用,并在那里执行notifyDataSetChanged():

public class MyPagerAdapter extends FragmentPagerAdapter {
...
@Override
public void finishUpdate(ViewGroup container) {
super.finishUpdate(container);
this.notifyDataSetChanged();
}
...
}
这是为所有像我这样的人,需要从服务(或其他后台线程)更新Viewpager,没有一个建议已经工作: 经过一些日志检查,我意识到,notifyDataSetChanged()方法永远不会返回。getItemPosition(对象对象)被称为没有进一步处理的所有结束。然后我在父PagerAdapter类的docs中发现(不是在子类的docs中),“数据集更改必须发生在主线程上,必须以调用notifyDataSetChanged()结束。". 因此,本例中的工作解决方案是(使用FragmentStatePagerAdapter) 和getItemPosition(对象对象)设置为返回POSITION_NONE):

然后调用notifyDataSetChanged():

runOnUiThread(new Runnable() {
@Override
public void run() {
pager.getAdapter().notifyDataSetChanged();
}
});

我也有同样的问题,我的解决方案是使用FragmentPagerAdapter覆盖FragmentPagerAdapter#getItemId(int position):

@Override
public long getItemId(int position) {
return mPages.get(position).getId();
}

默认情况下,此方法返回项目的位置。我假设ViewPager检查itemId是否被更改,并仅在更改时重新创建页面。但是非覆盖版本返回与itemId相同的位置,即使页面实际上不同,并且ViewPager没有定义页面被替换并需要重新创建。

要使用它,每个页面都需要long id。通常,它应该是唯一的,但我建议,对于这种情况,它应该不同于相同页面的前一个值。因此,在适配器或随机整数(具有广泛的分布)中使用连续计数器是可能的。

我认为这是更一致的方式,而不是使用标签的视图作为解决方案,在这个主题中提到。但可能不是所有情况都适用。

你可以像这样在Viewpager上添加分页器转换

myPager.setPageTransformer(true, new MapPagerTransform());

在下面的代码中,我改变了我的视图颜色在运行时,当寻呼机滚动

public class MapPagerTransform implements ViewPager.PageTransformer {


public void transformPage(View view, float position) {
LinearLayout showSelectionLL = (LinearLayout) view.findViewById(R.id.showSelectionLL);


if (position < 0) {
showSelectionLL.setBackgroundColor(Color.WHITE);
} else if (position > 0) {
showSelectionLL.setBackgroundColor(Color.WHITE);
} else {
showSelectionLL.setBackgroundColor(Color.RED);
}
}
}

下面的代码对我有用。

创建一个扩展FragmentPagerAdapter类的类,如下所示。

public class Adapter extends FragmentPagerAdapter {


private int tabCount;
private Activity mActivity;
private Map<Integer, String> mFragmentTags;
private FragmentManager mFragmentManager;
private int container_id;
private ViewGroup container;
private List<Object> object;


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


public Adapter(FragmentManager fm, int numberOfTabs , Activity mA) {
super(fm);
mActivity = mA;
mFragmentManager = fm;
object = new ArrayList<>();
mFragmentTags = new HashMap<Integer, String>();
this.tabCount = numberOfTabs;
}


@Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return Fragment0.newInstance(mActivity);
case 1:
return Fragment1.newInstance(mActivity);
case 2:
return Fragment2.newInstance(mActivity);
default:
return null;
}}




@Override
public Object instantiateItem(ViewGroup container, int position) {
Object object = super.instantiateItem(container, position);
if (object instanceof Fragment) {
Log.e("Already defined","Yes");
Fragment fragment = (Fragment) object;
String tag = fragment.getTag();
Log.e("Fragment Tag","" + position + ", " + tag);
mFragmentTags.put(position, tag);
}else{
Log.e("Already defined","No");
}
container_id = container.getId();
this.container = container;
if(position == 0){
this.object.add(0,object);
}else if(position == 1){
this.object.add(1,object);
}else if(position == 2){
this.object.add(2,object);
}
return object;
}


@Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
if (object instanceof Fragment) {
Log.e("Removed" , String.valueOf(position));
}
}


@Override
public int getItemPosition (Object object)
{   int index = 0;
if(this.object.get(0) == object){
index = 0;
}else if(this.object.get(1) == object){
index = 1;
}else if(this.object.get(2) == object){
index = 2;
}else{
index = -1;
}
Log.e("Index" , "..................." + String.valueOf(index));
if (index == -1)
return POSITION_NONE;
else
return index;
}


public String getFragmentTag(int pos){
return "android:switcher:"+R.id.pager+":"+pos;
}


public void NotifyDataChange(){
this.notifyDataSetChanged();
}


public int getcontainerId(){
return container_id;
}


public ViewGroup getContainer(){
return this.container;
}


public List<Object> getObject(){
return this.object;
}


@Override
public int getCount() {
return tabCount;
}}

然后在您创建的每个Fragment中,创建一个updateFragment方法。在这个方法中,您可以更改片段中需要更改的内容。例如,在我的例子中,Fragment0包含了一个GLSurfaceView,它显示了一个基于.ply文件路径的3d对象,所以在我的updateFragment方法中,我改变了这个ply文件的路径。

然后创建一个ViewPager实例,

viewPager = (ViewPager) findViewById(R.id.pager);

和一个Adpater实例,

adapter = new Adapter(getSupportFragmentManager(), 3, this);

然后这样做,

viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(1);

然后在类内部初始化了上面的Adapter类并创建了一个viewPager,每次你想要更新你的一个片段(在我们的例子中是Fragment0)时,使用以下方法:

adapter.NotifyDataChange();


adapter.destroyItem(adapter.getContainer(), 0, adapter.getObject().get(0)); // destroys page 0 in the viewPager.


fragment0 = (Fragment0) getSupportFragmentManager().findFragmentByTag(adapter.getFragmentTag(0)); // Gets fragment instance used on page 0.


fragment0.updateFragment() method which include the updates on this fragment


adapter.instantiateItem(adapter.getContainer(), 0); // re-initialize page 0.

这个解决方案是基于Alvaro Luis Bustamante提出的技术。

重写getItemPosition()的正确方法:

@Override
public int getItemPosition(@NonNull Object object) {
if (!(object instanceof MyPageView)) {
// Should never happen
return super.getItemPosition(object);
}
MyDataObject dataObject = ((MyPageView) object).getData();
if (!(dataObject instanceof MyDataObject)) {
// Should never happen
return super.getItemPosition(object);
}
if (lstItems.contains(dataObject)) {
return POSITION_UNCHANGED;
}
return POSITION_NONE;
}

简而言之,您需要将用于绘制视图的数据附加/保存到视图。从那里,当调用getItemPosition()时,ViewPager可以确定当前显示的视图是否仍然存在于适配器上,并据此采取行动。

你只需要这段简单的代码:

代码:

private void updateAdapter() {
if (adapterViewPager != null) {
int from = vpMyViewPager.getCurrentItem() - vpMyViewPager.getOffscreenPageLimit();
int to = vpMyViewPager.getCurrentItem() + vpMyViewPager.getOffscreenPageLimit();
vpMyViewPager.removeAllViews();
for (int i = from; i <= to; i++) {
if (i < 0) {
continue;
}
adapterViewPager.instantiateItem(vpMyViewPager, i);
}
}
}

解释:

如果你没有改变ViewPageroffscreenPageLimit,它总是有3到4个子元素,这取决于你的方向。为了在其子程序中显示正确的内容,它使用适配器来获得正确的内容。 现在当你在你的ViewPager上调用removeAllViews()时,只有3到4个Views实际上是从Window的层次结构中删除的,而通过调用instantiateItem(ViewGroup viewPager, int index),你只会重新创建3到4个Views。然后一切恢复正常,滑动和滚动,ViewPager显示使用其适配器的内容。在所有情况下,它的子节点的数量在所有设备上都是不一样的,例如,如果你将offscreenPageLimit设置为5,它可能会有大约11到12个子节点,但这就是全部,它并不多。这是快。< / p >

根据我的经验,最好的解决方案是:https://stackoverflow.com/a/44177688/3118950,它覆盖了long getItemId()并返回唯一的ID,而不是默认位置。除了导入答案之外,还要注意旧的片段将保留在片段管理器中,以防总金额小于页面限制,并且当片段被替换时,onDetach()/onDestory()将不会被调用。

我知道我迟到了,但还是能帮到别人。我只是扩展了接受的答案,我也添加了评论。

答案本身说明它是低效的

为了让它只在需要时刷新,你可以这样做

private boolean refresh;


public void refreshAdapter() {
refresh = true;
notifyDataSetChanged();
}


@Override
public int getItemPosition(@NonNull Object object) {
if (refresh) {
refresh = false;
return POSITION_NONE;
} else {
return super.getItemPosition(object);
}
}

ViewPager不是为支持动态视图更改而设计的。

在寻找与https://issuetracker.google.com/issues/36956111https://issuetracker.google.com/issues/36956111#comment56相关的另一个bug时,我已经确认了这一点

这个问题有点老了,但是谷歌最近用ViewPager2解决了这个问题。 它将允许用标准的解决方案取代手工的(不需要维护并且可能有bug的)解决方案。它还可以防止像某些回答那样不必要地重新创建视图

对于ViewPager2的例子,您可以检查https://github.com/googlesamples/android-viewpager2

如果您想使用ViewPager2,您需要在构建中添加以下依赖项。Gradle文件:

  dependencies {
implementation 'androidx.viewpager2:viewpager2:1.0.0-beta02'
}

然后你可以替换你的ViewPager在你的xml文件:

    <androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />

在那之后,你需要在你的活动中用ViewPager2替换ViewPager

ViewPager2需要一个RecyclerView。适配器,或者FragmentStateAdapter,在你的例子中,它可以是RecyclerView。适配器

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;


import java.util.ArrayList;


public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {


private Context context;
private ArrayList<String> arrayList = new ArrayList<>();


public MyAdapter(Context context, ArrayList<String> arrayList) {
this.context = context;
this.arrayList = arrayList;
}


@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false);
return new MyViewHolder(view);
}


@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
holder.tvName.setText(arrayList.get(position));
}


@Override
public int getItemCount() {
return arrayList.size();
}


public class MyViewHolder extends RecyclerView.ViewHolder {
TextView tvName;


public MyViewHolder(@NonNull View itemView) {
super(itemView);
tvName = itemView.findViewById(R.id.tvName);
}
}
}

在使用TabLayout的情况下,你可以使用TabLayoutMediator:

        TabLayoutMediator tabLayoutMediator = new TabLayoutMediator(tabLayout, viewPager, true, new TabLayoutMediator.OnConfigureTabCallback() {
@Override
public void onConfigureTab(@NotNull TabLayout.Tab tab, int position) {
// configure your tab here
tab.setText(tabs.get(position).getTitle());
}
});


tabLayoutMediator.attach();

然后,您将能够通过修改适配器的数据并调用notifyDataSetChanged方法来刷新视图

所有这些答案对我都不起作用。

唯一对我有用的是,我必须再次将适配器设置为viewpager,然后它将刷新内容。

removeView(int pos)在我的PagerAdaper

public void removeView(int index) {
imageFileNames.remove(index);
notifyDataSetChanged();
}

无论我删除文件,我必须这样做

imagePagerAdapter.removeView(currentPosition);
viewPager.setAdapter(imagePagerAdapter);

编辑:

下面的方法是有效的,你可以使用下面的方法。

public void updateView(int pos){
viewPager.setAdapter(null);
imagePagerAdapter =new ImagePagerAdapter(YOUR_CONTEXT,YOUR_CONTENT);
viewPager.setAdapter(imagePagerAdapter);
viewPager.setCurrentItem(pos);
}

用你的上下文替换你的YOUR_CONTEXT,用你的内容名称替换你的内容,即更新列表或其他东西。

在我的情况下,在我的Viewpager中有一个textView,在mainActivity中单击按钮,我想改变textView的颜色并更新pagerAdapter。在按钮上单击“我在SharedPreference中保存了颜色”,并更新pagerAdapter,它可以更新从共享首选项中获取的颜色。我用下面的方式更新viewPager视图。

btn_purple.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int color = ContextCompat.getColor(mContext, R.color.colorPrimaryDark2);
editor.putInt("sahittoFontColor", color);
editor.apply();
toNotifyDatasetChanged();
}
});

现在更新方法:

 private  void  toNotifyDatasetChanged (){
if(viewPager!=null&& pagerAdapter!=null) {
viewPager.setAdapter(null);
viewPager.setAdapter(pagerAdapter);
           

}


}

我的pagerAdapter是:

pagerAdapter = new  Sahitto_ViewPagerAdapter (mContext, filenameParameter, 30, lineList);
viewPager.setAdapter(pagerAdapter);

并且在instantiateItem was(在PagerAdapter中):

    SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mContext);
int   bnfntcolor=settings.getInt("sahittoFontColor", 0);
if (bnfntcolor!=0){
textView.setTextColor(bnfntcolor);
}

因此,当我点击按钮,颜色立即改变在pagerAdapter的Textview。

快乐的编码。

在ViewPager2中,您可以重新初始化适配器,用新的视图刷新页导航列表。# EYZ0

好了,伙计们,我用instantiateItem找到了解决办法。11年后再看这个视频的人:

 viewModel.mImage.observe(this, imagePath -> {
int pos = binding.sliderIntro.getCurrentPagePosition();
adapter.editList(pos, imagePath);
if (binding.sliderIntro.findViewWithTag("" + pos) != null)
adapter.instantiateItem(binding.sliderIntro.findViewWithTag("" + pos), pos);
});

以及您的适配器:

 public void editList(int position, String imagePath) {
this.sliderItems.set(position, imagePath);
}

和:

  @Override
public void onBindViewHolder(IntroSliderAdapterViewHolder viewHolder, final int position) {
viewHolder.itemView.setTag("" + position);
       

}