侧边栏壁纸
博主头像
落叶人生博主等级

走进秋风,寻找秋天的落叶

  • 累计撰写 130562 篇文章
  • 累计创建 28 个标签
  • 累计收到 9 条评论
标签搜索

目 录CONTENT

文章目录

FragmentPagerAdapter 页面类型、数量、内容更新问题

2023-12-12 星期二 / 0 评论 / 0 点赞 / 44 阅读 / 17679 字

场景 存在一种需求,当用户系统中,属于某一组织的用户登录之后(或者账户切换),要求主页面显示不同的ViewPager + Fragment组合,并且要求app无需退出就能刷新组合以及组合中的页面。 此

场景

存在一种需求,当用户系统中,属于某一组织的用户登录之后(或者账户切换),要求主页面显示不同的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) {  //viewPager必须赋值ID,否则无法添加fragment            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 fragmentType = 0;        if(count>0){            fragmentType = getItemFragmentType(position);        }        if(fragmentType>0 && fragmentType>=count){            throw  new IllegalArgumentException("{fragmentType's number >= fragmentTypeCount's number} is illegal");        }        // 生成tag,用于保存和标记每个位置的fragment        final String name = makeFragmentName(container.getId(), itemId); //生成tag        final String oldFragmentName = fragmentViewTypeManager.get(fragmentType);        Fragment fragment = mFragmentManager.findFragmentByTag(name);        if (fragment != null) {            final String fragmentClassName = fragment.getClass().getName();            if(!fragmentClassName.equals(oldFragmentName)) {                  //如果发现新旧类型不一致,移除旧类型                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);        }         if(fragment!=null){             //保存类型,用来校验缓存的正确性            fragmentViewTypeManager.put(fragmentType,fragment.getClass().getName());        }        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) {    }    /**     *     *  获取每个fragment的id,注意保证唯一性     */    public long getItemId(int position) {        return position;    }   //生成tag    public static String makeFragmentName(int viewId, long id) {        return "android:switcher:" + viewId + ":" + id;    }    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是可以被替换和更新的    * 这里人可以继续优化,由于ViewPager.LayoutParams中的position是非public的,因此要优化可以在该类的基类中完成    */   @Override    public int getItemPosition(Object object) {        if(!(object instanceOf Fragment))        {         return PagerAdapter.POSITION_NONE;       }                return PagerAdapter.POSITION_UNCHANGED;         }    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);  //获取类型,注意,最大值不能大于getFragmentTypeCount()        }        @Override        public int getFragmentTypeCount() {                       return FragmentTabEntity.getTotalTypeCount();           //获取所有fragment的类型数量,一般是固定值,主要看程序实现方式了        }        @Override        public Fragment getItem(Fragment contentFragment,int position) {            final int fragmentType = getItemFragmentType(position);            final FragmentTabEntity dataEntity = dataEntities.get(position);            BaseFragment fragment = null;            if(contentFragment==null) {                if (fragmentType == 0) {                    fragment = new IndexFragment();                  } else  if(fragmentType ==1){                     fragment = new UserFragment();                 } else  if(fragmentType ==2){                      fragment = new WebFragment();                 }else if(fragmentType ==3){                    fragment = new ListFragment();                 }            }else{                fragment = (BaseFragment) contentFragment;            }            fragment.setPosition(position);            if(fragment!=null) {                Bundle fb = new Bundle();                fb.putString(BaseFragment.KEY_TYPE, dataEntity.getType());                fb.putString(BaseFragment.KEY_TITLE, dataEntity.getTitle());                fragment.setNoneStateArguments(fb); //使用非状态参数传递方法            }            return fragment;        }        @Override        public int getCount() {            return dataEntities.size();        }    }

使用方法

private MyPagerAdapter  mTabPagerAdapter;public  void updatePager(List<FragmentTabEntity> data){ 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; //实现viewcacheprivate boolean isFinishedInflated = false;private Bundle mNoneStateArguments;private int position = -1;public void setPosition(int position){  this.position = position;}public int getPosition(){   return  this.position;}  @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;    }@Overridepublic void onResume() {        super.onResume();        if(getUserVisibleHint() ){            onFragmetShow();        }    }@Overridepublic void setUserVisibleHint(boolean isVisibleToUser) {        super.setUserVisibleHint(isVisibleToUser);        if(!isFinishedInflated) return;        if( getUserVisibleHint()){            onFragmetShow();        }else if(isResumed()){            onFragmetHide();        }}    @Overridepublic 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(){   }    public void setNoneStateArguments(Bundle bundle){  //解决已初始化状态的参数刷新问题       this.mNoneStateArguments = bundle;   }  public void getNoneStateArguments(){   return  this.mNoneStateArguments!=null? this.mNoneStateArguments:new Bundle(); }}

 

对于原生页面,新旧页面闪烁并不是很明显,但是对于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 fragmentType = getItemFragmentType(position);            final FragmentTabEntity dataEntity = dataEntities.get(position);            BaseFragment fragment = null;            if(contentFragment==null) {                if (viewType == 0) {                    fragment = new IndexFragment();                  } else  if(fragmentType ==1){                     fragment = new UserFragment();                 } else  if(fragmentType ==2){                      fragment = new WebFragment();                 }else if(fragmentType ==3){                    fragment = new ListFragment();                }            }else{                fragment = (BaseFragment) contentFragment;            }            final Bundle fa = fragment.getArguments();            if(fa!=null) {                final String oldTag = fa.getString("md5", "");                if (!TextUtils.isEmpty(oldTag) && !oldUrl.oldTag(dataEntity.getMd5())) {                    fragment.clearView();  //如果tag不一致,清空一下view cache                }            }            if(fragment!=null) {                Bundle fb = new Bundle();                                  fb.putString("md5",dataEntity.getMd5());   //植入新的md5 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的finishUpdate中使用了commit方法,这个方法是将任务发送到mainLooper的队列中,而不是立即执行。

    @Override    public void finishUpdate(ViewGroup container) {        if (mCurTransaction != null) {            mCurTransaction.commitNowAllowingStateLoss();            mCurTransaction = null;        }    }

基于队列的先进先出,FragmentTransaction将更新消息加入到Fragment add/attach消息之后,我们如果直接获取argument可能出现数据不一致的问题,因此我们需要将我们的方法作为任务同样放入到mainLooper中。如果不这么做,可能导致获取到的argument是旧的,导致我们更新时使用了旧的参数。当然,可以参考《Android Fragment重复添加问题解决方法》,原理基本相同。

 

以上是一般常见的问题,至于其他问题,可以留言。

广告 广告

评论区