Fragment
一种可以嵌入在 Activity 当中的 UI 片段,能让程序更加合理和充分地利用大屏幕的空间,因而在平板上应用得非常广泛
- 与 Activity 类似, 能包含布局, 拥有自己的声明周期
- 但 Activity 中可以包含多个 Fragment 实现分页效果

Quick Start
- 为 Fragment 设置好布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/left_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Button"
/>
</LinearLayout>
- 新建 Fragment 类, 使其继承
androidx.fragment.app.Fragment实现对 Fragment 布局的加载
class LeftFragment: Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// 通过 LayoutInflater 的 inflate() 方法将刚才定义的 left_fragment 布局动态加载进来
return inflater.inflate(R.layout.left_fragment, container, false)
}
}
- 在主 Activity 中的布局中引入 Fragment
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/leftFrag"
android:name="com.example.a07fragment_1.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<fragment
android:id="@+id/rightFrag"
android:name="com.example.a07fragment_1.RightFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
动态添加 Fragment
- 在主 Activity 中为动态替换的 Fragment 留有布局
<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:id="@+id/rightLayout"
/>
- 通过点击或其他事件触发 fragment 的替换加载
// 将待添加 Fragment 的实例作为参数传入
private fun replaceFragment(fragment: Fragment) {
// 通过 getSupportFragmentManager() 获取 fragment 操作对象
val fragmentManager = supportFragmentManager
// 开启一个事务,通过调用 beginTransaction() 方法开启
val transaction = fragmentManager.beginTransaction()
// 使用 replace()方法向容器内替换 Fragment,需要传入容器的id和待添加的 Fragment 实例
transaction.replace(R.id.rightLayout, fragment)
// 提交事务
transaction.commit() // 并行处理
// transaction.commitNow() // 立即执行,避免其它引用冲突
}
带有参数的 Fragment
Fragment 显示的内容可能需要其父 Activity 告知, 因此初始化时需要配置相应的参数
- xml 中用 FrameLayout 为 Fragment 占位
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/toolbar_fragment"
/>
- Activity 中选择适当的时机动态加载该 Fragment
val toolbarFragment = ToolbarFragment()
// 标题栏左右两侧的图标以及中间的标题名
val bundle = Bundle().apply {
putString("toolbarName", toolbarNmae)
toolbarLeft?.let { putInt("toolbarLeft", it) }
toolbarRight?.let { putInt("toolbarRight", it) }
}
toolbarFragment.arguments = bundle
val fragmentManager = supportFragmentManager
val transaction = fragmentManager.beginTransaction()
transaction.replace(R.id.toolbar_fragment, toolbarFragment)
transaction.commit()
return toolbarFragment
- Fragment 中在 onCreateView 中接收参数进行处理
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val view: View = inflater.inflate(R.layout.toolbar_fragment, container, false)
// 省略 findViewByID ...
// Bundle.getArguments()
toolbarName.text = arguments?.getString("toolbarName")
// ....
return view
}
与主 Activity 的交互
类似于父组件管理多个子组件, 主要通过 supportFragmentManager 实现
- 获取 fragment View 实例:
supportFragmentManager.findFragmentById(R.id.leftFrag) as LeftFragment - 获取主 Activity 实例:
val mainActivity = getActivity() as MainActivity
生命周期
与 Activity 类似, 拥有四种状态: 运行状态, 暂停状态, 停止状态, 销毁状态
- 运行状态: 当一个 Fragment 所关联的 Activity 正处于运行状态时,该 Fragment 也处于运行状态
- 暂停状态: 同上
- 停止状态: 同上 或 通过 FragmentTransaction 的 remove() replace() 方法将 Fragment 从 Activity 中移除并且调用了 addToBackStack() 也会进入停止状态
- 销毁状态: 当 Activity 被销毁时,与它相关联的 Fragment 就会进入销毁状态 或者 同上但没有调用 addToBackStack() 将会进入销毁状态
特有的状态回调方法:
onAttach(): Fragment 与 Activity 建立关联onCreateView(): 为 Fragment 创建视图(加载布局) 时调用onActivityCreated(): 确保与 Fragment 相关联的 Activity 已经创建完毕时调用onDestroyView(): 当与 Fragment 关联的视图被移除时onDetach(): 当 Fragment 和 Activity 解除关联时调用

动态布局加载技巧
旨在能更灵活的根据分辨率, 屏幕大小等在运行时决定加载哪个布局
限定符 qualifier
准备好单页模式的布局 layout/activity_main.xml 与双页模式的布局 layout-large/activity_main.xml 这里面的 large 就是一个内置的限定符
实际过程中你可以按照双页模式进行布局的绑定初始化, 但是两种布局会根据设备的大小进行展示
因此如果想要程序有更多的适配, 我们就要为各类限定符作好布局, 常见的限定符有
大小类限定符
- small: 小屏幕
- normal: 中等屏幕
- large: 大屏幕
- xlarge: 超大平步
- smallest-width qualifier: 最小宽度限定符, 允许对屏幕的宽度指定一个最小值(单位 dp) 屏幕宽度大于这个值的设备就加载这个布局, 小于这个值的就加载另一个布局, 具体后缀可缩写为
-sw600dp
分辨率类限定符:
- ldpi: 120 dpi 以下
- mdpi: 120 - 160 dpi
- hdpi: 160 - 240 dpi
- xhdpi: 240 - 320 dpi
- xxhdpi: 320 - 480 dpi
方向:
- land: 横屏
- port: 竖屏
Project
编写兼容手机和平板的应用程序
- MainActivity 中根据 layout 进行动态布局加载, 选择性加载单页布局
newsTitleFrag和双页布局newsContentFrag - 针对单页模式设置其 Fragment
NewsTitleFragment: 在其中加载标题列表所对应的布局news_title_frag; 在生命周期onViewCreated中加载RecyclerView的数据NewsAdapter;NewsAdapter中通过ViewHolder实例为每个数据项添加点击处理事件holder.itemView.setOnClickListener, 点击处理事件中通过判断当前 Activity 中是否是双页布局来选择性定义点击处理方式isTwoPane = (activity?.findViewById<View>(R.id.newsContentLayout)) != null:
- 如果是双页布局, 则定位到目标 fragment 对象后刷新内容即可
- 如果是单页布局, 则需要启动一个新的 Activity 用于展示目标新闻的详细信息
holder.itemView.setOnClickListener {
val news = newsList[holder.adapterPosition]
if (isTwoPane) {
val fragment = activity?.supportFragmentManager?.findFragmentById(R.id.newsContentFrag) as NewsContentFragment
fragment.refresh(news.title, news.contennt)
} else {
NewsContentActivity.actionStart(parent.context, news.title, news.contennt)
}
}
- 针对双页模式设置其 Fragment
NewsContentFragment: 在其中加载新闻内容所对应的布局, 并定义布局内容刷新的接口函数refresh供 MainActivity 调用
fun refresh(title: String, content: String) {
contentLayout.visibility = View.VISIBLE
newsTitle.text = title
newsContent.text = content
}
- 针对单页模式的点击事件, 定义新的 Activity 展示新闻内容
NewsContentActivity通过companion object定义静态方法actionStart以向外部提供快速启动该 Activity 的接口; 启动后则根据传递来的数据渲染界面即可
fun actionStart(context: Context, title: String, content: String) {
val intent = Intent(context, NewsContentActivity::class.java).apply {
putExtra("news_title", title)
putExtra("news_content", content)
}
context.startActivity(intent)
}
val title = intent.getStringExtra("news_title")
val content = intent.getStringExtra("news_content")
if (title != null && content != null) {
val fragment = supportFragmentManager.findFragmentById(R.id.newsContentFrag) as NewsContentFragment
fragment.refresh(title, content)
}