FragmentPagerAdapter+ViewPager 更新问题


声明:本文转载自https://my.oschina.net/ososchina/blog/3004180,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

场景

存在一种需求,当用户系统中,属于某一组织的用户登录之后(或者账户切换),要求主页面显示不同的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;
        }
    }

 

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

本文发表于2019年01月21日 20:00
(c)注:本文转载自https://my.oschina.net/ososchina/blog/3004180,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 1358 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1