隐藏并显示ViewPager的父Fragment时,ViewPager的Fragment的视图丢失


69

我一直在使用ViewPager和我自己的FragmentStatePagerAdapter看到一些奇怪的行为。

我的视图层次结构如下所示:

-> (1) Fragment root view (RelativeLayout)
 -> (2) ViewPager
  -> (3) ViewPager's current fragment view

当负责片段根视图(1)的片段被隐藏(在片段事务中使用.hide())然后显示(与.show()一起)时,当前在ViewPager中显示的片段视图(3) )变为空,尽管该片段仍然存在。基本上,我的ViewPager变得完全空白/透明。

我发现解决此问题的唯一方法是致电

int current = myViewPager.getCurrentItem();
myViewPager.setAdapter(myAdapter);
myViewPager.setCurrentItem(current);

显示父片段之后。这会触发视图重新创建并出现在屏幕上。不幸的是,这偶尔会导致处理寻呼机适配器的异常,该适配器unregisterDataSetObserver()在旧的观察器上调用两次。

有一个更好的方法吗?我想我要问的是:

隐藏ViewPager的父片段时,为什么ViewPager中的片段视图被破坏?

更新:当应用程序“最小化”然后“恢复”时(通过按下Home Action键然后返回),也会发生这种情况。

根据请求,这是我的寻呼机适配器类:

public class MyInfoSlidePagerAdapter extends FragmentStatePagerAdapter {

    private ArrayList<MyInfo> infos = new ArrayList<MyInfo>();

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

    public MyInfoSlidePagerAdapter (FragmentManager fm, MyInfo[] newInfos) {
        super(fm);
        setInfos(newInfos);
    }

    @Override
    public int getItemPosition(Object object) {
        int position = infos.indexOf(((MyInfoDetailsFragment)object).getMyInfo());
        return position > 0 ? position : POSITION_NONE;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return infos.get(position).getName();
    }

    @Override
    public Fragment getItem(int i) {
        return infos.size() > 0 ? MyInfoDetailsFragment.getNewInstance(infos.get(i)) : null;
    }

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

    public Location getMyInfoAtPosition(int i) {
        return infos.get(i);
    }

    public void setInfos(MyInfo[] newInfos) {
        infos = new ArrayList<MyInfo>(Arrays.asList(newInfos));
    }

    public int getPositionOfMyInfo(MyInfo info) {
        return infos.indexOf(info);
    }
}

我已经重命名了一些变量,但除此之外,这正是我所拥有的。


在“隐藏” /“秀”之后......可以尝试调用适配器上“notifyDatasetChanged” ......可避免与重复数据删除您的通话问题,“注销..”
罗伯特·朗特里

@RobertRowntree,是的,我已经尝试过了。我最初针对此问题的解决方法是尝试重置适配器数据并调用notifyDatasetChanged()
John Leehey 2013年

您是否尝试过保存片段(onretain = true),然后在隐藏/显示之后重做一个fragmentTransaction?
罗伯特·罗恩特里

ViewPager在另一个ViewPager吗?您是否getChildFragmentManager()在的适配器中使用过ViewPager
用户

3
当您从Fragment实例化viewpager适配器时,请传递getChildSupportFragmentManager。这2个之间的区别是此片段管理器由片段而不是主机Activity管理。我不确定这是否会发生,但是值得尝试。
贡纳

Answers:


94

您没有为您的特定问题提供足够的信息,所以我建立了一个示例项目来尝试重现您的问题:该应用程序有一个活动,该活动PagerFragment在相对布局中包含一个片段(),在此布局下方,我有一个隐藏的按钮&以上显示PagerFragmentPagerFragment有一个ViewPager寻呼机适配器内每个片段只显示一个标签-这个片段被命名DataFragment。标签列表是在父活动中创建的,并传递给PagerFragment,然后通过其适配器传递给每个标签DataFragment。更改PagerFragment可见性是没有问题的,并且每次再次变得可见时,都会显示先前显示的标签。

问题的关键:创建Viewpager适配器时 使用Fragment#getChildFragmentManager()而不是getFragmentManager!

也许您可以将这个简单的项目与您拥有的项目进行比较,并检查差异之处。因此,这里是(自上而下):

PagerActivity(项目中唯一的活动):

public class PagerActivity extends FragmentActivity {

    private static final String PAGER_TAG = "PagerActivity.PAGER_TAG";

    @Override
    protected void onCreate(Bundle savedInstance) {
        super.onCreate(savedInstance);
        setContentView(R.layout.pager_activity);
        if (savedInstance == null) {
            PagerFragment frag = PagerFragment.newInstance(buildPagerData());
            FragmentManager fm = getSupportFragmentManager();
            fm.beginTransaction().add(R.id.layout_fragments, frag, PAGER_TAG).commit();
        }
        findViewById(R.id.btnFragments).setOnClickListener(new OnClickListener() {

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

    private List<String> buildPagerData() {
        ArrayList<String> pagerData = new ArrayList<String>();
        pagerData.add("Robert de Niro");
        pagerData.add("John Smith");
        pagerData.add("Valerie Irons");
        pagerData.add("Metallica");
        pagerData.add("Rammstein");
        pagerData.add("Zinedine Zidane");
        pagerData.add("Ronaldo da Lima");
        return pagerData;
    }

    protected void changeFragmentVisibility() {
        Fragment frag = getSupportFragmentManager().findFragmentByTag(PAGER_TAG);
        if (frag == null) {
            Toast.makeText(this, "No PAGER fragment found", Toast.LENGTH_SHORT).show();
            return;
        }
        boolean visible = frag.isVisible();
        Log.d("APSampler", "Pager fragment visibility: " + visible);
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        if (visible) {
            ft.hide(frag);
        } else {
            ft.show(frag);
        }
        ft.commit();
        getSupportFragmentManager().executePendingTransactions();
    }
}

其布局文件pager_activity.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="4dp" >

    <Button
        android:id="@+id/btnFragments"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:text="Hide/Show fragments" />

    <RelativeLayout
        android:id="@+id/layout_fragments"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/btnFragments"
        android:layout_marginBottom="4dp" >
    </RelativeLayout>

</RelativeLayout>

观察到我在PagerFragment第一次显示活动时添加了-和PagerFragment类:

public class PagerFragment extends Fragment {

    private static final String DATA_ARGS_KEY = "PagerFragment.DATA_ARGS_KEY";

    private List<String> data;

    private ViewPager pagerData;

    public static PagerFragment newInstance(List<String> data) {
        PagerFragment pagerFragment = new PagerFragment();
        Bundle args = new Bundle();
        ArrayList<String> argsValue = new ArrayList<String>(data);
        args.putStringArrayList(DATA_ARGS_KEY, argsValue);
        pagerFragment.setArguments(args);
        return pagerFragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        data = getArguments().getStringArrayList(DATA_ARGS_KEY);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.pager_fragment, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        pagerData = (ViewPager) view.findViewById(R.id.pager_data);
        setupPagerData();
    }

    private void setupPagerData() {
        PagerAdapter adapter = new LocalPagerAdapter(getChildFragmentManager(), data);
        pagerData.setAdapter(adapter);
    }

}

其布局(仅ViewPager占据完整尺寸):

<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager_data"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

及其适配器:

public class LocalPagerAdapter extends FragmentStatePagerAdapter {
    private List<String> pagerData;

    public LocalPagerAdapter(FragmentManager fm, List<String> pagerData) {
        super(fm);
        this.pagerData = pagerData;
    }

    @Override
    public Fragment getItem(int position) {
        return DataFragment.newInstance(pagerData.get(position));
    }

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

该适配器DataFragment为每个页面创建一个:

public class DataFragment extends Fragment {
    private static final String DATA_ARG_KEY = "DataFragment.DATA_ARG_KEY";

    private String localData;

    public static DataFragment newInstance(String data) {
        DataFragment df = new DataFragment();
        Bundle args = new Bundle();
        args.putString(DATA_ARG_KEY, data);
        df.setArguments(args);
        return df;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        localData = getArguments().getString(DATA_ARG_KEY);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.data_fragment, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        view.findViewById(R.id.btn_page_action).setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                Toast.makeText(getActivity(), localData, Toast.LENGTH_SHORT).show();
            }
        });
        ((TextView) view.findViewById(R.id.txt_label)).setText(localData);
    }
}

DataFragment的布局:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="4dp" >

    <Button
        android:id="@+id/btn_page_action"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:text="Interogate" />

    <TextView
        android:id="@+id/txt_label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:textAppearance="?android:attr/textAppearanceLarge" />

</RelativeLayout>

享受编码!


嗯,我建立的结构与您在这里所做的非常相似。我可以看到的一个小差异是,当我使用FrameLayout时,用于片段视图的容器是相对布局。我将进行比较,看看是否还有其他内容。
John Leehey 2013年

3
它应该没有任何区别。
gunar 2013年

比较是否显示任何相关内容?
贡纳

8
只是为了跟进那些直接发表这些评论的人,该比较显示了(或者如果我已经看得更透彻的话,应该是)我在使用getActivity().getSupportFragmentManager()而不是getChildFragmentManager()在创建寻呼机适配器时使用的比较。
John Leehey

@gunar一切正常,对我来说问题是Toast.makeText(getActivity(),.....)在看不见时出错,我正在为每个子片段使用网络调用,我正在加载7个子片段查看页面!我的问题是,当我们在第4个片段上刷卡到第1个片段的网络延迟时,将获得响应,而看不见Toast响应将导致错误!
LOG_TAG

21

也许会帮助mViewPager.setOffscreenPageLimit(5)

设置在空闲状态下视图层次结构中应保留到当前页面任一侧的页面数。超出此限制的页面将在需要时从适配器重新创建。

这是作为优化提供的。如果您事先知道需要支持的页面数或页面上具有延迟加载机制,则调整此设置可以使分页动画和交互的流畅性受益。如果只有很少的页面(3-4)可以一次全部保持活动状态,则随着用户来回翻页,新创建的视图子树的布局将花费更少的时间。

您应将此限制保持在较低水平,尤其是在页面布局复杂的情况下。此设置默认为1。


6

View Pager坚决保持其片段始终保持最新状态,从而通过在不使用片段时释放内存来优化性能。显然,这是移动系统中有效的有用特性。但是由于资源的这种持久释放,所以每次获得焦点时都会创建该片段。

mViewPager.setOffscreenPageLimit(NUMBEROFFRAGMENTSCREENS);

这里是 文档。

旧帖子为您的问题提供了有趣的解决方案。


5

对我来说,我改为getChildFragmentManager()而不是,getFragmentManager() 并且效果很好。 例如:

pagerAdapt = new PagerAdapt(getChildFragmentManager());

1

我有同样的问题。我的应用程序(FragmentActivity)的寻呼机(ViewPager)包含3种香料。在片段之间滑动时,它们一直被破坏并重新创建。实际上,它在功能上没有问题(期望未封闭的游标),但是我也想知道这个问题。

我不知道是否有解决方法来更改ViewPager的行为,但是我建议您有一个配置对象(也许是静态的),并在销毁myViewPager对象之前将其保存在配置对象中。

public class App extends FragmentActivity {

    static MyData data;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
         data = (MyData) getLastCustomNonConfigurationInstance();
         if (data == null) {
             data = new MyData();
             data.savedViewPager = myViewPager;
         } else {
             myViewPager = data.savedViewPager;
        }
    }

    @Override
    public Object onRetainCustomNonConfigurationInstance() {
        Log.d("onRetainCustomNonConfigurationInstance", "Configuration call");
        return data;
    }
}

public class MyData {
    public ViewPager savedViewPager;
}

通过这种方式,您可以保存对不会破坏的对象的引用,因此存在对它的引用,并且可以重新加载所有关键对象。

希望您觉得我的建议有用!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.