关于android:ViewPager PagerAdapter不更新View

ViewPager PagerAdapter not updating the View

我正在使用兼容性库中的viewpager。我成功地让它显示了几个我可以翻阅的视图。

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

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

没有任何帮助,文本视图与原始数据保持不变。

更新:我做了一个小小的测试项目,几乎可以更新视图。我将把这门课贴在下面。

但是,没有更新的是第二个视图,"B"仍然存在,按下更新按钮后应显示"Y"。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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) {
        }
    }
}


实现这一点有几种方法。

第一种选择更容易,但效率更低。

在您的PagerAdapter中覆盖getItemPosition,如下所示:

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

这样,当您调用notifyDataSetChanged()时,视图寻呼机将删除所有视图并重新加载所有视图。因此,可以获得重新加载效果。

第二种选择,由Alvaro Luis Bustamante(以前叫Alvarolb)提出,是在实例化新视图时,使用instantiateItem()中的setTag()方法。然后,您可以使用findViewWithTag()来查找要更新的视图,而不是使用notifyDataSetChanged()

第二种方法是非常灵活和高性能的。对阿尔瓦罗布最初研究的赞誉。


我认为PagerAdapter中没有任何类型的bug。问题是,了解它的工作原理有点复杂。看看这里解释的解决方案,有一个误解,因此从我的观点来看,实例化视图的用法很差。

在过去的几天里,我一直在和PagerAdapterViewPager合作,发现了以下情况:

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

我认为,在notifyDataSetChanged()电话之后,ViewPager接受了孩子的意见,并与getItemPosition()核对了他们的立场。如果对于子视图,此方法返回POSITION_NONE,则ViewPager会理解该视图已被删除,调用destroyItem()并删除此视图。

这样,如果只想更新页面内容,则重写getItemPosition()以始终返回POSITION_NONE是完全错误的,因为以前创建的视图将被破坏,每次调用notifyDataSetChanged()时都会创建新的视图。仅仅对于一些TextViews来说,这似乎并没有那么错,但是当您有复杂的视图时,比如从数据库中填充的listview,这可能是一个真正的问题,并且浪费了资源。

因此,有几种方法可以有效地更改视图的内容,而不必再次删除和实例化视图。这取决于你想解决的问题。我的方法是对instantiateItem()方法中的任何实例化视图使用setTag()方法。因此,当您想更改数据或使所需的视图无效时,您可以调用ViewPager上的findViewWithTag()方法来检索先前实例化的视图,并根据需要修改/使用它,而不必每次更新某个值时都删除/创建新的视图。

例如,假设您有100个带有100个TextView的页面,并且您只希望定期更新一个值。使用前面介绍的方法,这意味着您要在每次更新时删除并实例化100个TextView。这没有意义…


FragmentPagerAdapter改为FragmentStatePagerAdapter

重写getItemPosition()方法,返回POSITION_NONE

最终,它将收听notifyDataSetChanged()的浏览寻呼机。


阿尔瓦罗布给出的答案无疑是最好的方法。基于他的答案,实现这一点的一个简单方法是简单地按位置存储活动视图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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方法,可以刷新视图…

1
2
3
4
5
6
7
8
9
10
@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中使用类似的代码来刷新视图。在我的代码中,我使用完全相同的方法。


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

1
2
3
4
5
6
7
8
9
@Override
public Parcelable saveState() {
    return null;
}

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

}


在经历了数小时的挫折之后,我尝试了所有上述的解决方案来克服这个问题,也尝试了许多其他类似问题的解决方案,这些和这些问题都未能解决这个问题,也未能使ViewPager摧毁旧的Fragment并用新的Fragment填满pager。我已经解决了问题描述如下:

1)使ViewPager类扩展FragmentPagerAdapter如下:

1
 public class myPagerAdapter extends FragmentPagerAdapter {

2)为存储titleFragmentViewPager创建一个项目,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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;
}

}

3)让ViewPager的建设者以我的FragmentManager实例存储在我的class中,如下:

1
2
3
4
5
6
7
8
private FragmentManager mFragmentManager;
private ArrayList<PagerItem> mPagerItems;

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

4)通过直接从FragmentManager中删除所有以前的Fragment来创建一种用新数据重新设置adapter数据的方法,使adapter重新从新列表中设置新的Fragment如下:

1
2
3
4
5
6
7
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;
}

5)从容器ActivityFragment中,不要用新数据重新初始化适配器。通过方法setPagerItems设置新数据,新数据如下:

1
2
3
4
5
6
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();

希望有帮助。


我也有同样的问题,我的解决方案是凌驾于ViewPagerAdapter#getItemId(int position)之上:

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

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

要使用它,每一页需要long id。通常情况下,它应该是唯一的,但我建议,在这种情况下,它应该与同一页的前一个值不同。所以,可以在适配器中使用连续计数器,也可以在这里使用随机整数(分布广泛)。

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


一种更简单的方法:使用FragmentPagerAdapter,并将页面视图包装成片段。他们确实得到了更新


手术提出问题两年半后,这个问题仍然是一个问题。很明显,谷歌在这方面的优先权并不特别高,所以我找到了一个解决办法,而不是找到一个解决办法。对我来说,最大的突破是找出问题的真正原因(见本帖接受的答案)。一旦发现问题是任何活动页面没有正确刷新,我的解决方法就显而易见了:

在我的片段(页面)中:

  • 我从OnCreateView中获取了所有填充表单的代码,并将其放入名为PopulateForm的函数中,该函数可以从任何地方调用,而不是由框架调用。此函数尝试使用get view获取当前视图,如果为空,则返回。PopulateForm只包含显示的代码是很重要的——创建FocusChange侦听器等的所有其他代码仍然在OnCreate中。
  • 创建一个布尔值,该值可用作指示必须重新加载窗体的标志。我的是mbreloadform
  • 如果设置了mbreloadform,则重写onresume()以调用populateform()。

在我的活动中,我执行页面加载的位置:

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

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

希望这能帮助别人!


感谢Rui.Araujo和Alvaro Luis Bustamante。一开始,我试着用rui.araujo的方式,因为这很容易。它可以工作,但是当数据更改时,页面将明显地重新绘制。很糟糕,所以我试着用阿尔瓦罗·路易斯·布斯塔曼特的方式。它是完美的。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@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;
    }
}

当数据发生变化时:

1
2
3
4
5
6
7
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();
    }
}

所有这些解决方案对我没有帮助。因此我找到了一个可行的解决方案:你可以每次都做一次,但这还不够。您应该在更改适配器之前执行这些操作:

1
2
3
4
5
6
7
FragmentManager fragmentManager = slideShowPagerAdapter.getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
List<Fragment> fragments = fragmentManager.getFragments();
for (Fragment f : fragments) {
    transaction.remove(f);
}
transaction.commit();

在此之后:

1
viewPager.setAdapter(adapter);


我发现这个问题的决定很有趣。我们可以使用fragmentStatepageradapter(android.support.v4.app.fragmentStatepageradapter),而不是使用fragmentpageradapter(将所有片段保存在内存中),当我们选择它时,它每次都会重新加载片段。

两个适配器的实现是相同的。所以,我们只需要在"extend fragmentstatepageradapter"上更改"extend fragmentpageradapter"


如果有人使用基于fragmentstatepageradapter的适配器(这将允许viewpager创建显示所需的最小页面,我的情况下最多2个),@rui.araujo在适配器中覆盖getitemsposition的回答不会造成显著的浪费,但仍然可以改进。

在伪代码中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}


在大量寻找这个问题之后,我找到了一个非常好的解决方案,我认为这是解决这个问题的正确方法。本质上,仅当视图被实例化时才调用InstantateItem,除非视图被销毁(这是重写getItemPosition函数以返回位置"无"时发生的情况)。相反,您要做的是保存创建的视图,并在适配器中更新它们,生成一个get函数以便其他人可以更新它,或者一个set函数更新适配器(我的最爱)。

因此,在MyViewPageRadapter中添加如下变量:

1
private View updatableView;

实例项中的:

1
2
3
4
5
6
 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;
    }

这样,您就可以创建一个函数来更新视图:

1
2
3
4
public updateText(String txt)
{
    ((TextView)updatableView).setText(txt);
}

希望这有帮助!


我有一个类似的问题,我有四页,其中一页更新了其他三页的视图。我可以更新与当前页面相邻的页面上的小部件(参见工具栏、文本视图等)。在调用mTabsAdapter.getItem(position)时,最后两个页面可能没有初始化小部件。

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

在所有更新之后,我将使用setSelectedPage(position),然后使用notifyDataSetChanged()

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


我只是发布这个答案以防其他人发现它有用。为了做完全相同的事情,我只需从兼容性库中获取viewpager和pageradapter的源代码,并在代码中编译它(您需要整理所有错误并导入自己,但这绝对可以完成)。

然后,在customviewpager中,创建一个名为updateviewat(int position)的方法。视图本身可以从viewpager类中定义的arraylist斜接中获取(您需要在实例化项时为视图设置一个ID,并将此ID与updateViewat()方法中的位置进行比较)。然后您可以根据需要更新视图。


对我有好处的是1〔25〕。

在适配器中,将更新视图的代码放在getItemPosition中,就像这样

1
2
3
4
5
6
7
8
9
10
@Override
public int getItemPosition(Object object) {

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

    return super.getItemPosition(object);
}

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


我想,我知道viewpager的逻辑。

如果我需要刷新一组页面并基于新数据集显示它们,我将调用notifyDataSetChanged()。然后,viewpager对getitemPosition()进行了多次调用,并将片段作为对象传递到该位置。这个片段可以来自旧的数据集(我想丢弃它),也可以来自新的数据集(我想显示它)。所以,我重写getItemPosition(),在这里我必须确定我的片段是来自旧的数据集还是来自新的数据集。

在我的例子中,我有一个两窗格的布局,在左窗格上有一个顶部项目列表,在右窗格上有一个滑动视图(viewpager)。因此,我将一个链接存储到我的pageradapter中的当前顶级项目,以及每个实例化的页面片段中。当列表中选定的顶级项更改时,我将新的顶级项存储在PageRadapter中,并调用NotifyDataSetChanged()。在重写的getItemPosition()中,我将适配器中的顶级项与片段中的顶级项进行比较。只有当它们不相等时,我才返回位置"无"。然后,pageradapter重新检验返回位置"无"的所有碎片。

注意事项。最好是存储顶部项目ID而不是引用。

下面的代码片段有点示意性,但我根据实际工作的代码对其进行了调整。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
  }
}

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


您可以动态更新所有片段,您可以通过三个步骤看到。

在适配器中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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;
}}

现在在您的活动中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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) {

}}

最后,在您的片段中,类似这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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来动态更改PageRadapter中的项目。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@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库还支持由片段创建的页面。


下面的代码对我有用。

创建一个类来扩展fragmentpageradapter类,如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
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;
}}

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

然后创建一个viewpager实例,

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

以及一个适配器实例,

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

那就这么做,

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

然后,在类内部初始化了上面的适配器类并创建了一个viewpager,每次要更新其中一个片段(在我们的例子中是fragment0)时,请使用以下方法:

1
2
3
4
5
6
7
8
9
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.

该解决方案基于阿尔瓦罗·路易斯·布斯塔曼特提出的技术。


您可以像这样在viewpager上添加寻呼机转换

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
     }
   }
}


这适用于像我这样需要从服务(或其他后台线程)更新viewpager的所有人,并且所有建议都无效:经过一点日志检查,我意识到notifyDataSetChanged()方法永远不会返回。getitemposition(object object)被称为all-ends-there,不需要进一步处理。然后我在父pageradapter类的文档中发现(不在子类的文档中),"数据集更改必须在主线程上发生,并且必须以调用notifyDataSetChanged()结束。"因此,本例中的工作解决方案是(使用FragmentStatePageRadapter和GetItemPosition(对象对象)设置为返回位置"无"):

然后调用notifyDataSetChanged():

1
2
3
4
5
6
runOnUiThread(new Runnable() {
         @Override
         public void run() {
             pager.getAdapter().notifyDataSetChanged();
         }
     });

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
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个ViewWindow的层次结构中删除,而通过调用instantiateItem(ViewGroup viewPager, int index)你只重新创建3到4个View的层次结构。然后一切恢复正常,你滑动和滚动,ViewPager使用它的适配器显示内容。在所有情况下,其子代的数量在所有设备上都不相同,例如,如果将offscreenPageLimit设置为5,它可能会有大约11到12个子代,但仅此而已,并不多。它很快。


重写getItemPosition()的正确方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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可以确定当前显示的视图是否仍存在于适配器上,并相应地执行操作。


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

1
2
3
4
5
6
7
8
9
public class MyPagerAdapter extends FragmentPagerAdapter {
    ...
    @Override
    public void finishUpdate(ViewGroup container) {
        super.finishUpdate(container);
        this.notifyDataSetChanged();
    }
    ...
}


值得一提的是,在Kitkat+上,如果你的setOffscreenPageLimit足够高,那么adapter.notifyDataSetChanged()似乎足以引起新的观点的出现。我可以通过做viewPager.setOffscreenPageLimit(2)来获得想要的行为。


除了返回POSITION_NONE并再次创建所有片段之外,您还可以按照我在这里建议的方式进行操作:动态更新viewpager?


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


1.首先,必须在PageRadapter类中设置GetItemPosition方法。2.您必须阅读视图传呼机的确切位置。3.然后将该位置作为新位置的数据位置发送4.在setOnPageChange监听器中写入更新按钮onclick监听器

那个程序代码是我修改过的,只设置了特定的位置元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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) {
    }
}
}


我正在将Tablayout与ViewPageRadapter结合使用。对于在片段之间传递数据或在片段之间通信,请使用下面的代码,该代码工作良好,并且在片段出现时刷新它。在第二个片段的内部按钮点击写在代码下面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

        }
    });

below is the code which has to be written in first fragment (in my case) i-e in receiving Fragment.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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
    }
}


在我的例子中,每次片段进入@override onCreateView(...)时,我都会重新启动值,因此处理空值或空值,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if(var == null){
    var = new Something();
}

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

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

// etc...

每次我们更改并在运行时从UI返回视图时,Fragment类都可以进入OnCreateView。


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

(在我的例子中,我从未添加超过3个片段,但是您可以使用例如getFragmentManager().getBackStackEntryCount()并检查所有fragments)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * 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();
}

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

(目前为"android:switcher:" + containerId +":" + i)

那么,使用它的方法是在获得容器之后:

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


我想我已经做了一个简单的方法来通知数据集更改:

首先,稍微改变instantiatem函数的工作方式:

1
2
3
4
5
6
7
8
9
    @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的工作方式如下:

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

现在,假设您需要更改数据,进行更改,然后调用pageradapter上的下一个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    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显示的所有视图发生了更改,可以调用:

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

就这样。


这是一个可怕的问题,我很高兴提出一个优秀的解决方案:简单、高效和有效!

见下文,代码显示使用标志指示何时返回位置"无"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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();
    }

}


我在ViewPagerCirclePageIndicator上实际使用notifyDataSetChanged(),然后在ViewPager上调用destroyDrawingCache(),它就工作了。其他的解决方案对我都不起作用。


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