FragmentPagerAdapter+ViewPager 更新问题 原 荐

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

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

查看所有标签

猜你喜欢:

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

Trading and Exchanges

Trading and Exchanges

Larry Harris / Oxford University Press, USA / 2002-10-24 / USD 95.00

This book is about trading, the people who trade securities and contracts, the marketplaces where they trade, and the rules that govern it. Readers will learn about investors, brokers, dealers, arbit......一起来看看 《Trading and Exchanges》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

MD5 加密
MD5 加密

MD5 加密工具

SHA 加密
SHA 加密

SHA 加密工具