【问题标题】:Android: re-use fragments of ViewPager & FragmentPagerAdapterAndroid:复用 ViewPager 和 FragmentPagerAdapter 的片段
【发布时间】:2015-07-06 17:24:52
【问题描述】:

我在详细视图中使用 ViewPagerFragmentPagerAdapter。这意味着我有一个项目列表,一个屏幕显示单个项目的详细信息。但是用户可以左右滑动来浏览所有项目。这遵循谷歌guideline for swiping views

但我想知道一件事。在 ListView 中,每一行的视图都会被重用。一旦一行滚动出屏幕,它将被重新用作绑定到适配器的 getView 方法的 convertView 参数列表视图。但是这种重用行为似乎并没有为滑动视图实现。这个例子说明了这一点:

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;
    }
}

但问题出在这里: FragmentPagerAdapterFragmentStatePagerAdapter 都在创建片段(每个屏幕都是一个片段)他们的 getItem 方法。但他们不会将旧片段作为输入参数。唯一的区别是,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;
    }
}

那么,我怎样才能重用片段? 实际上,getItems 应该只调用两次,因为一次只有一个片段可见,而在用户滑动时开始转换时,第二个片段可见。

更新:由于混淆,我创建了这张图。它显示了适配器的行为。默认设置将所有片段保存在内存中,除非设备内存不足。一个应用程序在后台或被杀死然后恢复每个片段将从其 SavedInstanceState 恢复。第二种实现只在内存中保留一些片段,但如果你向左/向右滑动,被破坏的片段将完全从头开始重新创建。第三个实现是我正在寻找的。您只有三个片段,然后在向左或向右滑动时会重复使用这些片段。所以片段A可以是位置1、2、3、4、5、6和7。

【问题讨论】:

  • 为什么要重用片段?列表视图重用视图,因为这样它就不必一次又一次地膨胀它们,这应该是昂贵的。我个人认为重用视图被高估了。您必须自己编程片段的重用,这是一项不平凡的工作。为什么要打扰?
  • 因为每个页面看起来都一模一样,而膨胀布局确实是一项昂贵的操作。

标签: android android-fragments android-viewpager fragmentpageradapter


【解决方案1】:

首先回答你的问题。

那么,我怎样才能重用这些片段呢?

您可以在SparseArray 中维护一个片段数组(当您需要将对象映射到整数时,它比 HashMap 更节省内存)。

private SparseArray<BaseFragment> fragments;

所以你的代码在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;
    }

在这里,我使用了一个 BaseFragment,几乎所有我想使用的片段都对其进行了扩展。

而 AFAIK,getItem() 是基于 offScreenPageLimit 调用的。默认值为 1。因此基于此数字,将保留在内存中的片段将是

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

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

更新 1

您不必担心处理从内存中删除片段的问题。如docs 中所述,FragmentStatePagerAdapter 会自动为您处理。

这个版本的寻呼机在人多的时候比较有用 页面,更像是一个列表视图。当页面不可见时 用户,他们的整个片段可能会被销毁,只保留 该片段的保存状态。这允许寻呼机保持很多 与每个访问的页面相关的内存更少 FragmentPagerAdapter 以潜在的更多开销为代价 在页面之间切换。

当您要滑动的页面较少时,通常在使用选项卡或片段为静态时使用 FragmentPagerAdapter。 doc 说,

这个版本的寻呼机最适合在有少数人的情况下使用 通常需要分页的更多静态片段,例如一组 标签。用户访问的每个页面的片段将保存在 内存,尽管它的视图层次结构在不可见时可能会被破坏。 这可能会导致使用大量内存,因为片段 实例可以保持任意数量的状态。对于较大的套装 页面,请考虑 FragmentStatePagerAdapter。

更新 2
您上面对案例 2 的定义是错误的。

第二种实现只在内存中保留一些片段,但如果 你向左/向右滑动,被破坏的将被完全创建 再次从头开始。

它不会从头开始创建,它会保存之前创建的片段的状态,并在创建新片段时使用相同的状态。您可以在此处查看source code 以更好地了解它的工作原理。

至于您的第三个实现,我建议覆盖适配器的默认行为,然后根据当前位置从适配器手动 removing/adding 视图。

【讨论】:

  • 但是我怎样才能清理一些不再使用的片段数组。 FragmentStatePagerAdapter 有什么不同?
  • 我知道文档。但只要您将视图保存在 SparseArray 中,它们就永远不会被销毁,而是保留在内存中。我将尝试使用您的偏移量计算并仅保留数组中的那些。
  • 当我想访问特定位置的片段实例时,我通常使用 SparseArrays。 FragmentStatePagerAdapter 销毁内存中的项目但保持它们的状态,所以下次片段膨胀时,它可以恢复到状态。可悲的是(afaik)当从适配器内存中删除片段时没有调用任何方法。
  • 所以我通读了您提到的其他问题,并且都说在您的适配器中保存对片段的引用是不好的做法。相反,会为每个位置创建一个片段,并且 FragmentStatePagerAdapter 将其中至少两个保留在内存中,但会破坏其他的。但是在刷卡时会创建新的并破坏其他的。在 FragmentPagerAdapter 中,您将获得 pos 0、1 等的旧版本,因为它们仍在内存中。但是没有一个解决方案只创建 2 个片段。
猜你喜欢
  • 2019-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-12
  • 2018-07-13
相关资源
最近更新 更多