FragmentPagerAdapter 頁面類型、數量、內容更新問題

場景

存在一種需求,當用戶系統中,屬於某一組織的用戶登陸以後(或者帳戶切換),要求主頁面顯示不一樣的ViewPager + Fragment組合,而且要求app無需退出就能刷新組合以及組合中的頁面。android

此外,爲了保證Fragment和Fragment中View沒必要要的inflate和渲染,要求儘量重用已存在的Fragment和View。顯然FragmentPagerAdapter是首選。可是存在三個問題:緩存

一、FragmentPagerAdapter默認沒法更新,須要重寫getItemPosition,返回值爲PagerAdapter.POSITION_NONE才能夠更新app

二、重用的Fragment設置參數沒法從新初始化ide

三、重用的Fragment類型和新的Fragment類型存在不匹配問題,如舊的UserFragment頁面,可是新的要求是ListFragment,因此類型存在問題。oop

 

解決方案

咱們須要重寫FragmentPagerAdapter,但問題是存在各類不方便的因素,所以,咱們須要自定義FragmentPagerAdapter。post

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();
        }

    }

使用方法ui

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的狀況,此外,對於生命週期的控制,可能或多或少出現舊頁面向新頁面過渡時閃爍問題。this

 

一、View Cache 問題spa

先來看看這種Fragment的定義方式

public class BaseFragment extends Fragment{

  
private SoftReference<View> mRootViewCache = null; //實現viewcache
private 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;
    }

@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(){


   }
 
   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;
        }

 

二、生命週期問題

關於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重複添加問題解決方法》,原理基本相同。

 

以上是通常常見的問題,至於其餘問題,能夠留言。

相關文章
相關標籤/搜索