关于Fragment,你可能不知道的一切
省略Fragment的生命周期
省略Fragment的静态加载
1.动态添加Fragment的方法
-
添加有UI的Fragment
ExampleFragment fragment = new ExampleFragment(); fragmentTransaction.add(R.id.fragment_container, fragment); fragmentTransaction.commit();
-
添加没有UI的Fragment
ExampleFragment fragment = new ExampleFragment(); fragmentTransaction.add(fragment,"ExampleFragment"); fragmentTransaction.commit();
由于它并不与 Activity 布局中的视图关联,因此不会收到对
onCreateView()
的调用。因此,不需要实现该方法。对应的可以通过 findFragmentByTag("ExampleFragment")获取Fragment实例。
2.两个Fragment与Activity之间的通信(官方建议)
前提:
- 在Activity中有两个Fragment,分别时LeftFragment和RightFragment
- RightFragment会随着LeftFragment的变化而改变(eg:点击左侧Fragment后右侧Fragment加载详情)
实现:
-
1.在LeftFragment中定义一个回调接口,Activity必须实现这个接口
public static class LeftFragment extends ListFragment { ... // Container Activity must implement this interface public interface OnArticleSelectedListener { public void onArticleSelected(Uri articleUri); } ... }
-
2.宿主Activity必须实现该接口
public class MainActivity extends Activity implements OnArticleSelectedListener{ //...省略若干代码 @Override public void onArticleSelected(Uri articleUri){ } }
-
3.在LeftFragment中获取Activity实现接口的实例
public static class FragmentA extends ListFragment { OnArticleSelectedListener mListener; ... @Override public void onAttach(Activity activity) { super.onAttach(activity); try { mListener = (OnArticleSelectedListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener"); } } ... }
3.当Activity重启时,使用Fragment保存数据
当Activity的配置发生变化导致重启时,为了提高用户体验,一般需要在Activity销毁的时候保存界面上的一些数据,再次创建的时候恢复数据。
通常的做法是在
onSaveInstanceState()
回调时保存有关应用状态数据,然后,可以在onCreate()
或onRestoreInstanceState()
期间恢复 Activity 状态。
通过系统回调
onSaveInstanceState()
方法将数据保存在Boundle
,但当数据量较大时,可能无法完全恢复 Activity 状态,因为它并非设计用于携带大型对象(例如位图Bitmap),而且其中的数据必须先序列化,再进行反序列化,这可能会消耗大量内存并使得配置变更速度缓慢。在这种情况下,如果 Activity 因配置变更而重启,则可通过保留
Fragment
来减轻重新初始化 Activity 的负担。
步骤如下:
- 1.扩展
Fragment
类并声明对有状态对象(即我们要保存的对象)的引用。 - 2.在创建片段后调用
setRetainInstance(boolean)
。 - 3.将片段添加到 Activity。
- 4.重启 Activity 后,使用
FragmentManager
检索片段。
例如:
定义一个RetainedFragment
用来保存数据
public class RetainedFragment extends Fragment {
// data object we want to retain
private MyDataObject data;
// this method is only called once for this fragment
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// retain this fragment
setRetainInstance(true);
}
public void setData(MyDataObject data) {
this.data = data;
}
public MyDataObject getData() {
return data;
}
}
注意:尽管可以存储任何对象,但是切勿传递与
Activity
绑定的对象,例如,Drawable
、Adapter
、View
或其他任何与Context
关联的对象。否则,它将泄漏原始 Activity 实例的所有视图和资源。 (泄漏资源意味着应用将继续持有这些资源,但是无法对其进行垃圾回收,因此可能会丢失大量内存。)
然后,使用 FragmentManager
将片段添加到 Activity。在运行时配置变更期间再次启动 Activity 时,就可以获得片段中的数据对象。
public class MyActivity extends Activity {
private RetainedFragment dataFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// find the retained fragment on activity restarts
FragmentManager fm = getFragmentManager();
dataFragment = (DataFragment) fm.findFragmentByTag(“data”);
// create the fragment and data the first time
if (dataFragment == null) {
// add the fragment
dataFragment = new DataFragment();
fm.beginTransaction().add(dataFragment, “data”).commit();
// load the data from the web
dataFragment.setData(loadMyData());
}
// the data is available in dataFragment.getData()
...
}
@Override
public void onDestroy() {
super.onDestroy();
// store the data in the fragment
dataFragment.setData(collectMyLoadedData());
}
}
4.向应用栏添加项目
Fragment可以通过实现 onCreateOptionsMenu()
向 Activity 的选项菜单(并因此向应用栏)贡献菜单项。不过,为了使此方法能够收到调用,必须在 onCreate()
期间调用 setHasOptionsMenu()
,以指示Fragment想要向选项菜单添加菜单项(否则,片段将不会收到对 onCreateOptionsMenu()
的调用)。
从Fragment添加到选项菜单的任何菜单项都将追加到现有菜单项之后。 选定菜单项时,Fragment还会收到对 onOptionsItemSelected()
的回调。
Fragment向应用栏添加项目应用十分广泛,例如下面的效果
我们自己也可以动手实现这样的效果,先看效果:
当切换Fragment的时候,应用栏不同的状态,so easy!
-
step1.在Fragment的onCreate回调时,调用Fragment的
setHasOptionsMenu(true)
方法;@Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); }
-
step2.覆盖
onCreateOptionsMenu()
方法根据不同位置加载不同的应用栏菜单;@Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); switch (mFragmentType) { case 0: break; case 1: inflater.inflate(R.menu.action, menu); break; case 2: default: inflater.inflate(R.menu.action0, menu); break; } }
-
step3.如果需要监听应用栏的点击,只需要覆盖
onOptionsItemSelected()
方法。@Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.navigation_home: Toast.makeText(getContext(), "home", Toast.LENGTH_SHORT).show(); break; case R.id.navigation_dashboard: Toast.makeText(getContext(), "dashboard", Toast.LENGTH_SHORT).show(); break; case R.id.navigation_notifications: Toast.makeText(getContext(), "notifications", Toast.LENGTH_SHORT).show(); break; } return super.onOptionsItemSelected(item); }
另外,还可以通过调用 registerForContextMenu()
,在片段布局中注册一个视图来提供上下文菜单。用户打开上下文菜单时,片段会收到对 onCreateContextMenu()
的调用。当用户选择某个菜单项时,片段会收到对 onContextItemSelected()
的调用。
注:尽管Fragment会收到与其添加的每个菜单项对应的菜单项选定回调,但当用户选择菜单项时,Activity 会首先收到相应的回调。 如果 Activity 对菜单项选定回调的实现不会处理选定的菜单项,则系统会将事件传递到片段的回调。 这适用于选项菜单和上下文菜单。