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 }