设计原理
Android 在 Android 3.0(API 级别 11)中引入了片段,主要是为了给大屏幕(如平板电脑)上更加动态和灵活的 UI 设计提供支持。由于平板电脑的屏幕比手机屏幕大得多,因此可用于组合和交换 UI 组件的空间更大。利用片段实现此类设计时,您无需管理对视图层次结构的复杂更改。 通过将 Activity 布局分成片段,您可以在运行时修改 Activity 的外观,并在由 Activity 管理的返回栈中保留这些更改。
例如,新闻应用可以使用一个片段在左侧显示文章列表,使用另一个片段在右侧显示文章 — 两个片段并排显示在一个 Activity 中,每个片段都具有自己的一套生命周期回调方法,并各自处理自己的用户输入事件。 因此,用户不需要使用一个 Activity 来选择文章,然后使用另一个 Activity 来阅读文章,而是可以在同一个 Activity 内选择文章并进行阅读,(摘抄自官方文档)
Fragment特点
- Fragment表示Activity中的行为或用户界面部分
- 可以将多个Fragment组合在一个Activity中
- 可以在多个Activity中复用同一个Fragment
- Fragment有自己的生命周期能够接受自己的输入事件
- Fragment必须镶嵌在Activity中,其生命周期受宿主Activity生命周期影响
- 我们在运行时可以动态添加和删除Fragment
Fragment 优点
- 模块化:我们不必把所有的代码放入Activity中,而把代码写入Fragment中
- 可重用性:多个Activity可以重用一个Fragment
- 可适配性:可以根据硬件屏幕尺寸,方向,能够方便的实现不同的布局
Fragment的核心类
- Fragment :Fragment的基类,任何创建Fragment的类都需要集成该类
- FragmentManager:管理和维护Fragment。他是抽象类,具体的实现类是FragmentManagerImpl。
- FragmentTransaction:对Fragment的添加、删除等操作都需要通过事务方式进行。他是抽象类,具体的实现类是BackStackRecord。
- Nested Fragment(Fragment内部嵌套Fragment的能力)是Android 4.2提出的,support-fragment库可以兼容到1.6。通过getChildFragmentManager()能够获得子Fragment的FragmentManager,在子Fragment中可以通过getParentFragment()获得父Fragment。
基本使用
创建Fragment
想要创建Fragment,你必须要继承Fragment,通常至少应该实现一下的生命周期方法
-
onCreate
系统会在创建Fragment的时候,调用此方法,可以在此方法做暂停或者停止后,恢复数据 -
onCreateView()
系统会在Fragment首次绘制用户界面的时候调用,此方法返回的view就是就是Fragment的根布局,如果Fragment没有提供UI就可以返回null -
onPause()
系统将此方法作为离开Fragment的第一个信号
public class MyFragment extends Fragment {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void onPause() {
super.onPause();
}
}
添加布局
onCreateView方法返回的View就是Fragment的根布局
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment,container,false);
}
- 先在xml中定义布局
- 用LayoutInflater 去加载布局
inflate() 方法带有三个参数:
- 你想要加载的布局id
- 将作为扩展布局父项的 ViewGroup。传递 container 对系统向扩展布局的根视图(由其所属的父视图指定)应用布局参数具有重要意义;
- 指示是否应该在扩展期间将扩展布局附加至 ViewGroup(第二个参数)的布尔值。(在本例中,其值为 false,因为系统已经将扩展布局插入 container — 传递 true 值会在最终布局中创建一个多余的视图组。)
向Activity中添加布局
- 静态添加:在xml中通过添加,缺点就是一旦添加就不能运行时删除
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<fragment
android:id="@+id/fragment"
android:name="com.baidu.bpit.aibaidu.fragment.MyFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
注:每个片段都需要一个唯一的标识符,重启 Activity 时,系统可以使用该标识符来恢复片段(您也可以使用该标识符来捕获片段以执行某些事务,如将其移除)。 可以通过三种方式为片段提供 ID:
1 为 android:id 属性提供唯一 ID。
2 为 android:tag 属性提供唯一字符串。
3 如果您未给以上两个属性提供值,系统会使用容器视图的 ID。
- 动态添加:通过编程将Fragment添加到某个现有的ViewGroup,运行时添加,这种比较灵活,建议使用这种方式
1 首先你需要一个容器
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<FrameLayout
android:id="@+id/fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
2 想要在Activity中添加Fragment需要用到FragmentTransaction 的Api,首先获取一个FragmentTransaction 的实例
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
3 用add方法添加一个Fragment
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFragment();
}
private void initFragment() {
Fragment myFragment = new MyFragment();
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.fragment, myFragment);
fragmentTransaction.commit();
}
}
如何添加没有ui的Fragment
要想添加没有 UI 的片段,请使用 add(Fragment, String) 从 Activity 添加片段(为片段提供一个唯一的字符串“标记”,而不是视图 ID)。 这会添加片段,但由于它并不与 Activity 布局中的视图关联,因此不会收到对 onCreateView() 的调用。因此,您不需要实现该方法。
并非只能为非 UI 片段提供字符串标记 — 您也可以为具有 UI 的片段提供字符串标记 — 但如果片段没有 UI,则字符串标记将是标识它的唯一方式。如果您想稍后从 Activity 中获取片段,则需要使用 findFragmentByTag()。
- 因为我们使用了support库的Fragment,因此需要使用getSupportFragmentManager()获取FragmentManager。
- add()是对Fragment众多操作中的一种,还有remove(), replace()等,第一个参数是根容器的id(FrameLayout的id,即”@id/container”),第二个参数是Fragment对象,第三个参数是fragment的tag名,指定tag的好处是后续我们可以通过Fragment1 frag = getSupportFragmentManager().findFragmentByTag(“f1”)从FragmentManager中查找Fragment对象。
- 在一次事务中,可以做多个操作,比如同时做add().remove().replace()。
- 调用 commit() 不会立即执行事务,而是在 Activity 的 UI 线程(“主”线程)可以执行该操作时再安排其在线程上运行。不过,如有必要,您也可以从 UI 线程调用 executePendingTransactions() 以立即执行 commit() 提交的事务。通常不必这样做,除非其他线程中的作业依赖该事务
注意:您只能在 Activity 保存其状态(用户离开 Activity)之前使用 commit() 提交事务。如果您试图在该时间点后提交,则会引发异常。 这是因为如需恢复 Activity,则提交后的状态可能会丢失。 对于丢失提交无关紧要的情况,请使用 commitAllowingStateLoss()。
管理Fragment
要想管理您的 Activity 中的片段,您需要使用 FragmentManager。要想获取它,请从您的 Activity 调用 getFragmentManager()。
您可以使用 FragmentManager 执行的操作包括:
- 通过 findFragmentById()(对于在 Activity 布局中提供 UI 的片段)或 findFragmentByTag()(对于提供或不提供 UI 的片段)获取 Activity 中存在的片段。
- 通过 popBackStack()(模拟用户发出的返回命令)将片段从返回栈中弹出。
- 通过 addOnBackStackChangedListener() 注册一个侦听返回栈变化的侦听器。
回退栈
可以把每个Fragment保存到由Activity管理的返回栈中,从而可以实现用户可以返回之前打开的Fragment,类似于回退Activity
实现这个功能需要用到addToBackStack方法,例如:
private void initFragment() {
Fragment myFragment = new MyFragment();
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.fragment, myFragment)
.addToBackStack(null)
.commit();
}
在Commit之前调用
与Activity进行通信
- Fragment可以通过getActivity() 访问 Activity 实例
- 同样地,您的 Activity 也可以使用 findFragmentById() 或 findFragmentByTag(),通过从 FragmentManager 获取对 Fragment 的引用来调用片段中的方法。
Fragment的生命周期
- onAttach():Fragment和Activity相关联时调用。可以通过该方法获取Activity引用,还可以通过getArguments()获取参数。
- onCreate():Fragment被创建时调用。
- onCreateView():创建Fragment的布局。
- onActivityCreated():当Activity完成onCreate()时调用。
- onStart():当Fragment可见时调用。
- onResume():当Fragment可见且可交互时调用。
- onPause():当Fragment不可交互但可见时调用。
- onStop():当Fragment不可见时调用。
- onDestroyView():当Fragment的UI从视图结构中移除时调用。
- onDestroy():销毁Fragment时调用。
- onDetach():当Fragment和Activity解除关联时调用。
同样与 Activity 一样,假使 Activity 的进程被终止,而您需要在重建 Activity 时恢复片段状态,您也可以使用 Bundle 保留片段的状态。您可以在片段的 onSaveInstanceState() 回调期间保存状态,并可在 onCreate()、onCreateView() 或 onActivityCreated() 期间恢复状态。
注意:如需 Fragment 内的某个 Context 对象,可以调用 getActivity()。但要注意,请仅在片段附加到 Activity 时调用 getActivity()。如果片段尚未附加,或在其生命周期结束期间分离,则 getActivity() 将返回 null。
不加回退栈,替换Fragment 会调用onDestroy()和onDetach()。加了回退栈不会调用者俩方法
FragmentTransaction有一些基本方法,下面给出调用这些方法时,Fragment生命周期的变化:
add(): onAttach()->…->onResume()。
remove(): onPause()->…->onDetach()。
replace(): 相当于旧Fragment调用remove(),新Fragment调用add()。
show(): 不调用任何生命周期方法,调用该方法的前提是要显示的Fragment已经被添加到容器,只是纯粹把Fragment UI的setVisibility为true。
hide(): 不调用任何生命周期方法,调用该方法的前提是要显示的Fragment已经被添加到容器,只是纯粹把Fragment UI的setVisibility为false。
detach(): onPause()->onStop()->onDestroyView()。UI从布局中移除,但是仍然被FragmentManager管理。
attach(): onCreateView()->onStart()->onResume()。
参考:https://xiazdong.github.io/2017/06/15/android-fragment/
https://developer.android.com/guide/components/fragments?hl=zh-CN