ViewPager没有重绘内容,保持/变为空白

90

我们在使用ViewPager时遇到了一个非常奇怪的问题。我们在每个ViewPager页面上嵌入列表,并在更新列表数据时同时触发列表适配器和视图页适配器的notifyDataSetChanged。

我们观察到有时,页面不会更新其视图树,即保持空白,甚至在翻页到该页面时有时会消失。在前后翻页几次后,内容会突然重新出现。似乎Android在这里漏掉了一个视图更新。我还注意到,在使用层次结构查看器进行调试时,选择一个视图将始终使其重新出现,显然是因为层次结构查看器强制选定的视图重绘自身。

但是,我无法通过编程使其正常工作;无效化列表视图,或者甚至整个ViewPager都没有效果。

这是使用compatibility-v4_r7库时出现的情况。我也尝试使用最新版本的库,因为它声称修复了许多与视图页相关的问题,但事情变得更糟了(例如,手势被破坏,有时它不再允许我浏览所有页面)。

还有人遇到这些问题吗?或者你有什么想法是什么原因造成了这个问题?

13个回答

57
如果将ViewPager设置在使用FragmentPagerAdapter的片段中,请使用getChildFragmentManager()而不是getSupportFragmentManager()作为参数来初始化你的FragmentPagerAdapter
mAdapter = new MyFragmentPagerAdapter(getChildFragmentManager());

不是

mAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager());

2
很好的发现,似乎解决了我在使用FragmentPagerAdapter时遇到的问题。 - Tobliug
1
谢谢 - 这个方法可行。我试图在重新创建的Fragment中强制使用FragmentPagerAdapter上的getItem()方法。 - kosiara - Bartosz Kosarzycki
哇,这个工作像魔法一样。我猜问题是由于在片段中填充ViewPager引起的。 - Thecarisma
这种时候我真希望我们有像 Medium 那样的鼓掌功能,这样我就可以给你 1000+ 个鼓掌了。干得好,这应该是被采纳的答案。 - Codelicious
弃用了。我们该怎么办? - neobie

46
我们终于成功找到了解决方案。显然,我们的实现存在两个问题:
  1. 我们的适配器没有在 destroyItem() 中删除视图。
  2. 我们正在缓存视图,以便只需充气布局一次,由于我们没有在 destroyItem() 中删除视图,因此我们没有将其添加到 instantiateItem() 中,而只是返回对应于当前位置的缓存视图。
我并没有深入研究 ViewPager 的源代码,而且它并不明确要求这样做,但文档中说道:

destroyItem()
Remove a page for the given position. The adapter is responsible for removing the view from its container, although it only must ensure this is done by the time it returns from finishUpdate(ViewGroup).

还有:

一个非常简单的 PagerAdapter 可能会选择将页面 View 本身作为关键对象,从创建后的 instantiateItem(ViewGroup, int) 返回它们并将它们添加到父 ViewGroup 中。匹配的 destroyItem(ViewGroup, int, Object) 实现将从父 ViewGroup 中移除 View,isViewFromObject(View, Object) 可以实现为 return view == object;。

因此,我的结论是 ViewPager 依赖于其底层适配器在 instantiateItem()/destroyItem() 中显式添加/删除其子项。也就是说,如果您的适配器是 PagerAdapter 的子类,则您的子类必须实现此逻辑。
附注:如果您在 ViewPager 内部使用列表,请注意此问题(链接)。

2
我也遇到了同样的问题,但是我使用的是FragmentPagerAdapter,它会为我处理onDestroy。这里提供的其他解决方案都不起作用。 - Greg Ennis
14
另一个可能的问题是,有人在使用 getFragmentManager 而不是 getChildFragmentManager。 - Boy
@Boy,GetChildFragmentManager是什么? - fire in the hole
1
@Boy 哇哦...我已经拔了整整两天的头发,试图找出为什么我无法更新任何内容。谢谢你!(他们真的应该在Google的文档上放置一个通知,使用childfragmentmanager,这完全不需要另一个管理器)。 - user0721090601
@futtetennista 我也遇到了同样的问题,请问你能否帮忙查看一下这个链接:https://stackoverflow.com/questions/61727835/textview-content-is-getting-trimmerd-after-viewpager-swipe-back - AskQ

23
我遇到了完全相同的问题,但实际上我在destroyItem中摧毁了视图(我认为是这样)。然而问题在于,我使用了viewPager.removeViewAt(index);来销毁它,而不是viewPager.removeView((View) object);

错误:

@Override
public void destroyItem(ViewGroup viewPager, int position, Object object) {
    viewPager.removeViewAt(position);
}

正确:

@Override
public void destroyItem(ViewGroup viewPager, int position, Object object) {
    viewPager.removeView((View) object);
}

谢谢你的提示。我已经花了很长时间研究这个问题了。 - Moritz
2
这对我有用,但你知道第一个为什么是个问题吗? - HannahMitt
@HannahMitt removeViewAt会移除ViewPager当前child列表中特定位置的视图,而且页面可以以任意顺序添加到ViewPager中(所以页面0实际上可能在ViewPager中的索引1或其他位置)。removeView将遍历子项列表并删除指定的对象。 - user153275
2
无法使用:无法将视图转换为片段。这里的对象是一个片段。 - FRK
viewpager必须是静态的吗? - Kanagalingam

11

ViewPager试图在重复使用项目方面做一些聪明的事情,但它要求您在事物发生变化时返回新的项位置。尝试将以下代码添加到您的PagerAdapter中:

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

这基本上告诉ViewPager一切都已经改变了(并强制它重新实例化所有内容)。那是我能想到的唯一一件事。


1
是的,我在这个帖子中读到了这个选项:https://dev59.com/QGw05IYBdhLWcg3wZBAC?rq=1 -- 然而,这似乎是一种粗暴的方法。肯定有更加优雅的方式吧?我想知道是否与使用列表作为分页页面有关? - mxk
这是一种有点粗暴的方法,但你可以从中找回来。也就是说,从该方法返回一个正确的值。 - Chris Banes
@ChrisBanes 正如你所说,调用 return POSITION_NONE;强制重新实例化所有内容,但在我的情况下,我不想重新实例化所有内容,那么是否有可能在不清除现有内容的情况下删除 view?请告诉我。 - Ritesh Adulkar
你是救命恩人。 - mask8

3

尝试了很多解决方案,但意外地 viewPager.post() 起作用了。

 mAdapter = new NewsVPAdapter(getContext(), articles);
    viewPager.post(new Runnable() {
        @Override
        public void run() {
            viewPager.setAdapter(mAdapter);
        }
    });

1
我的天啊,为什么没有人点赞这个?当我重新进入一个片段时,里面的ViewPager没有渲染自己,稍微延迟一下就解决了这个奇怪的问题! - Fugogugo

1

Android支持库有一个演示活动,其中包括每个页面上的ListView的ViewPager。您应该看一下并了解它的作用。

在Eclipse(使用Android Dev Tools r20)中:

  1. 选择New > Android Sample Project
  2. 选择目标API级别(建议选择最新可用的版本)
  3. 选择Support4Demos
  4. 右键单击项目,然后选择Android Tools > Add Support Library
  5. 运行应用程序,然后选择Fragment,然后选择Pager

此代码位于src/com.example.android.supportv4.app/FragmentPagerSupport.java中。祝你好运!


谢谢,我会看一下的!也许我能发现我们做错了什么。 - mxk

0

我遇到了同样的问题,这与ListView有关(因为如果列表为空,则我的空视图会正常显示)。我只需在有问题的ListView上调用requestLayout()即可。现在它可以正常绘制了!


0

我也遇到了同样的问题,并且在stack overflow上提出了问题

对于我来说,在我的视图的父级的父级中,有人对LinearLayout进行了子类化并覆盖了requestLayout(),但未调用super.requestLayout()。这会阻止ViewPager上的onMeasureonLayout被调用(尽管hierarchyviewer手动调用这些)。如果没有被测量,它们将在ViewPager中显示为空白。

因此,请检查您包含的视图。确保它们是从View进行子类化,并且不要盲目地覆盖requestLayout或任何类似的内容。


0

我曾经遇到过一个与此类似的问题,但是原因不同,最后发现是我的一个愚蠢错误。我想在这里加上它,以防对任何人有所帮助。

我使用了FragmentStatePagerAdapter的ViewPager,最初只有两个片段,但后来我添加了第三个。然而,我忘记了默认的离屏页面限制为1,所以当我切换到新的第三个片段时,第一个片段会被销毁,然后在切换回来后重新创建。问题在于我的活动负责通知这些片段初始化其UI状态。当活动和片段生命周期相同时,这种方法可以正常工作,但为了解决这个问题,我必须更改片段以在启动生命周期期间初始化其自己的UI。最终,我还将setOffscreenPageLimit更改为2,以便始终保持所有三个片段处于活动状态(在这种情况下安全,因为它们的内存占用不高)。


0
当我使用ViewPager和FragmentStatePagerAdapter时,我遇到了同样的问题。我尝试使用一个延迟3秒的处理程序来调用invalidate()和requestLayout(),但它没有起作用。有效的方法是按以下方式重置viewPager的背景颜色:

MyFragment.java

    private Handler mHandler;
    private Runnable mBugUpdater;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View rootView = new ViewPager(getActivity());
        //...Create your adapter and set it here...

        mHandler = new Handler();
        mBugUpdater = new Runnable(){
            @Override
            public void run() {
                mVp.setBackgroundColor(mItem.getBackgroundColor());
                mHandler = null;
                mBugUpdater = null;
            }           
        };
        mHandler.postDelayed(mBugUpdater,50);

        return rootView;
    }

    @Override
    public void onPause() {
        if(mHandler != null){
            //Remove the callback if it hasn't triggered yet
            mHandler.removeCallbacks(mBugUpdater);
            mHandler = null;
            mBugUpdater = null;
        }
        super.onPause();
     }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接