简体   繁体   English

Android:重新使用ViewPager和FragmentPagerAdapter的片段

[英]Android: re-use fragments of ViewPager & FragmentPagerAdapter

I'm using ViewPager and FragmentPagerAdapter within a detail-view. 我在详细视图中使用ViewPagerFragmentPagerAdapter This means I have a list of items and one screen shows the details of a single item. 这意味着我有一个项目列表,一个屏幕显示单个项目的详细信息。 But the user can swipe left and right to navigate through all items. 但是,用户可以向左和向右滑动以浏览所有项目。 This follows the Google guideline for swiping views . 这遵循Google 浏览视图的指南

But I wonder about one thing. 但我想知道一件事。 Within a ListView the views for each row get re-used. ListView中,每行的视图都可以重复使用。 Once a row scrolls out of the screen it is re-used as the convertView parameter of the getView method of the adapter that is bound to the ListView . 一旦一行滚出屏幕,它就会被重新用作绑定到ListView的适配器的getView方法的convertView参数。 But this re-usage behavior does not seem to be implemented for swiping views. 但是这种重用行为似乎没有实现滑动视图。 This example illustrates this: 这个例子说明了这个:

class DemoAdapter extends ArrayAdapter<DemoItem> {

    public DemoAdapter(Context context, List<DemoItem> objects) {
        super(context, 0, objects);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        if (convertView == null) {
            // create a new view, otherwise re-use the existing convertView
            LayoutInflater i = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = i.inflate(R.layout.list_item_demo, parent, false);
        }

        // get current item
        DemoItem item = getItem(position);

        if (item == null)
            return convertView;

        // update view with the item
        TextView textTitle = (TextView)convertView.findViewById(R.id.demo_title);
        if (textTitle != null)
            textTitle.setText(item.getTitle());

        return convertView;
    }
}

But here's the problem: Both, the FragmentPagerAdapter and the FragmentStatePagerAdapter are creating the fragments (each screen is a fragment) in their getItem method. 但问题在于: FragmentPagerAdapterFragmentStatePagerAdapter都在其getItem方法中创建片段(每个屏幕都是一个片段)。 But they don't get old fragments as an input parameter. 但是它们不会将旧片段作为输入参数。 The only difference is, that the FragmentStatePagerAdapter destroys unused fragments. 唯一的区别是, FragmentStatePagerAdapter会破坏未使用的片段。

public class DemoItemsPagerAdapter extends FragmentPagerAdapter {

    private final Context context;

    public DemoItemsPagerAdapter(FragmentManager fm, Context context) {
        super(fm);    
        this.context = context;

        // ToDo: get cursor or array of available items that can be swiped through
    }

    @Override
    public Fragment getItem(int i) {

        Fragment fragment = new DemoItemFragment();

        // ToDo: initialize fragment by correct item
        // ToDo: avoid creating too many fragments - try reusing them (but how?)

        return fragment;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {

        // the container is not the fragment, but the ViewPager itself
        return super.instantiateItem(container, position);
    }

    @Override
    public CharSequence getPageTitle(int position) {    
        // ToDo: return name for current entry
        return null;
    }

    @Override
    public int getCount() {    
        // ToDo: get count from cursor/array of available items
        return 2;
    }
}

So, how can I reuse the fragments? 那么,我该如何重用碎片呢? Actually getItems should only be called twice because there is only one fragment visible at a time and a second one once the transition starts while the user is swiping. 实际上,只应调用getItems两次,因为一次只有一个片段可见,而第二个片段在用户滑动时开始转换。

UPDATE: Because of confusion, I created this drawing. 更新:由于困惑,我创建了这个绘图。 It shows the behavior of the adapters. 它显示了适配器的行为。 The default one keeps all fragments in memory unless the device runs out of memory. 除非设备内存不足,否则默认值会将所有片段保留在内存中。 One the app is in background or killed and then restored each fragment will be restored from its SavedInstanceState. 一个应用程序处于后台或被杀死然后恢复每个片段将从其SavedInstanceState恢复。 The second implementation keeps only some fragments in memory but if you swipe left/right the destroyed ones will be completely created again from scratch. 第二个实现仅在内存中保留一些片段,但如果向左/向右滑动,则将从头开始再次完全创建被破坏的片段。 The third implementation is what I'm seeking. 第三个实现是我正在寻求的。 You have only three fragments which are then reused when swiping left or right. 您只有三个片段,然后在向左或向右滑动时重复使用。 So fragment A can be position 1, 2, 3, 4, 5, 6 and 7. 因此片段A可以是位置1,2,3,4,5,6和7。

FragmentPagerAdapter实现

Firstly to answer your question. 首先回答你的问题。

So, how can I reuse the fragments? 那么,我该如何重用碎片呢?

You can maintain an array of fragments in a SparseArray (It is more memory efficient than a HashMap when you need to map objects to integers). 您可以在SparseArray中维护一个片段数组(当您需要将对象映射到整数时,它比HashMap更有内存效率)。

private SparseArray<BaseFragment> fragments;

So your code can be something like this in the getItem(int i) . 所以你的代码在getItem(int i)可以是这样的。

@Override
    public Fragment getItem(int position) {
        // Create a new fragment only when the fragment is not present in the fragments sparse
        // array
        BaseFragment fragment = fragments.get(position);
        if (fragment == null) {
            switch (position) {
                case 0: {
                    fragment = new Fragment1();
                    fragments.put(0, fragment);
                    break;
                }

                case 1: {
                    fragment = new Fragment2();
                    fragments.put(1, fragment);
                    break;
                }

                .
                .
                .

                default:
                    fragment = null;
            }
        }
        return fragment;
    }

Here I use a BaseFragment which is extended by almost all the fragments that I want to use. 在这里,我使用了一个BaseFragment,它几乎扩展了我想要使用的所有片段。

And AFAIK, getItem() is called based upon the offScreenPageLimit . 并且基于offScreenPageLimit调用AFAIK, getItem() The default is 1. So based upon this number, the fragments that will be kept in memory will be 默认值为1.因此,基于此数字,将保留在内存中的片段将是

1 + 2*offScreenPageLimit // Current Page + 2 * left/right items

or 要么

1 + offScreenPageLimit // if its the first or last page.

UPDATE 1 更新1

You dont have to worry about handling the removal of fragments from the memory. 您不必担心处理从内存中删除片段。 The FragmentStatePagerAdapter automatically handles that for you as mentioned in their docs . FragmentStatePagerAdapter会自动为您处理它们,如文档中所述

This version of the pager is more useful when there are a large number of pages, working more like a list view. 当存在大量页面时,此版本的寻呼机更有用,更像列表视图。 When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. 当页面对用户不可见时,它们的整个片段可能会被破坏,只保留该片段的保存状态。 This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages. 与FragmentPagerAdapter相比,这允许寻呼机保持与每个被访问页面相关联的更少的存储器,代价是在页面之间切换时可能具有更多的开销。

You use FragmentPagerAdapter when you've less pages to swipe, generally when using tabs or when the fragments are static. 当您使用较少的页面进行滑动时,通常在使用制表符或片段是静态时,可以使用FragmentPagerAdapter。 The doc says, 医生说,

This version of the pager is best for use when there are a handful of typically more static fragments to be paged through, such as a set of tabs. 此版本的寻呼机最适合在有少量通常更多静态片段进行分页时使用,例如一组选项卡。 The fragment of each page the user visits will be kept in memory, though its view hierarchy may be destroyed when not visible. 用户访问的每个页面的片段将保留在内存中,但其视图层次结构可能在不可见时被销毁。 This can result in using a significant amount of memory since fragment instances can hold on to an arbitrary amount of state. 这可能导致使用大量内存,因为片段实例可以保持任意数量的状态。 For larger sets of pages, consider FragmentStatePagerAdapter. 对于较大的页面集,请考虑FragmentStatePagerAdapter。

UPDATE 2 更新2
Your definition above for case 2 is wrong. 您对案例2的上述定义是错误的。

The second implementation keeps only some fragments in memory but if you swipe left/right the destroyed ones will be completely created again from scratch. 第二个实现仅在内存中保留一些片段,但如果向左/向右滑动,则将从头开始再次完全创建被破坏的片段。

It wont be created from scratch, it will save the state of the previously created fragment and use the same state while creating the new one. 它不会从头开始创建,它将保存先前创建的片段的状态,并在创建新片段时使用相同的状态。 You can look at the source code here to better understand how it works. 您可以在此处查看源代码 ,以便更好地了解其工作原理。

As to your 3rd implementation, I'd suggest overriding the default behavior of the adapter and then manually removing / adding view from the adapter based upon the current position. 至于你的第三个实现,我建议覆盖适配器的默认行为,然后根据当前位置手动从适配器中删除 / 添加视图。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM