1. 程式人生 > >Fragment 頁面切換與UI更新

Fragment 頁面切換與UI更新

由於使用不通的事務方法,場景也是不通的,這裡我們重點討論show/hide與attach/dettach兩類問題。當然,我們繞不開的是add/remove和replace。

一、replace事務

replace相對簡單,對應的是Fragment最簡單的生命週期,因此頁面的切換在onResume中即可。

二、add事務

實際上add和remove雖然是【新增】和【移除】,但是實際上這倆個事務很少同時使用。常見的使用情況反而是attach/dettach+add和show/hide+add事務的組合相對常見。本質上,add+remove的事務組合和replace類似,因此也沒有必要去remove。

單獨的add事務無法實現頁面切換,這裡我們主要說明attach/detach+add和show/hide+add。

2.1 attach/detach+add事務組合

參考android.support.v4.app.FragmentPagerAdapter原始碼:

public Object instantiateItem(ViewGroup container, int position) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }

    final long itemId = getItemId(position);

    // Do we already have this fragment?
    String name = makeFragmentName(container.getId(), itemId);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);
    if (fragment != null) {
        if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
        mCurTransaction.attach(fragment);
    } else {
        fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
        mCurTransaction.add(container.getId(), fragment,
                makeFragmentName(container.getId(), itemId));
    }
    if (fragment != mCurrentPrimaryItem) {
        fragment.setMenuVisibility(false);
        fragment.setUserVisibleHint(false);
    }

    return fragment;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
            + " v=" + ((Fragment)object).getView());
    mCurTransaction.detach((Fragment)object);
}

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

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

這種場景下,使用add只是簡單的新增,attach和dettach負責頁面的切換。Fragment在ViewPager中一般是提前重建的,因此,傳統的Fragment生命週期已經不適合,這裡我們看到setUserVisibleHint被呼叫,因此,我們可以使用,setUserVisibleHint機制。



@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);

      

        if(getUserVisibleHint()) {
   
            onVisible();
        } else {
 
            onInvisible();
        }
}
protected void onVisible(){
      
}
protected void onInvisible(){

}

但是這裡有個問題,在初始化方法instantiateItem中,我們如果在沒有判斷的情況下,強行setUserVisibleHint,可能在onCreate之前執行,造成生命週期混亂。

if (fragment != mCurrentPrimaryItem) {
    fragment.setMenuVisibility(false);
    fragment.setUserVisibleHint(false);
}

實際上在setPrimaryItem比較合理,因為提前在ViewPager提前建立了Fragment並且呼叫了onAttach->onCreate。對於當前的問題,我們的解決方法是


protected boolean isCreated = false;
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    isCreated = true;
}

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);

         if(!isCreated) return;

        if(getUserVisibleHint()) {
   
            onShow();
        } else {
 
            onHide();
        }
}
protected void onShow(){
      
}
protected void onHide(){

}
 

2.1 show/hide+add事務組合

這類主要運用於FragmentManager自行管理的頁面

   public Fragment showFragment(FragmentManager fragmentManager,int viewId,int position, Bundle bundle) {
       
            FragmentTransaction mCurTransaction = fragmentManager.beginTransaction();
        

        try {

            String name = makeFragmentName(viewId, position);
            Fragment fragment = mFragmentManager.findFragmentByTag(name);
            if (fragment == null) {
                fragment = instantiateItem(position);
                fragment.setUserVisibleHint(false);
            }

            if (mCurrentPrimaryItem != fragment) {
                if (mCurrentPrimaryItem != null) {
                    mCurTransaction.hide(mCurrentPrimaryItem);
                    mCurrentPrimaryItem.setUserVisibleHint(false);
                }
                if (fragment.isAdded()) {
                    mCurTransaction.show(fragment);
                } else {
                    mCurTransaction.add(viewId, fragment, makeFragmentName(mViewContainer.getId(), position));
                }
                mCurrentPrimaryItem = fragment;
            }

            if(bundle!=null)
                mCurrentPrimaryItem.setArguments(bundle);

            if (!mCurrentPrimaryItem.getUserVisibleHint()) {
                mCurrentPrimaryItem.setUserVisibleHint(true);
            }

             mCurTransaction.commit();
            mCurTransaction = null;
            return fragment;

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

同樣,對於這類生命週期,我們也可以參考attach/detach+add的處理方式,但是我們這裡存在更好的方法,而且配合onResume一起呼叫,那就是onHiddenChanged,他的優點是在add時不會呼叫,在hide/show時才會呼叫。因此,在fragment之間切換時會呼叫onHiddenChanged,在Activity之間切換時呼叫onResume,這種更新更好解決問題,而且可以肯定的是,onHiddenChanged在執行過一次onResume之後才會被呼叫


@override
public void onResume(){

   if(isResumed() && isVisible()){ 
//這裡主要isVisible()和getUserVisibleHint類似,onResume在Fragment不可見時也會呼叫,為了防止此情況發生,需要做判斷
       onShow();
   }

}


@override
public void onStop(){  //onstop被呼叫,Fragment頁面必然隱藏
   onHide();

}

@override
public void onHiddenChanged(boolean hidd) {
        if (hidd) {
                //隱藏時所作的事情
            onHide();
        } else {
            //顯示時所作的事情
            onShow();

        }

}

protected void onShow(){
      
}
protected void onHide(){

}

三、FragmentTabHost事務

FragmentTabHost相對來說沒有就沒有那麼簡單了,內部通過了attach/detach+add的事務模式,對於這種更新,我們只能通過手動方式來實現了。

文章轉自 https://dwz.pm/7q