FragmentPagerAdapter&FragmentStatePageAdapter整理(二)

整理这个是因为平时用得很多,很多时候只是知其然,不知其所以然,现在有些时间,就慢慢撸一撸。

PagerAdapter

与ViewPager结合,现在应该是个app都会有ViewPager+PagerAdapter的组合了。PagerAdapter负责数据,而ViewPager负责展示。PagerAdapter是一个抽象类,FragmentPagerAdapter和FragmentStatePagerAdapter都是继承自PagerAdapter。当然也可以自己继承PagerAdapter:

public class MyPagerAdapter extends PagerAdapter {

    //PagerAdapter中 该方法直接throw exception 所以需要子类重写
    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        return super.instantiateItem(container, position);
    }

    //PagerAdapter中 该方法直接throw exception 所以需要子类重写
    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        super.destroyItem(container, position, object);
    }

    @Override
    public int getCount() {
        return 0;
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return false;
    }
}

instantiateItem:
在每次ViewPager需要一个显示的Object的时候,该函数都会被ViewPager.addNewItem()调用,什么意思呢?说简单点就是ViewPager需要显示的内容,这个方法对于FragmentPagerAdapter和FragmentStatePagerAdapter是有重要区别的,稍后会扯到。

destroyItem
定义移除给定位置的Object对象的方法,pagerAdapter中没有实现,子类中需要给出实现

isViewFromObject
很多时候,都是一句话:

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return object== view;
    }

这里的object 是来自instantiateItem中返回的,判断当前显示的view是否关该object,个人感觉这个方法应该是与view获取焦点有关。

FragmentPagerAdapter

它继承PagerAdapter,通过名字,我们知道它专注于任意页为Fragment的情况。通过FragmentPagerAdapter文档所述,该类中每一个生成的Fragment都将保存在内存中,所以缺点非常明显,对于存在相对较多的fragment,程序将会吃掉非常多的内容。所以FragmentPagerAdapter适合那些相数量相对较少,静态的页面。对于存在多个fragment的情况,一般推荐使用FragmentStatePagerAdapter。FragmentPagerAdapter重载了几个必须实现的函数:

getItem()
不是继承自PagerAdapter,是FragmentPagerAdapter自身的一个函数,目的是生成我们需要的fragment。该方法会被FragmentPagerAdapter.instantiateItem()方法调用:

  @Override
  public Fragment getItem(int position) {
            Fragment fragment = new Fragment();
            Bundle bundle = new Bundle();
            bundle.putString("position", "" + position);
            fragment.setArguments(bundle);
            return fragment;
        }

destoryItem()
该函数被调用后,会对Fragment进行FragmentTransaction.detach(),它并不是remove,而是detach[解除附着]了,因此fragment依旧在FragmentManager的管理中,Fragment依旧会占有资源。

instantiateItem()
它首先会判断一下要生成的Fragment是否已经存在[因为FragmentPagerAdapter通过FragmentManager保留所有已经生成的fragment],如果存在,那么使用旧的fragment,旧的fragment将会被attach;如果不存在,就调用getItem()生成一个新的,新的对象将会被保存,并FragmentTransation.add().举个例子:

public class CustomFragment extends Fragment {

    public static final String BUNDLE_KEY = CustomFragment.class.getSimpleName();

    private String mKey;

    private List<String> mList = new ArrayList<>();

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_layout, null);
        TextView tv = (TextView) rootView.findViewById(R.id.id_tv_content);
        tv.setText(getContent());

        return rootView;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        init();
    }

    private void init() {
        for (int i = 0; i < 2000; i++) {
            mList.add(String.valueOf(i));
        }

        Log.d(BUNDLE_KEY, "---" + mKey + "------>>" + mList.size());
    }

    private String getContent() {
        Bundle bundle = getArguments();
        mKey = bundle.getString(BUNDLE_KEY);

        Log.d(BUNDLE_KEY, "---->>create view--->>" + mKey);

        return TextUtils.isEmpty(mKey) ? "content" : "position" + mKey;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        Log.d(BUNDLE_KEY, "onDestroyView ---->>" + mKey);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(BUNDLE_KEY, "onDestroy ---->>" + mKey);
    }

    @Override
    public void onDetach() {
        super.onDetach();
        Log.d(BUNDLE_KEY, "onDetach --->>>" + mKey);
    }
}

主要看list.size():

private void init() {
        for (int i = 0; i < 2000; i++) {
            mList.add(String.valueOf(i));
        }

        Log.d(BUNDLE_KEY, "---" + mKey + "------>>" + mList.size());
    }

activity中方法:


    ViewPager mViewPager;

    List<String> mListContent = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        for (int i = 0; i < 30; i++) {
            mListContent.add(String.valueOf(i));
        }

        mViewPager = (ViewPager) findViewById(R.id.id_view_pager);
        mViewPager.setAdapter(new MyFragmentPagerAdapter(getSupportFragmentManager()));
    }


    private class MyFragmentPagerAdapter extends FragmentPagerAdapter {

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

        @Override
        public Fragment getItem(int position) {
            CustomFragment fragment = new CustomFragment();
            Bundle bundle = new Bundle();
            bundle.putString(CustomFragment.BUNDLE_KEY, "" + position);
            fragment.setArguments(bundle);
            return fragment;
        }

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

检查我们的log文件:
这里写图片描述

会发现list大小变成了原来的两倍,说明fragment没有被销毁,只有Fragment的视图被destroyView,而是被存储起来了,再次获取时,mlist并没有重新new,而是直接使用存储起来的。

FragmentStatePagerAdapter

与FragmentPagerAdapter一致,是继承自PagerAdapter,但是正如”State”所表明的含义一样,该PagerAdapter的实现将只保留当前页面,当页面离开时,就会被消除,释放其资源。在页面需要显示时,生成新的页面。这样实现的好处就是当拥有大量的页面时,不必在内存中占用大量的内存

getItem()
是FragmentStatePagerAdapter自己的方法,目的与FragmentStateAdapter中getItem()一致,生成需要的Fragment

instantiateItem()
此函数会调用getItem()函数,返回新的函数,新的对象将会被FragmentTransaction.add().FragmentStatePagerAdapter就是通过这种方式,每一次都会创建新的Fragment,而在不需要的情况下立刻释放资源,来达到节省内存的目的

destroyItem()
将Fragment移除,此时使用的FragementTransaction.remove(),并释放其资源:

   private class MyFragmentStatePagerAdapter extends FragmentStatePagerAdapter {

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

        @Override
        public Fragment getItem(int position) {
            CustomFragment fragment = new CustomFragment();
            Bundle bundle = new Bundle();
            bundle.putString(CustomFragment.BUNDLE_KEY, "" + position);
            fragment.setArguments(bundle);
            return fragment;
        }

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

MainActivity:

mViewPager.setAdapter(new MyFragmentStatePagerAdapter(getSupportFragmentManager()));

通过log可以看出:
这里写图片描述

最后

有的时候,我们不希望维持Fragment的状态,因为现在有很多动态的tab,将会存在多个fragment的情况,此时我们不能使用FragmentPagerAdapter,因为会占有大量的内存;使用FragmentStatePagerAdapter也不好,通过源码分析会发现FragmentPagerAdapter内部是通过两个集合来保存加入的Fragment和状态的:
这里写图片描述
因此,我们可以尝试自定义自己的PagerAdapter,不保存Fragment的状态,也不存储所有的fragment:

public abstract class CustomFragmentPagerAdapter extends PagerAdapter {

    private static final String TAG = CustomFragmentPagerAdapter.class.getSimpleName();

    private final FragmentManager mFragmentManager;
    private FragmentTransaction mCurTransaction;
    private Fragment mCurrentPrimaryItem;

    public CustomFragmentPagerAdapter(FragmentManager fm) {
        mFragmentManager = fm;
    }

    public abstract Fragment getItem(int position);

    @Override
    public void startUpdate(ViewGroup container) {
        if (container.getId() == View.NO_ID) {
            throw new IllegalStateException("ViewPager with adapter " + this + " requires a view id");
        }
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        Fragment fragment = getItem(position);
        Log.i(TAG, "Adding fragment item #" + position + ": f=" + fragment);
        fragment.setMenuVisibility(false);
        fragment.setUserVisibleHint(false);
        mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), getItemId(position)));
        return fragment;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        Fragment fragment = (Fragment) object;

        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        Log.i(TAG, "Removing fragment #" + position + ": f=" + fragment + " v=" + fragment.getView());
        mCurTransaction.remove(fragment);
    }

    @Override
    public void setPrimaryItem(ViewGroup container, int position, Object object) {
        Fragment fragment = (Fragment) object;
        if (fragment != mCurrentPrimaryItem) {
            if (mCurrentPrimaryItem != null) {
                mCurrentPrimaryItem.setMenuVisibility(false);
                mCurrentPrimaryItem.setUserVisibleHint(false);
            }
            if (fragment != null) {
                fragment.setMenuVisibility(true);
                fragment.setUserVisibleHint(true);
            }
            mCurrentPrimaryItem = fragment;
        }
    }

    public void commitUpdate() {
        if (mCurTransaction != null) {
            mCurTransaction.commitNowAllowingStateLoss();
            mCurTransaction = null;
        }
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return ((Fragment) object).getView() == view;
    }

    protected String makeFragmentName(int viewId, long id) {
        return "android:switcher:" + viewId + ":" + id;
    }

    protected long getItemId(int position) {
        return position;
    }
}

使用是:

public class MainActivity extends AppCompatActivity {

    ViewPager mViewPager;
    List<String> mListContent = new ArrayList<>();

    Fragment mCurrentFragment;
    CustomFragmentPagerAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        for (int i = 0; i < 30; i++) {
            mListContent.add(String.valueOf(i));
        }

        mViewPager = (ViewPager) findViewById(R.id.id_view_pager);
        mViewPager.setAdapter(mAdapter = new CustomFragmentPagerAdapter(getSupportFragmentManager()) {
            @Override
            public int getCount() {
                return mListContent.size();
            }

            @Override
            public Fragment getItem(int position) {
                CustomFragment fragment = new CustomFragment();
                Bundle bundle = new Bundle();
                bundle.putString(CustomFragment.BUNDLE_KEY, "" + position);
                fragment.setArguments(bundle);
                return fragment;
            }

            @Override
            public void setPrimaryItem(ViewGroup container, int position, Object object) {
                super.setPrimaryItem(container, position, object);
                if (mCurrentFragment == null) {
                    commitUpdate();
                }
                mCurrentFragment = (Fragment) object;
            }

            @Override
            public int getItemPosition(Object object) {
                return PagerAdapter.POSITION_NONE;
            }
        });

        mViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
            @Override
            public void onPageScrollStateChanged(int state) {
                super.onPageScrollStateChanged(state);
                //在viewPager状态停止时,我们才提交transcription
                if (state == ViewPager.SCROLL_STATE_IDLE) {  
                    mAdapter.commitUpdate();
                }
            }
        });
    }
}

更新于2018/06/28 19:49

最近在业务中遇到一个问题:在ViewPager+FragmentPagerAdapter中,可能需要增加\删除,更新数据。类似于ListView,RecyclerView中Adapter直接使用notifyDataSetChanged()来刷新数据,但是没什么卵用。
基于这个问题,百度和google之后,很多人建议将FragmentPagerAdapter直接替换成FragmentStatePagerAdapter,并将FragmentStatePagerAdapter.getItemPosition方法改成POSITION_NONE返回值:

@Override
public int getItemPosition(Object object) {
       return PagerAdapter.POSITION_NONE;
}

基于这个说法,我也尝试了,的确能解决问题。但是现在的问题是,如果我非要使用FragmentPagerAdapter那该怎么做呢?我们主要分析FragmentPagerAdapter&FragmentStatePageAdapter不同点了,FPA(FragmentPagerAdapter)会生成的所有Fragment都保存在内存中,即使destoryItem方法也只是FragmentManager.detach这个Fragment,而不是销毁它;而FSPA(FragmentPagerStateAdapter)只专注于当前Fragment页面,离开视线的Fragment都将被remove掉,即从内存中去掉。

对于FPA想要notifyDataHasChanged起作用,需要重写getItem()和instantiateItem()方法,getItem是产出新的Fragment,而instantiateItem是通过FragmentManager管理获取存储的Fragment,此时的Fragment我们可以看成是old Fragment,虽然是从内存中获取的,但还是会执行onCreateView(),onActivityCreate()等等生命周期方法,但是此时不能使用getArguments()方法重新获取参数,因为你重新setArguments()时会报java.lang.IllegalStateException: Fragment already active异常,此时我们可以使用set方法将我们的参数设置进去,然后再执行相关操作。

FPA.notifyDataHasChanged()方法最终都会调用instantiateItem(), 所以我们设置参数时,先要获取父类的instantiateItem()获取Fragment,然后再设置相关参数,伪代码如下:

public class MyFragmentPagerAdapter extends FragmentPagerAdapter {
  @Override
  public Object instantiateItem(ViewGroup container, int position) {
      MyFragment myFragment = (MyFragment)super.instantiateItem(container, position);
      //相应动作
      myFragment.setValue("newValue");
      return myFragment;
  }

  @Overrider
  public Fragment getItem(int position){
      MyFragment myFragment = new MyFragment();
      Bundle bundle = new Bundle();
      myFragment.setAgrument(bundle);
      return myFragment;
    }    

    @Override
    public int getItemPosition(Object object) {
           return PagerAdapter.POSITION_NONE;
    }
}

当然,还需要设置getItemPosition方法返回值为PagerAdapter.POSITION_NONE,这里是来自FragmentPagerAdapter.notifyDataHasChanged():

public void notifyDataSetChanged() {
        synchronized (this) {
            if (mViewPagerObserver != null) {
                mViewPagerObserver.onChanged();
            }
        }
        mObservable.notifyChanged();
    }

这里的mViewPagerObserver来自ViewPager的内部类:PagerObserver中解析:

void dataSetChanged() {
        //代码省略
        final int adapterCount = mAdapter.getCount();
        for (int i = 0; i < mItems.size(); i++) {
            final ItemInfo ii = mItems.get(i);
            final int newPos = mAdapter.getItemPosition(ii.object);

            //如果没有更改,数据将不会更新
            if (newPos == PagerAdapter.POSITION_UNCHANGED) {
                continue;
            }

            //代码省略
    }
}

所以个人的建议是,如果你的ViewPager关联的Fragment数目不多,且只存在数据的更新,而不存在数目的更新,推荐你使用FragmentPagerAdapter,但是如果你的ViewPager关联的Fragment数目较多,而且还存在动态的增加和删除,推荐你使用FragmentStatePagerAdapter,因为你只需要管理当前可见的Fragment即可,而相对于FragmentPagerAdapter,要想从内存里面清除,或者替换,或者增加新的Fragment,还是比较困难的,当然了,你可以自定义PagerAdapter,管理你想要的Fragment.

参考资料:
http://www.cnblogs.com/lianghui66/p/3607091.html
http://blog.csdn.net/hknock/article/details/46741573
http://blog.csdn.net/jackrex/article/details/9885469
https://blog.csdn.net/dyllove98/article/details/8806576

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值