FragmentPagerAdapter+ViewPager 更新问题 原 荐

栏目: Android · 发布时间: 5年前

内容简介:场景存在一种需求,当用户系统中,属于某一组织的用户登录之后(或者账户切换),要求主页面显示不同的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 更新问题 原 荐》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

写给大家看的设计书(第4版)

写给大家看的设计书(第4版)

Robin Williams / 苏金国、李盼 / 人民邮电出版社 / 2016-1 / 59.00元

畅销设计入门书最新版,让每个人都能成为设计师 在这个创意无处不在的时代,越来越多的人成为设计师。简历、论文、PPT、个人主页、博客、活动海报、给客人的邮件、名片……,处处都在考验你的设计能力。 美术功课不好?没有艺术细胞?毫无设计经验? 没关系!在设计大师RobinWilliams看来,设计其实很简单。在这部畅销全球多年、影响了一代设计师的经典著作中,RobinWilliams将......一起来看看 《写给大家看的设计书(第4版)》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

随机密码生成器
随机密码生成器

多种字符组合密码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具