内容简介:场景存在一种需求,当用户系统中,属于某一组织的用户登录之后(或者账户切换),要求主页面显示不同的ViewPager + Fragment组合,并且要求app无需退出就能刷新组合以及组合中的页面。此外,为了保证Fragment和Fragment中View不必要的inflate和渲染,要求尽可能重用已存在的Fragment和View。显然FragmentPagerAdapter是首选。但是存在三个问题:
场景
存在一种需求,当用户系统中,属于某一组织的用户登录之后(或者账户切换),要求主页面显示不同的ViewPager + Fragment组合,并且要求app无需退出就能刷新组合以及组合中的页面。
此外,为了保证Fragment和Fragment中View不必要的inflate和渲染,要求尽可能重用已存在的Fragment和View。显然FragmentPagerAdapter是首选。但是存在三个问题:
1、FragmentPagerAdapter默认无法更新,需要重写getItemPosition,使其返回值为PagerAdapter.POSITION_NONE
2、重用的Fragment设置参数无法重新初始化
3、重用的Fragment类型和新的Fragment类型存在不匹配问题,如旧的UserFragment页面,但是新的要求是ListFragment,所以类型存在问题。
解决方案
我们需要重写FragmentPagerAdapter,但问题是存在各种不方便的因素,因此,我们需要自定义FragmentPagerAdapter。
public abstract class CustomFragmentPagerAdapter extends PagerAdapter { private static final String TAG = "FragmentPagerAdapter"; private static final boolean DEBUG = false; private final FragmentManager mFragmentManager; private FragmentTransaction mCurTransaction = null; private Fragment mCurrentPrimaryItem = null; private final LongSparseArray<String> fragmentViewTypeManager = new LongSparseArray<String>(); public CustomFragmentPagerAdapter(FragmentManager fm) { mFragmentManager = fm; } @Override public void startUpdate(ViewGroup container) { if (container.getId() == View.NO_ID) { throw new IllegalStateException("ViewPager with adapter " + this + " requires a view id"); } } @SuppressWarnings("ReferenceEquality") @Override public Object instantiateItem(ViewGroup container, int position) { mCurTransaction = beginTransaction(); final long itemId = getItemId(position); final int count = this.getFragmentTypeCount(); int viewType = 0; if(count>0){ viewType = getItemFragmentType(position); //获取类型 } if(viewType>0 && viewType>=count){ throw new IllegalArgumentException("{viewType >= TypeCount} is not allowed"); } // Do we already have this fragment? final String name = makeFragmentName(container.getId(), itemId, viewType); //生成tag final String oldName = fragmentViewTypeManager.get(position); Fragment fragment = mFragmentManager.findFragmentByTag(name); if (fragment != null) { if(!TextUtils.isEmpty(oldName) && !name.equals(oldName)) { //如果发现新旧类型不一致,移除旧类型 if (DEBUG) Log.v(TAG, "Removeing item #" + itemId + ": f=" + fragment); mCurTransaction.remove(fragment); //获取新类型 fragment = getItem(null,position); if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment); mCurTransaction.add(container.getId(), fragment,name); }else { Fragment newFragment = getItem(fragment,position); //获取newFragment ,如果2次fragment不一致,移除旧的fragment if(newFragment!=fragment){ if (DEBUG) Log.v(TAG, "Removeing item #" + itemId + ": f=" + fragment); mCurTransaction.remove(fragment); fragment = newFragment; if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment); mCurTransaction.add(container.getId(), fragment,name); }else { //如果获取到fragment与原来的是同一个,attach即可 if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment); mCurTransaction.attach(fragment); } } } else { fragment = getItem(fragment,position); if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment); mCurTransaction.add(container.getId(), fragment,name); } fragmentViewTypeManager.put(position,name); //保存该位置的tag if (fragment != mCurrentPrimaryItem) { fragment.setMenuVisibility(false); fragment.setUserVisibleHint(false); } return fragment; } @Override public void destroyItem(ViewGroup container, int position, Object object) { mCurTransaction = beginTransaction(); if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object + " v=" + ((Fragment)object).getView()); mCurTransaction.detach((Fragment)object); //dattach fragment } @SuppressWarnings("ReferenceEquality") @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; //设置当前的fragment } } @Override public void finishUpdate(ViewGroup container) { if (mCurTransaction != null) { mCurTransaction.commitNowAllowingStateLoss(); //提交,注意该方法将任务加入到mainLooper中,可能产生延迟 mCurTransaction = null; } } @Override public boolean isViewFromObject(View view, Object object) { return ((Fragment)object).getView() == view; } @Override public Parcelable saveState() { return null; } @Override public void restoreState(Parcelable state, ClassLoader loader) { } /** * Return a unique identifier for the item at the given position. * * <p>The default implementation returns the given position. * Subclasses should override this method if the positions of items can change.</p> * * @param position Position within this adapter * @return Unique identifier for the item at position */ public long getItemId(int position) { return position; } //生成tag public static String makeFragmentName(int viewId, long id,int viewType) { return "android:switcher:" + viewId + ":" + id+":"+viewType; } public FragmentTransaction beginTransaction(){ if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } return mCurTransaction; } public FragmentManager getFragmentManager(){ return mFragmentManager; } /** * 获取当前位置的fragment */ public abstract Fragment getItem(Fragment contentFragment,int position); /** * 获取当前位置的type FragmentType */ public abstract int getItemFragmentType(int position); /** * 获取当前类型的数量 FragmentCount */ public abstract int getFragmentTypeCount(); /** * 在ViewPager中调用,告诉ViewPager该位置的Fragment是可以被替换和更新的 */ @Override public int getItemPosition(Object object) { return PagerAdapter.POSITION_NONE; } public boolean isEmpty(){ return getCount()==0; } }
到这里,我们便可以实现他的子类
static class MyPagerAdapter extends CustomFragmentPagerAdapter{ private ArrayList<FragmentTabEntity> dataEntities; private final String TAG_NAME = "MyPagerAdapter "; public MyPagerAdapter(FragmentManager fm,List<FragmentTabEntity> dataEntities) { super(fm); this.dataEntities = new ArrayList<>(); this.dataEntities.addAll(dataEntities); } @SuppressWarnings("unchecked") public void updateDataEntities(List<FragmentTabEntity> dataEntities) { this.dataEntities.clear(); if(dataEntities!=null && dataEntities.size()>0){ this.dataEntities.addAll(dataEntities); } this.notifyDataSetChanged(); } @Override public CharSequence getPageTitle(int position) { final FragmentTabEntity entity = dataEntities.get(position); return entity.getTitle(); } @Override public int getItemFragmentType(int position) { final FragmentTabEntity dataEntity = dataEntities.get(position); return dataEntity.getType(position); } @Override public int getFragmentTypeCount() { return dataEntity.getTotalType(); } @Override public Fragment getItem(Fragment contentFragment,int position) { final int viewType = getItemFragmentType(position); final FragmentTabEntity dataEntity = dataEntities.get(position); BaseFragment fragment = null; if(contentFragment==null) { if (viewType == 0) { fragment = new IndexFragment(); } else if(viewType ==1){ fragment = new UserFragment(); } else if(viewType ==1){ fragment = new WebFragment(); }else{ fragment = new ListFragment(); } }else{ fragment = (BaseFragment) contentFragment; } if(fragment!=null) { Bundle fb = new Bundle(); fb.putString(BaseFragment.KEY_TYPE, dataEntity.getType()); fb.putString(BaseFragment.KEY_TITLE, dataEntity.getTitle()); fragment.setArguments(fb); } return fragment; } @Override public int getCount() { return dataEntities.size(); } }
使用方法
if((pager.getAdapter() instanceof MyPagerAdapter)){ mTabPagerAdapter = (MyPagerAdapter) pager.getAdapter(); } if(mTabPagerAdapter==null){ mTabPagerAdapter = new MyPagerAdapter(getChildFragmentManager(),data); pager.setAdapter(mTabPagerAdapter); }else{ mTabPagerAdapter.updateDataEntities(data); }
Fragment ViewCache问题 & 生命周期问题
到这一步事实上我们的自定义FragmentPagerAdapter已经完成了,但是这里还存在不完美的问题,那就是Fragment中添加了View Cache的情况,此外,对于生命周期的控制,可能或多或少出现旧页面向新页面过渡时闪烁问题。
1、View Cache 问题
先来看看这种Fragment的定义方式
public class BaseFragment extends Fragment{ private SoftReference<View> mRootViewCache = null; private boolean isFinishedInflated = false; @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState{ View root = null; if(!cacheIsEmpty()){ root = mRootViewCache.get(); } if(root==null){ root = inflater.inflate(R.layout.base_view_layout, container, false); root.findViewById(R.id.toolbar).setVisibility(View.GONE); mRootViewCache = new SoftReference<View>(root); } return root; } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); try { isFinishedInflated = true; renderFragmentView(view); } catch (Exception e) { e.printStackTrace(); } } private boolean cacheIsEmpty(){ return mRootViewCache==null || mRootViewCache.get()==null; } @Override public void onResume() { super.onResume(); if(getUserVisibleHint() ){ onFragmetShow(); } } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if(!isFinishedInflated) return; if( getUserVisibleHint()){ onFragmetShow(); }else if(isResumed()){ onFragmetHide(); } } @Override public void onStop() { super.onStop(); if(getUserVisibleHint()){ onFragmetHide(); } } public void onFragmetShow(){ if(getView()==null) return ; getView().post(new Runnable(){ public void run(){ //这里可以用来获取Fragment的参数,然后更新 } }); } public void onFragmetHide(){ } }
对于原生页面,新旧页面闪烁并不是很明显,但是对于Webview页面,这种闪烁很明显,导致该问题的原因是View Cache,因此,我们需要在Fragment中添加clearView方法来清空一下Cache
public void clearView() { if(mRootViewCache!=null){ mRootViewCache.clear(); } }
在MyPagerAdapter的getItem方法中,我们有必要植入一个flag
@Override public Fragment getItem(Fragment contentFragment,int position) { final int viewType = getItemFragmentType(position); final FragmentTabEntity dataEntity = dataEntities.get(position); BaseFragment fragment = null; if(contentFragment==null) { if (viewType == 0) { fragment = new IndexFragment(); } else if(viewType ==1){ fragment = new UserFragment(); } else if(viewType ==1){ fragment = new WebFragment(); }else{ fragment = new ListFragment(); } }else{ fragment = (BaseFragment) contentFragment; } final Bundle fa = fragment.getArguments(); if(fa!=null) { final String oldTag = fa.getString("TAG", ""); if (!TextUtils.isEmpty(oldTag) && !oldUrl.oldTag(dataEntity.getMd5())) { fragment.clearView(); //如果tag不一致,清空一下view cache } } if(fragment!=null) { Bundle fb = new Bundle(); fb.putString("TAG",dataEntity.getMd5()); //植入新的tag fb.putString(BaseFragment.KEY_TYPE, dataEntity.getType()); fb.putString(BaseFragment.KEY_TITLE, dataEntity.getTitle()); fragment.setArguments(fb); } return fragment; }
2、生命周期问题
关于onFragmentShow与onFragmentHide的生命周期用法,请参考《Fragment页面切换》,这里我们主要说一下mainLooper问题
public void onFragmetShow(){ if(getView()==null) return ; getView().post(new Runnable(){ public void run(){ //这里可以用来获取Fragment的参数,然后更新 } }); }
如果要更新UI,我们建议这里使用post将消息发送到mainLooper,为什么要这样呢?
主要原因是FragmentPagerAdapter的commit方法,这个方法是将任务发送到mainLooper的队列中,而不是立即执行,基于队列的先进先出,我们将更新消息加入到Fragment add/attach消息之后,能够更好的获取Fragment 的argument,否则可能导致获取到的argument是旧的,导致我们更新时使用了旧的参数。当然,可以参考《 Android Fragment重复添加问题解决方法 》,原理基本相同。
@Override public void finishUpdate(ViewGroup container) { if (mCurTransaction != null) { mCurTransaction.commitNowAllowingStateLoss(); mCurTransaction = null; } }
以上是一般常见的问题,至于其他问题,可以留言。
以上所述就是小编给大家介绍的《FragmentPagerAdapter+ViewPager 更新问题 原 荐》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- mpvue 问题汇总(持续更新)
- Spark常见问题(持续更新)
- 深度剖析MySQL慢更新问题
- Waterfox 56.0.2,安全问题更新
- Spark一些问题集锦【持续更新】
- Vue添加数据视图不更新问题
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。