Android Fragment基础及使用
同一个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