同一個app內的界面切換 用Fragment比較合適,因為Activity比較重量級
Fragment 輕量級,切換靈活
-------------------------------------------
1. 創建和使用 Fragment
2. Fragment 的生命周期 及相關的實際應用
3. 創建一個帶側邊欄的 Activity 以及使用
4. 創建一個 Tabbed Activity 並使用
5. 多個Fragment的切換和狀態保存
6. Fragment的橫豎屏切換
7. Fragment 與 Activity通信
-------------------------------------------
工程代碼:FragmentDemo.zip
-------------------------------------------
1. 創建和使用 Fragment
* 創建一個 帶Fragment的Activity,將Fragment重構到一個新文件中PlaceholderFragment.java
* 創建另一個Fragment,AnotherFragment.java
* 使用按鈕實現兩個Fragment的切換
1.1 在layout fragment_main中添加一個按鈕btnOpenAnohterFragment, 用於打開另一個Fragment;
replace, add, hide, show
public class PlaceholderFragment extends Fragment { public PlaceholderFragment() { } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_main, container, false); rootView.findViewById(R.id.btnOpenAnohterFragment).setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub getFragmentManager().beginTransaction() .addToBackStack(null) //支持返回鍵,否則點返回直接退出app .replace(R.id.container, new AnotherFragment()) .commit(); } }); return rootView; } }
1.2 在AnotherFragment 添加按鈕btnBack,用於返回上一個Fragment
public class AnotherFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // TODO Auto-generated method stub View root = inflater.inflate(R.layout.fragment_another, container, false); root.findViewById(R.id.btnBack).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub getFragmentManager().popBackStack(); } }); return root; //super.onCreateView(inflater, container, savedInstanceState); } }
2. Fragment 的生命周期 及相關的實際應用
比Activity的生命周期多很多,
onCreate,onCreateView,onPause是最常用的
創建兩個Fragment, A, B
1. 打開FragmentA -> 暫停FragmentA -> 恢復FragmentA -> 關閉FragmentA,生命周期變化如下
打開Fragment A 08-13 19:18:32.062 D 27014 27014 FragmentA: onAttach 08-13 19:18:32.062 D 27014 27014 FragmentA: onCreate 08-13 19:18:32.062 D 27014 27014 FragmentA: onCreateView 08-13 19:18:32.062 D 27014 27014 FragmentA: onActivityCreated 08-13 19:18:32.062 D 27014 27014 FragmentA: onStart 08-13 19:18:32.062 D 27014 27014 FragmentA: onResume A Fragment 活動 暫停 Fragment A 08-13 19:19:32.352 D 27014 27014 FragmentA: onPause 08-13 19:19:32.382 D 27014 27014 FragmentA: onStop 恢復Fragment A 08-13 19:20:11.102 D 27014 27014 FragmentA: onStart 08-13 19:20:11.102 D 27014 27014 FragmentA: onResume 退出 Fragment A 08-13 19:20:52.022 D 27014 27014 FragmentA: onPause 08-13 19:20:52.472 D 27014 27014 FragmentA: onStop 08-13 19:20:52.472 D 27014 27014 FragmentA: onDestroyView 08-13 19:20:52.472 D 27014 27014 FragmentA: onDestroy 08-13 19:20:52.472 D 27014 27014 FragmentA: onDetach
2. 打開FragmentA -> 由FragmentA打開FragmentB -> 從FragmentB返回FragmentA,生命周期變化如下
打開Fragment A 08-13 19:51:09.227 D 5395 5395 FragmentA: onAttach 08-13 19:51:09.227 D 5395 5395 FragmentA: onCreate 08-13 19:51:09.237 D 5395 5395 FragmentA: onCreateView 08-13 19:51:09.237 D 5395 5395 FragmentA: onActivityCreated 08-13 19:51:09.237 D 5395 5395 FragmentA: onStart 08-13 19:51:09.237 D 5395 5395 FragmentA: onResume 從A 打開 B 08-13 19:51:12.097 D 5395 5395 FragmentA: onPause 08-13 19:51:12.097 D 5395 5395 FragmentA: onStop 08-13 19:51:12.097 D 5395 5395 FragmentA: onDestroyView
FragmentA內部的View組件完成 08-13 19:51:12.097 D 5395 5395 FragmentB: onAttach 08-13 19:51:12.097 D 5395 5395 FragmentB: onCreate 08-13 19:51:12.097 D 5395 5395 FragmentB: onCreateView 08-13 19:51:12.107 D 5395 5395 FragmentB: onActivityCreated 08-13 19:51:12.107 D 5395 5395 FragmentB: onStart 08-13 19:51:12.107 D 5395 5395 FragmentB: onResume B處於穩定狀態 從B返回A 08-13 19:51:36.067 D 5395 5395 FragmentB: onPause 08-13 19:51:36.067 D 5395 5395 FragmentB: onStop 08-13 19:51:36.067 D 5395 5395 FragmentB: onDestroyView 08-13 19:51:36.067 D 5395 5395 FragmentB: onDestroy 08-13 19:51:36.067 D 5395 5395 FragmentB: onDetach 08-13 19:51:36.077 D 5395 5395 FragmentA: onCreateView 08-13 19:51:36.077 D 5395 5395 FragmentA: onActivityCreated 08-13 19:51:36.077 D 5395 5395 FragmentA: onStart 08-13 19:51:36.077 D 5395 5395 FragmentA: onResume
可以看到,兩個Fragment使用replace方法切換的時候,是A完全銷毀以后,才加載的B
3. 創建一個帶側邊欄的 Activity 以及使用
新建 SliderActivity: 類型Navigation Drawer Activity, 可以看到SDK默認創建了幾個文件
SliderActivity.java
NavigationDrawerFragment.java
activity_slider.xml
fragment_navigation_drawer.xml
fragment_slider.xml
* 默認效果: onCreateView中有一個ListView,來顯示數據
在 NavigationDrawerFragment.java 中 修改onCreateView 中的Adapter,添加 "CARLOZ LIB"
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mDrawerListView = (ListView) inflater.inflate( R.layout.fragment_navigation_drawer, container, false); mDrawerListView .setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { selectItem(position); } }); mDrawerListView.setAdapter(new ArrayAdapter<String>(getActionBar() .getThemedContext(), android.R.layout.simple_list_item_activated_1, android.R.id.text1, new String[] { getString(R.string.title_section1), getString(R.string.title_section2), getString(R.string.title_section3), "CARLOZ LIB",})); mDrawerListView.setItemChecked(mCurrentSelectedPosition, true); return mDrawerListView; }
在 SliderActivity.java 中修改 onSectionAttached 中的添加case語句,即可出現如下效果
public void onSectionAttached(int number) { switch (number) { case 1: mTitle = getString(R.string.title_section1); break; case 2: mTitle = getString(R.string.title_section2); break; case 3: mTitle = getString(R.string.title_section3); break; case 4: mTitle = "CARLOZ LIB"; break; } }
* 自定義側邊欄
創建一個Fragment:CarlozLibFragment.java,並為其創建一個布局carloz_lib_webview.xml,內部有一個WebView控件,順便在AndroidManifest.xml中添加Intent訪問權限;在CarlozLibFragment中重寫onCreateView方法,讓WebView訪問我的個人網站(http://carloz.duapp.com);
public class CarlozLibFragment extends Fragment { private String TAG = "CARLOZ"; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // TODO Auto-generated method stub View root = inflater.inflate(R.layout.carloz_lib_webview, container, false); WebView wv =(WebView)root.findViewById(R.id.wv); Log.d(TAG, "load url: carloz lib"); wv.loadUrl("http://carloz.duapp.com"); return root; } }
將NavigationDrawerFragment.java 中 onCreateView中ListView相關內容刪除,用自定義布局 diy_slider_content.xml (目錄res/layout)替換;diy_slider_content中定義了一個按鈕,用來打開剛剛創建的CarlozLibFragment;
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.diy_slider_content, container, false); root.findViewById(R.id.btnJump).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (mDrawerLayout != null) { // 隱藏側邊欄 mDrawerLayout.closeDrawer(mFragmentContainerView); } if(mCallbacks != null) { mCallbacks.onGotoCarlozLibClicked(); } } }); return root; }
onGotoCarlozLibClicked()這個接口是自定義接口,在 靜態接口 NavigationDrawerCallbacks 中新增定義
public static interface NavigationDrawerCallbacks { /** * Called when an item in the navigation drawer is selected. */ void onNavigationDrawerItemSelected(int position); // 通過回調傳給主界面 void onGotoCarlozLibClicked(); }
需要在主界面SliderActivity中實現該回調方法, 因為主界面實現了 NavigationDrawerFragment.NavigationDrawerCallbacks 接口
@Override public void onGotoCarlozLibClicked() { // 需要實現 NavigationDrawerFragment.java Callback中新增的方法 // 在容器 container 中添加 fragment CarlozLibFragment getSupportFragmentManager().beginTransaction() .replace(R.id.container, new CarlozLibFragment()) .commit(); }
運行結果如下:
4. 創建一個 Tabbed Activity 並使用
默認樣式:
自定義:
* 創建drawble目錄,並在里面放三張圖片img1, img2, img3;
* 創建三個ImageFragment, 分別加載三種圖片
* 在 TabsActivity 中調用三個Fragment
public class Image1Fragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // TODO Auto-generated method stub ImageView iv = new ImageView(getActivity()); iv.setImageResource(R.drawable.img1); return iv; } }
public class SectionsPagerAdapter extends FragmentPagerAdapter { public SectionsPagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { // getItem is called to instantiate the fragment for the given page. // Return a PlaceholderFragment (defined as a static inner class // below). switch(position) { case 0: return new Image1Fragment(); case 1: return new Image2Fragment(); case 2: return new Image3Fragment(); } return null; //return PlaceholderFragment.newInstance(position + 1); }
結果如下
5. 多個Fragment的切換和狀態保存
Fragment的操作有 add, replace, remove三個方法
從2可以看出,使用replace方法來切換Fragment時會完全銷毀上一個Fragment,這樣再切換回上一個Fragment時,它的狀態就會丟失;
5.1 現在 有一個問題,當一個Activity中 使用了 多個同級Fragment,那么多個Fragment切換時如何保存Fragment的狀態?
智慧的網友們給出了解決方案:把用到的Fragment全部使用add方法添加, 使用hide、show方法來控制,需要使用哪個,就顯示哪個。缺點是:幾個Fragment會一直占用內存,直到Fragment銷毀
定義一個Activity:FragmentSwitchActivity,用於存放Fragment
定義兩個Fragment:PlaceholderFragment 和 FragmentSwitch1,用於演示 切換操作
定義一個Callback:FragmentSwitchCallBack,用於管理Fragment 和 切換邏輯
使用這個Callback,控制再多的Fragment都行,這里為了簡單,只控制兩個
public interface FragmentSwitchCallBack { //使用一個List管理當前添加的Fragment public void addFragment(String tag); public void removeFragment(String tag); public void openFragmentByTag(String tag); }
Callback在Fragment中使用如下:
public class FragmentSwitchX extends Fragment { public static String TAG = "FragmentSwitch1"; private FragmentSwitchCallBack mSwitchCallBack; @Override public void onAttach(Activity activity) { // TODO Auto-generated method stub super.onAttach(activity); Log.d(TAG, "onAttach"); mSwitchCallBack = (FragmentSwitchCallBack) activity; mSwitchCallBack.addFragment(TAG); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // TODO Auto-generated method stub View root = inflater.inflate(R.layout.fragment_switch1, container, false); root.findViewById(R.id.btnSwitchToPlaceholderFragment).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if(null != mSwitchCallBack){ Log.d(TAG, "switch to PlaceholderFragment"); mSwitchCallBack.openFragmentByTag(PlaceholderFragment.TAG); } } }); Log.d(TAG, "onCreateView"); return root; } @Override public void onDetach() { // TODO Auto-generated method stub Log.d(TAG, "onDetach"); mSwitchCallBack.removeFragment(TAG); super.onDetach(); } }
在Activity中實現該CallBack,用來管理 Fragment List, 將需要的Fragment顯示,不需要的隱藏:
public class FragmentSwitchActivity extends FragmentActivity implements FragmentSwitchCallBack { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_fragment_switch); if (savedInstanceState == null) { getSupportFragmentManager() .beginTransaction() .add(R.id.container, new PlaceholderFragment(), PlaceholderFragment.TAG).commit(); } } private List<String> fragmnetList; @Override public void openFragmentByTag(String tag) { FragmentManager fm = getSupportFragmentManager(); FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); if(null == ft) return; if (null == fragmnetList) fragmnetList = new ArrayList<String>(); if (fragmnetList.contains(tag)) { for (String s : fragmnetList) { if (s == tag) ft.show(fm.findFragmentByTag(s)); else ft.hide(fm.findFragmentByTag(s)); } } else { if (PlaceholderFragment.TAG == tag) { ft.add(R.id.container, new PlaceholderFragment(), tag); } else if (FragmentSwitch1.TAG == tag) { ft.add(R.id.container, new FragmentSwitch1(), tag); } for (String s : fragmnetList) { ft.hide(fm.findFragmentByTag(s)); } } ft.commit(); } @Override public void addFragment(String tag) { if (null == fragmnetList) fragmnetList = new ArrayList<String>(); fragmnetList.add(tag); } @Override public void removeFragment(String tag) { if (null != fragmnetList) { fragmnetList.remove(tag); } } }
兩個Fragment切換時的生命周期如下
打開FragmentSwitchActivity,順便添加第FragmentPlaceholder 08-14 16:30:43.601 D 5967 5967 FragmentPlaceholder: onAttach 08-14 16:30:43.601 D 5967 5967 FragmentPlaceholder: onCreate 08-14 16:30:43.701 D 5967 5967 FragmentPlaceholder: onCreateView 打開FragmentSwitch1 08-14 16:30:49.101 D 5967 5967 FragmentSwitch1: onAttach 08-14 16:30:49.101 D 5967 5967 FragmentSwitch1: onCreate 08-14 16:30:49.161 D 5967 5967 FragmentSwitch1: onCreateView 以后再切換,生命周期不再變化
5.2 二級Fragment的狀態保存
此問題以后再分析~
6. 單個Fragment的橫豎屏切換及狀態保存
6.1 橫豎屏切換時的Fragment生命周期變化如下, 會先銷毀,再重新創建
08-13 20:13:59.127 D 7089 7089 FragmentA: onPause 08-13 20:13:59.127 D 7089 7089 FragmentA: onStop 08-13 20:13:59.127 D 7089 7089 FragmentA: onDestroyView 08-13 20:13:59.127 D 7089 7089 FragmentA: onDestroy 08-13 20:13:59.127 D 7089 7089 FragmentA: onDetach
08-13 20:13:59.177 D 7089 7089 FragmentA: onAttach 08-13 20:13:59.177 D 7089 7089 FragmentA: onCreate 08-13 20:13:59.197 D 7089 7089 FragmentA: onCreateView 08-13 20:13:59.197 D 7089 7089 FragmentA: onActivityCreated 08-13 20:13:59.197 D 7089 7089 FragmentA: onStart 08-13 20:13:59.207 D 7089 7089 FragmentA: onResume
6.2 默認情況下橫豎屏切換后整個FragmentActivity會被銷毀並重建,所有Fragment中的成員變量也會丟失,但所有的Fragment狀態數據如上所述會被保留並還原,這個時候所有的視圖都會重新創建。
未采取任何解決方案時的轉屏log
08-14 10:17:07.389 D 6620 6620 ScreenRotationActivity: onCreate 08-14 10:17:07.389 D 6620 6620 ScreenRotationActivity-Fragment: onCreate 08-14 10:17:07.389 D 6620 6620 ScreenRotationActivity-Fragment: onCreateView
開始轉屏 08-14 10:17:13.259 D 6620 6620 ScreenRotationActivity-Fragment: onDestroyView 08-14 10:17:13.259 D 6620 6620 ScreenRotationActivity-Fragment: onDetach
08-14 10:17:13.319 D 6620 6620 ScreenRotationActivity-Fragment: onCreate 08-14 10:17:13.339 D 6620 6620 ScreenRotationActivity: onCreate 08-14 10:17:13.349 D 6620 6620 ScreenRotationActivity-Fragment: onCreateView
轉屏完成
6.2.1 解決方法一:在相應的Activity配置中加上android:configChanges="orientation|screenSize"設置,這樣切換時就不會銷毀FragmentActivity,所有的Fragment的狀態及視圖也就會保持。
<activity android:name=".v6_1.ScreenRotationActivity" android:configChanges="orientation|screenSize" android:label="@string/title_activity_screen_rotation" > </activity>
@Override public void onConfigurationChanged(Configuration newConfig) { // TODO Auto-generated method stub super.onConfigurationChanged(newConfig); Log.d(TAG, "onConfigurationChanged"); }
使用解決方案一以后的生命周期變化如下,可以解決問題:
08-14 10:21:22.429 D 7697 7697 ScreenRotationActivity: onCreate 08-14 10:21:22.429 D 7697 7697 ScreenRotationActivity-Fragment: onCreate 08-14 10:21:22.429 D 7697 7697 ScreenRotationActivity-Fragment: onCreateView
開始轉屏 08-14 10:21:26.249 D 7697 7697 ScreenRotationActivity: onConfigurationChanged
轉屏完成
6.2.2 解決方法二:在使用FragmentTransaction.add()方法添加fragment時設置第三個tag參數,隨后在還原時可通過FragmentManager.findFragmentByTag()方法找回還原的fragment.
Fragment f = getFragmentManager().findFragmentByTag(FragmentA); if(null == f) { Log.d(TAG, "onCreate: new FragmentA"); f = new PlaceholderFragment(); } if (savedInstanceState == null) { getFragmentManager().beginTransaction() .add(R.id.container, f, FragmentA) .commit(); }
經我實驗,Fragment雖然找回來了,但是還是回執行onCreateView方法,UI組件狀態已丟失,哪位大神有好的方法,請@我, 簡略版生命周期如下
08-14 11:01:43.639 D 10628 10628 ScreenRotationActivity: onCreate 08-14 11:01:43.649 D 10628 10628 ScreenRotationActivity: onCreate: new FragmentA 08-14 11:01:43.659 D 10628 10628 ScreenRotationActivity-Fragment: onCreate 08-14 11:01:43.659 D 10628 10628 ScreenRotationActivity-Fragment: onCreateView 開始轉屏 08-14 11:01:48.129 D 10628 10628 ScreenRotationActivity-Fragment: onDestroyView 08-14 11:01:48.129 D 10628 10628 ScreenRotationActivity-Fragment: onDetach 08-14 11:01:48.199 D 10628 10628 ScreenRotationActivity-Fragment: onCreate 08-14 11:01:48.199 D 10628 10628 ScreenRotationActivity: onCreate 08-14 11:01:48.229 D 10628 10628 ScreenRotationActivity-Fragment: onCreateView 轉屏完成
6.2.3 解決方案三: Fragment生命周期 onPause之后 onDestroyView之前,會執行 onSaveInstanceState 方法。
生命周期如下:
08-14 11:39:50.149 D 12536 12536 ScreenRotationActivity-Fragment: onPause 08-14 11:39:50.149 D 12536 12536 ScreenRotationActivity-Fragment: onSaveInstanceState 08-14 11:39:50.149 D 12536 12536 ScreenRotationActivity-Fragment: onDestroyView 08-14 11:39:50.159 D 12536 12536 ScreenRotationActivity-Fragment: onDestroy 08-14 11:39:50.159 D 12536 12536 ScreenRotationActivity-Fragment: onDetach 08-14 11:39:50.209 D 12536 12536 ScreenRotationActivity-Fragment: onCreate 08-14 11:39:50.209 D 12536 12536 ScreenRotationActivity: onCreate 08-14 11:39:50.229 D 12536 12536 ScreenRotationActivity-Fragment: onCreateView
我們可以在onSaveInstanceState方法中,保存關鍵數據
@Override public void onSaveInstanceState(Bundle outState) { // TODO Auto-generated method stub Log.d(TAG, "onSaveInstanceState"); outState.putString("carloz", "Carlo Zhang"); super.onSaveInstanceState(outState); } @Override public void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); Log.d(TAG, "onCreate"); if(null != savedInstanceState) { Log.d(TAG, "onCreate: " + savedInstanceState.getString("carloz")); } }
結果如下:
08-14 11:44:51.949 D 13933 13933 ScreenRotationActivity: onCreate 08-14 11:44:51.959 D 13933 13933 ScreenRotationActivity: onCreate: new FragmentA 08-14 11:44:51.979 D 13933 13933 ScreenRotationActivity-Fragment: onCreate 08-14 11:44:51.979 D 13933 13933 ScreenRotationActivity-Fragment: onCreateView 開始轉屏 08-14 11:44:55.249 D 13933 13933 ScreenRotationActivity-Fragment: onPause 08-14 11:44:55.259 D 13933 13933 ScreenRotationActivity-Fragment: onSaveInstanceState 08-14 11:44:55.259 D 13933 13933 ScreenRotationActivity-Fragment: onDestroyView 08-14 11:44:55.259 D 13933 13933 ScreenRotationActivity-Fragment: onDestroy 08-14 11:44:55.259 D 13933 13933 ScreenRotationActivity-Fragment: onDetach 08-14 11:44:55.329 D 13933 13933 ScreenRotationActivity-Fragment: onCreate 08-14 11:44:55.329 D 13933 13933 ScreenRotationActivity-Fragment: onCreate: Carlo Zhang 08-14 11:44:55.329 D 13933 13933 ScreenRotationActivity: onCreate 08-14 11:44:55.349 D 13933 13933 ScreenRotationActivity-Fragment: onCreateView 轉屏完成
其他如 保存在文件或者數據庫中的方法就不再列舉了
7. Fragment 與 Activity 通信
7.1 Activity 向 Fragment 傳遞數據
在Activity中創建Bundle數據包,並調用Fragment的setArguments(Bundle bundle)方法,即可將Bundle數據包 傳給Fragment
7.2 Fragment 向 Activity 傳遞數據
定義一個CallBack接口,讓Activity實現改接口,在Fragment中即可調用該Callback的接口,代碼見 5.1
-------------------------------------------
工程代碼:FragmentDemo.zip