深度解读:实现向下的导航(Descendant Navigation)从 Master/Detail 到外部 Activity 任务栈控制)
文档教程移动开发【免费下载链接】android-training-course-in-chineseAndroid官方培训课程中文版项目地址https://gitcode.com/gh_mirrors/an/android-training-course-in-chinese点击查看免费下载本篇文章基于开源仓库 android-training-course-in-chinese 中 ux/implement-nav/descendant.md 课程内容展开系统讲解如何实现“向下导航”Descendant Navigation即从应用信息层次的当前层级导航至更深层级。文章将覆盖两种核心实现机制——IntentstartActivity()与FragmentTransaction并结合仓库中 Master/Detail 自适应 UI 流、Fragment 动态 UI 等关联课程源码级示例深入剖析手机/平板双形态实现、Fragment 返回栈以及导航至外部 Activity 时的任务栈保护技巧。读完本文你将能够独立设计并实现一个跨手机与平板的 Master/Detail 导航流程并正确处理启动外部应用时的回退路径。一、什么是向下导航Descendant Navigation在 Android 应用的导航设计中应用的界面按照信息层次information hierarchy组织。用户从顶层向下探索更深层级的界面这一行为被称为Descendant Navigation向下导航它的设计概念在 ux/design-nav/descendant-lateral.md提供向下与横向导航中已有详细阐述。向下导航通常有两种典型实现方式使用Intent配合startActivity()启动一个新的 Activity从而在信息层次中下降一层使用FragmentTransaction对象向当前 Activity 中添加一个 Fragment在同一 Activity 内完成层级下沉。选择哪种方式取决于应用的目标设备形态与屏幕空间手机单窗格一次只显示一个屏幕master screen 或 detail screen一般使用Intent启动 detail Activity 实现向下导航平板多窗格尤其是横屏时最适合一次显示多个内容窗格——master 在左、detail 在右此时一般使用FragmentTransaction添加、删除或用新内容替换 detail 窗格pane。关于手机/平板不同屏幕空间的导航策略可进一步阅读 ux/design-nav/multi-sizes.md为多种大小的屏幕进行规划其中介绍了多窗格布局、伸缩、展开/折叠、显示/隐藏、堆叠等竖屏适配策略。二、实现向下导航的两大核心机制2.1 机制一Intent startActivity()当目标界面是独立于当前 Activity 的新屏幕典型于手机形态时使用显式或隐式Intent启动目标 ActivityIntent detailIntent new Intent(this, DetailActivity.class); detailIntent.putExtra(itemId, itemId); startActivity(detailIntent);关于Intent的完整构建方式显式与隐式、Uri 数据、MIME 类型、extra 数据传递、验证是否有应用接收、createChooser()强制弹出选择器等可参考 basics/intents/sending.mdIntent 的发送课程。2.2 机制二FragmentTransaction当目标界面与当前界面共处同一 Activity 的多窗格布局典型于平板形态时使用FragmentManager创建FragmentTransaction来添加、替换 detail 窗格getSupportFragmentManager().beginTransaction() .add(R.id.detail_container, detailFragment, detail) .addToBackStack(detail) .commit();FragmentTransaction的add()/replace()/remove()等 API 的完整说明见 basics/fragments/fragment-ui.md建立灵活动态的 UI。三、在手机与平板上实现 Master/Detail Flow在Master/Detail 导航流程中master screen主屏幕包含一个集合中 item 的列表detail screen详细屏幕显示集合中特定 item 的详细信息。从 master 到 detail 的导航正是向下导航的一种典型形式。这一模式的基础实现手机上两个 Activity、平板上一个 Activity 的 Master/Detail 设计在 ui/multiscreen/adapt-ui.md实现自适应 UI 流课程中有系统讲解下面结合该课程源码级示例展开。3.1 确定当前布局单窗格还是双窗格由于单窗格与双窗格两种布局的实现方式存在差别首先需要确定用户当前可见的是哪种布局。可以通过检查指定视图是否存在且可见来判断public class NewsReaderActivity extends FragmentActivity { boolean mIsDualPane; Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_layout); View articleView findViewById(R.id.article); mIsDualPane articleView ! null articleView.getVisibility() View.VISIBLE; } }注意使用代码查询 id 为article的 view 是否可见比直接硬编码查询指定布局更加灵活这是跨屏幕形态适配的推荐做法。3.2 根据当前布局响应导航某些操作会根据当前布局产生不同效果双窗格模式下内容直接显示在右边窗格单窗格模式下则启动一个独立的 Activity 展示Override public void onHeadlineSelected(int index) { mArtIndex index; if (mIsDualPane) { /* 双窗格在右侧窗格中展示文章 */ mArticleFragment.displayArticle(mCurrentCat.getArticle(index)); } else { /* 单窗格启动独立的 Activity */ Intent intent new Intent(this, ArticleActivity.class); intent.putExtra(catIndex, mCatIndex); intent.putExtra(artIndex, index); startActivity(intent); } }这种“布局决定导航行为”的模式是移动端 Master/Detail 实现的核心骨架双窗格走 Fragment 事务单窗格走 Intent 启动。3.3 在运行时向 Activity 添加 Fragment在平板双窗格布局中detail 窗格通常通过容器在运行时添加 Fragment。Activity 布局中需要提供一个保存 Fragment 布局的容器视图例如FrameLayout!-- res/layout/news_articles.xml单窗格布局的容器版本 -- FrameLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:idid/fragment_container android:layout_widthmatch_parent android:layout_heightmatch_parent /在 Activity 中通过 Support Library API 获取FragmentManager并添加 Fragmentimport android.os.Bundle; import android.support.v4.app.FragmentActivity; public class MainActivity extends FragmentActivity { Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.news_articles); // 确认 Activity 使用的布局版本包含 fragment_container FrameLayout if (findViewById(R.id.fragment_container) ! null) { // 如果要从先前的状态还原则无需执行任何操作而应返回 // 否则就会得到重叠的 Fragment if (savedInstanceState ! null) { return; } // 创建一个要放入 Activity 布局中的新 Fragment HeadlinesFragment firstFragment new HeadlinesFragment(); // 如果此 Activity 是通过 Intent 发出的特殊指令来启动的 // 请将该 Intent 的 extras 以参数形式传递给该 Fragment firstFragment.setArguments(getIntent().getExtras()); // 将该 Fragment 添加到 fragment_container FrameLayout 中 getSupportFragmentManager().beginTransaction() .add(R.id.fragment_container, firstFragment).commit(); } } }由于 Fragment 是在运行时添加到FrameLayout容器中的而非通过布局中的fragment元素定义Activity 可以随时移除和替换这个 Fragment——这正是平板双窗格中 detail 窗格动态切换的基础。3.4 用一个 Fragment 替换另一个 Fragment当用户点击 master 列表中的不同条目时detail 窗格需要被新内容替换。替换步骤与添加相似但调用replace()而非add()// 创建 Fragment 并为其添加一个参数用来指定应显示的文章 ArticleFragment newFragment new ArticleFragment(); Bundle args new Bundle(); args.putInt(ArticleFragment.ARG_POSITION, position); newFragment.setArguments(args); FragmentTransaction transaction getSupportFragmentManager().beginTransaction(); // 将 fragment_container View 中的内容替换为此 Fragment // 然后将该事务添加到返回栈以便用户可以向后导航 transaction.replace(R.id.fragment_container, newFragment); transaction.addToBackStack(null); // 执行事务 transaction.commit();注意当你执行替换或移除 Fragment 等事务时最好能通过addToBackStack()让用户向后导航和撤销所做更改。若事务未加入返回栈被移除/替换的 Fragment 会被销毁加入返回栈后Fragment 只会停止不会销毁用户按返回键时可恢复。3.5 为 Fragment 事务接入返回栈Back Stack在手机形态的 Master/Detail 流程中如果用交换 Fragment 的方式而非独立 Activity实现 detail 展示就必须保证点击 Back 按钮能从 detail screen 返回到 master screen。做法是在提交事务之前调用addToBackStack()// 使用 framework FragmentManager // 或 support package FragmentManager (getSupportFragmentManager). getSupportFragmentManager().beginTransaction() .add(detailFragment, detail) // 提交这一事务到返回栈中 .addToBackStack() .commit();当返回栈中存在FragmentTransaction且用户点击 Back 按钮时FragmentManager会从返回栈中弹出最近的事务并执行反向操作例如事务添加了一个 Fragment则删除该 Fragment。两点重要建议来自 ux/implement-nav/temporal.md 课程当事务用于横向导航例如切换 Tab或修改内容外观例如调整过滤器时不要将事务添加到返回栈如果应用更新了其他 UI 元素如 action bar来反映当前 Fragment 状态除了在提交事务时更新 UI还应监听返回栈变化getSupportFragmentManager().addOnBackStackChangedListener( new FragmentManager.OnBackStackChangedListener() { public void onBackStackChanged() { // 在这里更新你的 UI } });3.6 Fragment 复用与交互解耦在多屏幕设计中同一 Fragment 既可能在平板双窗格布局中作为右侧窗格也可能在手机布局中被独立 Activity 复用如ArticleFragment通过getSupportFragmentManager().beginTransaction().add(android.R.id.content, frag).commit()直接添加到内容容器。为了保证复用设计 Fragment 时不要为某个特定 Activity 设计耦合度过高的 Fragment。通常做法是在 Fragment 中定义抽象接口由宿主 Activity 实现该接口Fragment 通过接口回调与宿主交互详见 basics/fragments/communicating.md。例如public class HeadlinesFragment extends ListFragment { OnHeadlineSelectedListener mHeadlineSelectedListener null; /* 必须由宿主 Activity 实现 */ public interface OnHeadlineSelectedListener { public void onHeadlineSelected(int index); } public void setOnHeadlineSelectedListener(OnHeadlineSelectedListener listener) { mHeadlineSelectedListener listener; } Override public void onItemClick(AdapterView? parent, View view, int position, long id) { if (null ! mHeadlineSelectedListener) { mHeadlineSelectedListener.onHeadlineSelected(position); } } }宿主 Activity 实现该接口后再根据当前布局单窗格/双窗格决定是更新右侧窗格还是启动新 Activity从而将“导航决策”统一收敛在 Activity 层。3.7 处理屏幕配置变化如果使用独立 Activity 承载 detail 内容屏幕方向变化时需保持 UI 布局的一致性。例如在 7 寸平板上竖屏时使用独立 Activity 显示文章横屏时使用双窗格。当用户在竖屏阅读文章时旋转为横屏应检测到配置变化并结束当前 detail Activity返回主 Activity让内容显示在双窗格布局中public class ArticleActivity extends FragmentActivity { int mCatIndex, mArtIndex; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mCatIndex getIntent().getExtras().getInt(catIndex, 0); mArtIndex getIntent().getExtras().getInt(artIndex, 0); // 如果应该是双窗格模式结束当前 Activity 返回主 Activity if (getResources().getBoolean(R.bool.has_two_panes)) { finish(); return; } ... } }四、导航至外部 Activities保护任务栈有很多场景需要从别的应用“下降”进入你的应用信息层次中的 Activity。例如正在浏览手机通讯录中联系信息的 detail screen子屏幕详细显示由社交网络联系人提供的最近文章而这个子屏幕可能属于另一个社交网络应用。4.1 为什么需要保护任务栈当启动另一个应用的 Activity 来让用户说话、发邮件或选择照片附件时如果用户之后从启动器设备 Home 屏幕重启你的应用你通常不希望用户返回到别的应用的那个 Activity。否则点击你的应用图标又回到发邮件的屏幕会让用户感到非常困惑。也就是说这些外部 Activity只是用户在当前任务中的一个临时中转站当整个任务被重置例如从启动器重新进入应用时这些外部页面应当被清除。4.2 FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET 标记为防止这种情况只需在用于启动外部 Activity 的 Intent 上添加FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET标记Intent externalActivityIntent new Intent(Intent.ACTION_PICK); externalActivityIntent.setType(image/*); externalActivityIntent.addFlags( Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); startActivity(externalActivityIntent);其行为语义是当该任务被重置task reset典型场景即用户从启动器重新进入应用时从这个标记所在的 Activity 开始及其上方的所有 Activity 都会被清除从而保证用户从启动器再次进入应用时看到的是应用自身的界面层次而不是残留的外部页面。课程文档写作于较早 API 时代原文为 Android 官方 training 课程。需要注意该标记在 Android 5.0API 21起已被标记为弃用后续版本更推荐结合FLAG_ACTIVITY_NEW_DOCUMENT与文档式任务Document-centric task语义来管理外部页面在现代工程中请结合应用的targetSdkVersion与实际系统行为评估取舍。4.3 隐式 Intent 的构建与安全性检查上面的示例属于隐式 Intent不声明具体组件类名而是声明 action如ACTION_PICK选择照片并配合setType(image/*)指定 MIME 类型让系统匹配能够处理该请求的应用。构建隐式 Intent 时有两项最佳实践详见 basics/intents/sending.md尽量将 Intent 定义得确切例如用ACTION_VIEW显示图片时还应指定 MIME 类型为image/*避免其他能查看其他数据类型的应用如地图应用被错误唤起启动前验证是否有应用接收如果触发了没有任何应用能接收的 Intent应用会崩溃。可用PackageManager.queryIntentActivities()预检PackageManager packageManager getPackageManager(); ListResolveInfo activities packageManager.queryIntentActivities(intent, 0); boolean isIntentSafe activities.size() 0; if (isIntentSafe) { startActivity(intent); }当系统确定有多个 Activity 可以处理该 Intent 时会弹出选择对话框让用户选择如果希望每次都强制弹出选择界面例如分享功能应使用Intent.createChooser()包装后再启动。4.4 与向上、向后导航的配合向下的导航并非孤立存在它与任务栈管理密切相关向上导航Up当用户从外部 Activity 启动你的深层界面时向上导航应创建一个属于你的 app 的新任务可用NavUtils.shouldUpRecreateTask()判断配合TaskStackBuilder重建后退栈完整方案见 ux/implement-nav/ancestral.md向后导航Back当用户从通知、app widget 或深度链接直接进入深层 Activity 时需要通过TaskStackBuilder合并一个新的返回栈让 Back 键沿父层级向上导航而非退出应用完整方案见 ux/implement-nav/temporal.md。将三者向下、向上、向后放在一起理解才能构建完整自洽的应用导航体系。五、课程脉络与延伸阅读“实现向下的导航”是 ux/implement-nav/index.md实现高效的导航课程体系中的一节整个体系围绕 ux/design-nav/index.md设计高效的导航中阐述的设计模式展开建议按以下顺序学习课程核心内容仓库文档使用 Tabs 创建 Swipe View横向导航与分页视图实现ux/implement-nav/lateral.md创建抽屉导航Navigation Drawer隐藏式边栏导航ux/implement-nav/nav-drawer.md提供向上导航Action Bar App 图标 NavUtils向上导航ux/implement-nav/ancestral.md提供适当的向后导航深度链接返回栈、Fragment/WebView 返回处理ux/implement-nav/temporal.md实现向下导航本课Master/Detail 流 外部 Activity 任务栈保护ux/implement-nav/descendant.md与之配套的底层实现素材还包括ui/multiscreen/adapt-ui.mdMaster/Detail 自适应 UI 流的完整代码示例布局判断、响应导航、Fragment 复用、旋转处理basics/fragments/fragment-ui.md 与 basics/fragments/communicating.mdFragment 运行时事务与接口回调通信basics/fragments/creating.mdFragment 的创建与生命周期basics/intents/sending.md隐式 Intent、MIME 类型与安全性验证。小结向下导航Descendant Navigation是 Android 应用信息层次探索的核心动作。本文从 ux/implement-nav/descendant.md 出发完整覆盖了其两大实现机制IntentstartActivity()与FragmentTransaction、手机/平板双形态的 Master/Detail Flow 完整实现含布局判断、动态 Fragment 切换、返回栈接入、接口解耦与旋转适配以及导航至外部 Activity 时通过FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET保护任务栈的关键技巧并结合仓库中关联课程给出了可复制、可运行的源码级示例。掌握了这些内容你就能在多尺寸设备上构建出层次清晰、回退路径正确的应用导航体系。赞分享文档教程移动开发【免费下载链接】android-training-course-in-chineseAndroid官方培训课程中文版项目地址https://gitcode.com/gh_mirrors/an/android-training-course-in-chinese点击查看免费下载相关推荐Android官方培训课程中文版教程Android官方培训课程中文版教程 1. 项目介绍 Android官方培训课程中文版Android Training Course in Chinese是文档教程移动开发Databasus调度备份技巧灵活定时与智能压缩配置指南Databasus调度备份技巧灵活定时与智能压缩配置指南 在数据库管理中定期备份是保障数据安全的关键环节。Databasus作为一款强大的PostgreSQ数据库灾备Android官方培训课程中文版Activity 暂停与恢复onPause / onResume实战指南Android官方培训课程中文版Activity 暂停与恢复onPause / onResume实战指南 本文基于 android training co文档教程移动开发上一篇Sway 语言结构化数据详解Struct、Tuple 与 Enum 的类型系统、内存布局与实战用法下一篇eslint linebreak-style 规则深度解析统一 CRLF/LF 换行风格的最佳实践创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考