- 文档
- 教程
- 移动开发
【免费下载链接】android-training-course-in-chinese
Android官方培训课程中文版
本篇技术指南围绕《Android 官方培训课程中文版》(android-training-course-in-chinese)中 "与其他 Fragment 交互" 一课展开,讲解在基于 Fragment 的动态 UI 架构下,如何通过宿主 Activity 与回调接口实现 Fragment 之间的消息传递。读完本文,你将掌握"Fragment 内定义接口、onAttach() 中绑定实现、Activity 实现接口接收事件、findFragmentById() 反向传参"这一完整链路,并能写出兼容平板两窗格(two-pane)与手机单窗格(one-pane)布局的新闻阅读器式应用。
Fragment 通信架构原则:自包含组件,交互只经宿主 Activity
为了最大化复用 Fragment UI 组件,每一个 Fragment 都应被构建成完全自包含、模块化的组件:自己定义布局、自己管理行为。当多个这样的可复用 Fragment 组合进同一个 Activity 时,Activity 便承担起"复合 UI"的组装职责。
本课给出的核心架构铁律是:
所有 Fragment 之间的交互都应通过与之关联的 Activity 来完成,两个 Fragment 之间不应直接交互。
例如,当用户在"新闻标题列表 Fragment"中点击某一条目时,列表 Fragment 不应直接去修改右侧"文章内容 Fragment"的界面,而是先把事件上抛给 Activity,由 Activity 决定下一步做什么(更新右侧 Fragment,或替换整个界面)。这种"自下而上回调、自上而下派发"的模式,让 Fragment 之间保持零耦合:任何一个 Fragment 都可以被换掉、复用到别的 Activity 中,而不会破坏整体逻辑。
第一步:在 Fragment 中定义回调接口
要让 Fragment 与容器 Activity 交互,标准做法是:在 Fragment 类内部定义一个接口,由宿主 Activity 实现它。Fragment 在自己的onAttach()生命周期方法中获取该接口的实现(即宿主 Activity 本身),之后随时可以调用接口方法向 Activity 发消息。
(译注:onAttach()是 Fragment 与 Activity 关联时自动触发的生命周期回调;只要 Fragment 中实现了onAttach()方法,系统就会在它被附加到 Activity 时自动调用。)
以"新闻标题列表"Fragment 为例,完整代码如下:
public class HeadlinesFragment extends ListFragment { OnHeadlineSelectedListener mCallback; // 容器 Activity 必须实现该接口 // (译注:"容器 Activity"意即"包含该 Fragment 的 Activity") public interface OnHeadlineSelectedListener { public void onArticleSelected(int position); } @Override public void onAttach(Activity activity) { super.onAttach(activity); // 确认容器 Activity 已实现该回调接口。否则,抛出异常 try { mCallback = (OnHeadlineSelectedListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement OnHeadlineSelectedListener"); } } ... }关键点拆解:
- 接口定义在 Fragment 内部(
OnHeadlineSelectedListener),使回调契约与触发方天然绑定,便于阅读和维护; onAttach()中做强制类型转换:将宿主 Activity 强转为接口类型并存入成员变量mCallback;try/catch捕获ClassCastException:如果容器 Activity 忘了实现该接口,会在 Fragment 附加时立即以清晰、可定位的异常信息暴露出来(例如 "com.example.MainActivity must implement OnHeadlineSelectedListener"),而不是等到用户点击时才出现莫名其妙的空指针或类型错误。
兼容性说明:本课程基于 Android Support Library v4 时代的 API 编写(参见 basics/fragments/creating.md 中import android.support.v4.app.Fragment的写法)。在更高 API 级别中,onAttach(Activity)已被onAttach(Context context)取代,判断逻辑通常改写为if (context instanceof OnHeadlineSelectedListener)后再赋值,思路完全相同。
定义好接口后,Fragment 就通过mCallback(OnHeadlineSelectedListener的实例)随时向 Activity 传递消息。例如,当用户点击列表条目时,触发下方回调:
@Override public void onListItemClick(ListView l, View v, int position, long id) { // 向宿主 Activity 传送事件 mCallback.onArticleSelected(position); }ListFragment在用户点击列表项时自动调用onListItemClick(),这里把position(被点击条目的索引)作为参数上抛给宿主 Activity。Fragment 自身完全不知道也不关心 Activity 收到后要做什么——解耦由此达成。
第二步:宿主 Activity 实现接口并接收回调
要接收回调事件,宿主 Activity 必须声明implements该 Fragment 中定义的接口,并提供接口方法的实现:
public static class MainActivity extends Activity implements HeadlinesFragment.OnHeadlineSelectedListener{ ... public void onArticleSelected(int position) { // 用户从 HeadlinesFragment 选择了一篇文章的标题 // 在这里做点什么,以显示该文章 } }至此,事件链已经完整闭合:
- 用户点击列表项 →
onListItemClick()被调用; - Fragment 调用
mCallback.onArticleSelected(position); mCallback实际指向宿主 Activity → Activity 的onArticleSelected()被触发;- Activity 在回调中编排下一步 UI 操作。
值得注意:由于onAttach()中做过类型校验,Activity 若漏写implements会在运行早期抛出明确的异常;而这里onArticleSelected(int position)的实现,就是"复合 UI"逻辑真正落地的位置。
第三步:Activity 向 Fragment 传递消息
回调把"用户选了哪篇文章"送进了 Activity。接下来 Activity 需要把这条消息反向推送给负责展示文章内容的 Fragment。手段是:通过findFragmentById()获取 Fragment 实例,再直接调用它的 public 方法。
例如,假设 Activity 中还包含另一个用于展示文章详情的 FragmentArticleFragment,Activity 就可以在回调中拿到被选位置的数据并转发给它。完整示例(含两窗格与单窗格两种分支处理):
public static class MainActivity extends Activity implements HeadlinesFragment.OnHeadlineSelectedListener{ ... public void onArticleSelected(int position) { // 用户从 HeadlinesFragment 选择了一篇文章的标题 // 在这里做点什么,以显示该文章 ArticleFragment articleFrag = (ArticleFragment) getSupportFragmentManager().findFragmentById(R.id.article_fragment); if (articleFrag != null) { // 若 articleFrag 有效,则表示我们正在处理两格布局(two-pane layout)…… // 调用 ArticleFragment 的方法,以更新其内容 articleFrag.updateArticleView(position); } else { // 否则,我们正在处理单格布局(one-pane layout)。此时需要 swap frags... // 创建 Fragment,向其传递包含被选文章的参数 ArticleFragment newFragment = new ArticleFragment(); Bundle args = new Bundle(); args.putInt(ArticleFragment.ARG_POSITION, position); newFragment.setArguments(args); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); // 无论 fragment_container 视图里是什么,用该 Fragment 替换它。并将 // 该事务添加至回栈,以便用户可以往回导航(译注:回栈,即 Back Stack。 // 在有多个 Activity 的 APP 中,将这些 Activity 按创建次序组织起来的 // 栈,称为回栈) transaction.replace(R.id.fragment_container, newFragment); transaction.addToBackStack(null); // 执行事务 transaction.commit(); } } }这段代码展示了两种典型分支,对应两种屏幕布局策略:
分支一:两窗格布局(two-pane)。findFragmentById(R.id.article_fragment)返回非空,说明平板等宽屏上ArticleFragment已经静态地躺在布局里。此时无需创建新实例,直接调用其 public 方法updateArticleView(position)就地刷新内容即可——两个 Fragment 依旧互不直接引用,消息由 Activity 中转。
分支二:单窗格布局(one-pane)。手机上R.id.article_fragment不存在,返回null。此时需要动态"换屏":新建ArticleFragment,把被选位置position放进Bundle,再通过setArguments(args)绑定给新 Fragment,最后用FragmentTransaction执行replace(R.id.fragment_container, newFragment)+addToBackStack(null)+commit()。这样用户按返回键时,事务会从回栈弹出,界面"撤销"回列表页。
两种传参方式的语义区别值得强调:
setArguments(Bundle):在 Fragment 创建前注入参数,参数会被系统在 Activity 重建、Fragment 重新附加时保留(与savedInstanceState协同工作),适合"初始数据";- 调用 public 方法(如
updateArticleView(position)):适合更新已存在 Fragment 的运行时状态。
配套布局:两窗格与单窗格布局是如何定义的
本课默认读者已完成前两课(basics/fragments/creating.md 与 basics/fragments/fragment-ui.md),其中给出了与上述代码一一对应的布局方案,这里一并梳理:
**宽屏(平板)**使用res/layout-large/news_articles.xml,通过 XML 一次性静态放置两个 Fragment,并用layout_weight分配宽度(来自 basics/fragments/creating.md):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent"> <fragment android:name="com.example.android.fragments.HeadlinesFragment" android:id="@+id/headlines_fragment" android:layout_weight="1" android:layout_width="0dp" android:layout_height="match_parent" /> <fragment android:name="com.example.android.fragments.ArticleFragment" android:id="@+id/article_fragment" android:layout_weight="2" android:layout_width="0dp" android:layout_height="match_parent" /> </LinearLayout>这里@+id/article_fragment正是上一节findFragmentById()要查找的 ID;layout_weight="2"使文章区占三分之二宽度。
**手机(小屏)**使用同名但位于无large限定符目录下的res/layout/news_articles.xml,只放一个空的FrameLayout作为 Fragment 容器(来自 basics/fragments/fragment-ui.md):
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/fragment_container" android:layout_width="match_parent" android:layout_height="match_parent" />两窗格与单窗格的差异,正是上节回调代码中findFragmentById()判空分支的存在原因。下图直观展示了同一 Activity 在不同屏幕尺寸下的两种配置:
图1:两个 Fragment 显示在不同尺寸屏幕上同一 Activity 的不同配置中。宽阔屏幕上两个 Fragment 并列显示;手机上一次只能显示一个,必须在用户导航时替换 Fragment(图片来自 basics/fragments/fragment-ui.md)。
另外,onCreate()中运行时添加初始 Fragment 时,务必先检查savedInstanceState != null就提前返回,避免 Activity 重建后叠加重叠的 Fragment(详见 basics/fragments/fragment-ui.md 的MainActivity示例)。
最佳实践与注意事项汇总
综合本课及配套课程,可沉淀出以下可直接落地到项目的实践要点:
- Fragment 永不直接互调。所有跨 Fragment 的消息一律经宿主 Activity 中转,否则组件无法独立复用,也难以测试。
- 接口校验前置到
onAttach()。用try/catch包裹强转,宿主未实现接口时立刻抛出ClassCastException,错误信息中带上 Activity 类名,排障成本最低。 - 先判空再更新。对静态存在的 Fragment 使用
findFragmentById()前,先判空以区分两窗格/单窗格布局,避免在单窗格设备上 NPE。 addToBackStack(null)的语义(来自 basics/fragments/fragment-ui.md):移除或替换 Fragment 并加入回栈时,被移除的 Fragment 是停止而非销毁,用户按返回键可还原;不加入回栈则会直接销毁。addToBackStack()的可选字符串参数仅供FragmentManager.BackStackEntry高级操作使用,常规场景传null即可。- 支持库 API 的选择:支持 Android 1.6(API level 4)及以上设备时,Activity 需继承
FragmentActivity并使用getSupportFragmentManager();最低版本 ≥ 11 可用原生Activity;若引入 v7 appcompat 库,则应继承AppCompatActivity(其本身是FragmentActivity的子类),参见 basics/fragments/creating.md。 - 静态
<fragment>标签的限制:通过 XML 静态添加的 Fragment 无法在运行时移除或替换,因此凡需要"换屏"的交互场景,都必须走FragmentTransaction动态添加/替换的路线。
本章在课程中的位置
本课是 basics/fragments/index.md("使用 Fragment 建立动态 UI")三课中的收官一课,前两课分别解决"创建 Fragment"(basics/fragments/creating.md)与"运行时动态增删替换"(basics/fragments/fragment-ui.md)。将三课连起来即得到一个完整的、基于 Fragment 的响应式多窗格新闻阅读器实现思路;全书的课程导航见 SUMMARY.md。官方配套的完整示例工程名为 FragmentBasics,建议边读本课边对照示例中的MainActivity、HeadlinesFragment与ArticleFragment三个类验证本文所述的回调链路。
- 文档
- 教程
- 移动开发
【免费下载链接】android-training-course-in-chinese
Android官方培训课程中文版
相关推荐
Android 应用间交互完全指南:使用 Intent 启动其他应用、接收返回结果与暴露 Activity(android-training-course-in-chinese)
Android 应用间交互完全指南:使用 Intent 启动其他应用、接收返回结果与暴露 Activity(android training course in
文档教程移动开发Android Activity 重新创建与实例状态保存恢复全解析(android-training-course-in-chinese)
Android Activity 重新创建与实例状态保存恢复全解析(android training course in chinese) 本指南基于 andr
文档教程移动开发Android Activity 生命周期全解析:基于 android-training-course-in-chinese 的回调机制、状态迁移与实例状态保存实战
Android Activity 生命周期全解析:基于 android training course in chinese 的回调机制、状态迁移与实例状态保存
文档教程移动开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考