1. 程式人生 > >Fragment裡邊巢狀Fragment

Fragment裡邊巢狀Fragment

一、如何切換Fragment

①、瞭解FragmentManager FragmentManager  fm = getSupportFragmentManager();

作用:管理Fragment的顯示,儲存。 FragmentManger中有三個容器。

第一個用來儲存,Fragment的View,並控制View的顯示

第二個用來儲存,Fragment本身。 第三個用來儲存,Fragment的回退棧。(就是使用android的back鍵的時候,返回上一個Fragment)

②、瞭解FragmentTransaction的方法

FragmentTransaction transaction = fm.benginTransatcion();

//開啟一個事務 transaction.add()  往FragmentManager中新增一個Fragment,

並建立Fragment的View,新增到FrameLayout中顯示。

注:用程式碼建立Fragment本身,並不會執行Fragment的生命週期,也就是 FirstFragment fragment = new FirstFragment();

這時候是不會執行Fragment的生命週期。其生命週期是由Activity來控制的。所以後面說到的銷燬Fragment,

指的並不是銷燬fragment例項也就是fragment == null,而是重新執行了Fragment的生命週期。

(可以這麼理解new FirstFragment()只是創造了身體,靈魂的創造需要依靠Activity)

transaction.remove()  往FragmentManager中銷燬一個Fragment,並銷燬Fragment的View。

如果被移除的Fragment沒有新增到回退棧(回退棧後面會詳細說),這個Fragment例項會完全被移出FragmentManger。

否則只銷毀Fragment的檢視。(同detch()) transaction.replace() 使用另一個Fragment替換當前的,實際上就是remove()然後add()的合體。

(注:如果remove()的Fragment不存在,不會影響add操作。相當於執行了add()操作 如果replace()的Fragment與當前顯示的Fragment相同,則不執行replace()操作) transaction.hide() 隱藏當前的Fragment,僅僅是將View設為不可見,並不會銷燬。 transaction.show():(Fragment必須存在於FragmentManager內,才能使用) 顯示之前隱藏的Fragment。

detach() 移除Fragment的View,和remove()不同,此時fragment的狀態依然由FragmentManager維護。

attach() 重建view檢視,附加到UI上並顯示。 transatcion.commit()//提交一個事務

③、三種實際中切換Fragment的方法

第一種:add()+remove() = replace():銷燬當前顯示的Fragment,新增需要顯示的Fragment。

缺點:Fragment會被重新建立,導致View重繪。View重繪的同時也表示使用者之前在該View上做的操作都消失了。

優點:節省記憶體空間。

使用場景:不要求保留使用者操作。(使用者操作,比如說:ListView滑動到第5個item,但是由於View的重繪,又會回到第一個Item) 示例: public class MainActivity extends AppCompatActivity { private FirstFragment mFirstFragment; private SecondFragment mSecondFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initWidget(); } private void initWidget(){ //初始化Fragment mFirstFragment = new FirstFragment(); mSecondFragment = new SecondFragment(); //首先顯示FirstFragment showFragment(mFirstFragment); } private void showFragment(Fragment fragment){ //直接replace()就可以了,不需要先add~~~ getSupportFragmentManager().beginTransaction() .replace(R.id.main_frame,fragment) .commit(); } /** *使用了ActionBar的menu */ @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.switch_fragment,menu); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()){ case R.id.menu_first_fragment: showFragment(mFirstFragment); break; case R.id.menu_second_fragment: showFragment(mSecondFragment); break; } return super.onOptionsItemSelected(item); } }

第二種:show()+hide():將Fragment檢視隱藏之後在顯示。

缺點:由於Fragment只是被隱藏了,並未被銷燬,所以需要佔用記憶體空間來儲存。

優點:不用重新建立Fragment 使用場景;需要保留使用者資料的情形。

示例:(與上面示例不同的部分) private void initWidget(){

//初始化Fragment mFirstFragment = new FirstFragment()

; mSecondFragment = new SecondFragment();

setUpFragment(); }

private void setUpFragment(){

//首先將所有的Fragment新增到FragmentManager中

,並隱藏,只顯示當前需要的

FragmentgetSupportFragmentManager().beginTransaction().add(R.id.main_frame,mFirstFragment) .add(R.id.main_frame,mSecondFragment) .hide(mSecondFragment) .commit();

//設定當前的Fragment,知道下一次hide那個

Fragment mCurrentFragment = mFirstFragment; } private void showFragment(Fragment fragment){

if (mCurrentFragment != fragment){

getSupportFragmentManager().beginTransaction() .hide(mCurrentFragment) .show(fragment) .commit();

        mCurrentFragment = fragment;//設定當前的Fragment
} }
第三種:attach()+detch():保留Fragment(就是不會重複執行Fragment的生命週期),但刪除Fragment的View(就是第一種的改良版)
使用場景:如果你的當前Activity一直存在,那麼在不希望保留使用者操作的時候,你可以優先使用detach
  private void initWidget(){
        //初始化Fragment
        mFirstFragment = new FirstFragment();
        mSecondFragment = new SecondFragment();
        setUpFragment();
    }
 
    private void setUpFragment(){
        //首先將所有的Fragment新增到FragmentManager中,並刪除View,只顯示當前需要的Fragment
        getSupportFragmentManager().beginTransaction()
                .add(R.id.main_frame,mFirstFragment)
                .add(R.id.main_frame,mSecondFragment)
                .detach(mSecondFragment)
                .commit();
        //設定當前的Fragment,知道下一次hide那個Fragment
        mCurrentFragment = mFirstFragment;
    }
 
 
    private void showFragment(Fragment fragment){
        if (mCurrentFragment != fragment){
            getSupportFragmentManager().beginTransaction()
                    .detach(mCurrentFragment)
                    .attach(fragment)
                    .commit();
            mCurrentFragment = fragment;//設定當前的Fragment
        }
    }
形式上跟第二種方法差不多~~~~,但是內容上卻是第一種方法的改進
二、如何保證不發生Fragment重影
①、發生重影的原理:當將app縮小到後臺,由於資源回收,系統會回收後臺的資源(比如說Activity),並會呼叫onSaveInstanceState()儲存當前狀態。然後當app返回到前臺的時候,系統會重建被被回收的資源。系統會將onSaveInstanceState()儲存的Bundle物件傳遞給當前重建的Activity。就是onCreate(Bundle saveInstance)中的saveInstance引數。也會呼叫onRestoreInstanceState(Bundle saveInstance)來接收bundle物件。(詳細請參考異常生命週期下的資源重建)

舉例說明:
有ABC三個Fragment在FragmentManager中,且A正在顯示在Activity上。然後將app切換到後臺,Activity被回收了。FramgentManager就會呼叫onSaveInstanceState()方法儲存ABC三個Fragment到Bundle中。再重新將app切換到前臺,然後Activity進行。FragmentManager就會在onCreatre()前將ABC三個Fragment加入到FragmentManager中,並將A顯示到Activity上。(未查過原始碼,有一種說法是在onAttach()方法中建立的。)然後Activity繼續呼叫OnCreate()方法就如剛才的示例方法,再次呼叫setUpFragment()再次新增ABC到FragmentManager中,並將A顯示。這就發生了重影。()
②、解決辦法(推薦使用第一種)
第一種:(如果FragmentManger已經存在要建立的Fragment,那麼直接通過Tag從FragmentManager獲取需要的Fragment例項)
  private static final String TAG_FIRST = "FirstFragment";
    private static final String TAG_SECOND = "SecondFragment";
 
    private FirstFragment mFirstFragment;
    private SecondFragment mSecondFragment;
 
    private Fragment mCurrentFragment;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initWidget(savedInstanceState);
 
    }
 
    private void initWidget(Bundle savedInstanceState){
        if(savedInstanceState ==null){
            //初始化Fragment
            mFirstFragment = new FirstFragment();
            mSecondFragment = new SecondFragment();
            setUpFragment();
        }
        else {
            //由於FragmentManager已經存在了這些Fragment直接獲取就可以了
            mFirstFragment = (FirstFragment) getSupportFragmentManager()
                    .findFragmentByTag(TAG_FIRST);
            mSecondFragment = (SecondFragment) getSupportFragmentManager()
                    .findFragmentByTag(TAG_SECOND);
        }
 
    }
 
    private void setUpFragment(){
        //第一步:為每個Fragment新增Tag
        getSupportFragmentManager().beginTransaction()
                .add(R.id.main_frame,mFirstFragment,TAG_FIRST)
                .add(R.id.main_frame,mSecondFragment,TAG_SECOND)
                .detach(mSecondFragment)
                .commit();
        //設定當前的Fragment,知道下一次hide那個Fragment
        mCurrentFragment = mFirstFragment;
    }
第二種:(阻止FragmentManager呼叫onSaveInstanceState()儲存Fragment)
Activity 中的 onSaveInstanceState() 裡面有一句super.onSaveInstanceState(outState);,Google 對於這句話的解釋是 “Always call the superclass so it can save the view hierarchy state”,大概意思是“總是執行這句程式碼來呼叫父類去儲存檢視層的狀態”。通過註釋掉這句話,這樣主 Activity 因為種種原因被回收的時候就不會儲存之前的 fragment state,也可以成功解決重疊的問題。
三、Fragment與Activity的互動
①、Fragment中呼叫Activity的方法

錯誤的使用方法:

在MainActivity新增獲取Data的方法

    /**
     * 獲取資料
     */
    public int getData(){
        return 100;
    }
在FirstFragment直接呼叫該方法
//在FirstFragment中 
   @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        //直接呼叫Activity的方法
        int data = ((MainActivity)getActivity()).getData();
        Log.d("TAG",data+" ");
    }
這樣使用的缺點:該Fragment被MainActivity捆綁了。也就是說只能被MainActivity使用,不能被其他Activity使用了。如果有TestActivity使用FirstFragment,就會報錯,因為((MainActivity)getActivity()).getData();這行程式碼不成立。


最佳實踐:(通過使用監聽器,實現Activity與Fragment之間的解耦)


public class FirstFragment extends Fragment {
    private OnFirstFragmentListener mListener;
    
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        //第二步:將Activity轉換成監聽器
        try {
            mListener = (OnFirstFragmentListener) getActivity();
            Log.d("Tag",mListener.getData()+"");
        }catch (ClassCastException e){
            throw new ClassCastException("must implement OnFirstFragmentListener");
        }
    }
 
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_first,container,false);
        return view;
    }
 
    //第一步:建立監聽器
    public interface OnFirstFragmentListener{
        int getData();
    }
}
之後Activity實現介面:

public class MainActivity extends AppCompatActivity implements FirstFragment.OnFirstFragmentListener {
    public int getData(){
        return 100;
    }
}


②、Activity呼叫Fragment
1、向Fragment傳遞初始化Fragment所必要的資料

需求:有時候我們需要根據Activity掌握的資料建立Fragment。(MainActivity傳遞文章的id給ArticleActivity,ArticleActivity又需要將id傳遞給ArticleFragment,然後讓ArticleFragment通過載入資料)

最佳實踐:()

為Fragment設定建立Fragment的方法,並使用Bundle傳遞資料,使用getArgument獲取資料。


public class FirstFragment extends Fragment{ 
private static final String ARGS_ID = "id";
  @Override  public void onCreate(@Nullable Bundle savedInstanceState) {  
     super.onCreate(savedInstanceState);    
  //獲取資料     
  Bundle bundle = getArguments();    
  int article_id = bundle.getInt(ARGS_ID);  
    Log.d("TAG",article_id+"");  } 

public static FirstFragment newInstance(int id) { 
Bundle args = new Bundle(); 
args.putInt(ARGS_ID,id);
 FirstFragment fragment = new FirstFragment(); 
fragment.setArguments(args); return fragment; }} 

2、Activity與Fragment之間的互動 因為Activity掌握了Fragment的例項,所以直接通過呼叫Fragment的public方法就可以了。
 ③、Fragment與Fragment之間的互動 在Activity中,通過Fragment setTargetFragment(Fragment fragment,int flag);
將需要進行互動的Fragment傳遞給當前Fragment。(int flag為傳遞的Fragment設定id)
 例: public class MainActivity extends AppCompatActivity{ @Override protected void onCreate(Bundle savedInstanceState) { 
super.onCreate(savedInstanceState); 
setContentView(R.layout.activity_main)
 mFirstFragment = new FirstFragment(); 
mSecondFragment = new SecondFragment();  
//將SecondFragment的例項傳遞給
FirstFragment mFirstFragment.setTargetFragment(mSecondFragment,0); } }
 然後在FirstFragment中獲取SecondFragment:
 public class FirstFragment extends Fragment {
 @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); 
//首先判斷獲取的是哪個Fragment switch (getTargetRequestCode()){
 case 0: SecondFragment secondFragment = (SecondFragment) getTargetFragment(); 
//...之後進行互動的操作 break; } } } 
四、Fragment的回退棧  回退棧的作用: 如果你將Fragment任務新增到回退棧,當用戶點選後退按鈕時,將看到上一次的儲存的Fragment。一旦Fragment完全從後退棧中彈出,使用者再次點選後退鍵,則退出當前Activity。
 如何添加回退棧:addBackTo(String name);
//name表示該Fragment在棧中的標識,不需要可以設定為null 程式碼:
 private void showFragment(Fragment fragment){ 
if (mCurrentFragment != fragment){
 getSupportFragmentManager().beginTransaction() .detach(mCurrentFragment) .attach(fragment) .addToBackStack(null)
//將當前framgent加入回退棧 
.commit(); mCurrentFragment = fragment;//設定當前的Fragment 
}