我正在使用兼容性库中的ViewPager。我已经成功地让它显示几个视图,我可以通过页面。
但是,我很难弄清楚如何用一组新的视图更新ViewPager。
我已经尝试了各种各样的事情,比如调用mAdapter.notifyDataSetChanged(), mviewpage .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) {
}
}
}
我有同样的问题,我的解决方案是使用FragmentPagerAdapter覆盖FragmentPagerAdapter#getItemId(int位置):
@Override
public long getItemId(int position) {
return mPages.get(position).getId();
}
默认情况下,此方法返回项目的位置。我认为ViewPager检查itemId是否被更改,只有当它被更改时才重新创建页面。但是未覆盖的版本返回与itemId相同的位置,即使页面实际上不同,ViewPager没有定义页面被替换并需要重新创建。
要使用此功能,每个页面都需要长id。通常,它应该是唯一的,但我建议,对于这种情况,它应该不同于相同页面的前一个值。因此,在适配器或随机整数(具有广泛的分布)中使用连续计数器是可能的。
我认为这是更一致的方式,而不是使用标签的视图作为解决方案,在这个主题中提到。但可能不是所有情况都适用。
我认为我已经创建了一种简单的方法来通知数据集的更改:
首先,稍微改变一下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);
就是这样。
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();
}
实际上,您可以在instantiateItem和notifyDataSetChanged中使用类似的代码来刷新视图。在我的代码中,我使用了完全相同的方法。
我认为PagerAdapter中没有任何类型的错误。问题是,理解它是如何工作的有点复杂。看看这里解释的解决方案,从我的角度来看,有一个误解,因此实例化视图的使用很差。
在过去的几天里,我一直在使用PagerAdapter和ViewPager,我发现了以下内容:
PagerAdapter上的notifyDataSetChanged()方法只会通知ViewPager底层页面已经更改。例如,如果您动态地创建/删除页面(从列表中添加或删除项目),ViewPager应该负责这些。在这种情况下,我认为ViewPager决定是否应该使用getItemPosition()和getCount()方法删除或实例化一个新视图。
我认为ViewPager,在notifyDataSetChanged()调用后,它的子视图和检查他们的位置与getItemPosition()。如果对于子视图,该方法返回POSITION_NONE, ViewPager会认为该视图已被删除,并调用destroyItem()方法删除该视图。
通过这种方式,如果您只想更新页面的内容,覆盖getItemPosition()以总是返回POSITION_NONE是完全错误的,因为每次调用notifyDatasetChanged()时,以前创建的视图将被销毁,而新的视图将被创建。对于一些textview来说,这似乎没有什么问题,但是当你有复杂的视图时,比如从数据库中填充的listview,这可能是一个真正的问题和资源浪费。
So there are several approaches to efficiently change the content of a view without having to remove and instantiate the view again. It depends on the problem you want to solve. My approach is to use the setTag() method for any instantiated view in the instantiateItem() method. So when you want to change the data or invalidate the view that you need, you can call the findViewWithTag() method on the ViewPager to retrieve the previously instantiated view and modify/use it as you want without having to delete/create a new view each time you want to update some value.
例如,假设您有100个带有100个textview的页面,并且您只想定期更新一个值。使用前面解释的方法,这意味着您在每次更新时删除并实例化100个textview。这没有道理……