Fragment简介

Fragment自从Android 3.0引入开始,它所承担的角色就是显而易见的。它之于Activity就如html片段之于页面,好处无需赘述。

Fragment的生命周期和Activity一样,都不是由开发人员而是由系统维护的。自然而然的,每当它们被重建时,系统只会去调用它们的无参构造器,带参构造器会被无视。那如果要在它们创建时传入初始化数据咋办呢?这也是为什么类似OnCreateXXX方法里会有Bundle这个玩意儿的存在,就是用于开发人员存取相关的数据,如下所示:

/**
* Use the [ThumbnailsFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class ThumbnailsFragment() : Fragment() {
private var albumTag: String? = null companion object {
@JvmStatic
fun newInstance(albumTag: String?) =
ThumbnailsFragment().apply {
arguments = Bundle().apply {
putString("albumTag", albumTag)
}
}
} override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
albumTag = it.getString("albumTag")
}
} /*other code*/
}

底部导航栏切换Fragment

效果如下

BottomNavigationView

底部是BottomNavigationView组件,各个菜单在另外xml中定义,然后通过app:menu="xxx"指定。此处菜单定义如下

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_home_black_24dp"
android:title="@string/title_home" /> <item
android:id="@+id/navigation_dashboard"
android:icon="@drawable/ic_dashboard_black_24dp"
android:title="@string/title_dashboard" /> <item
android:id="@+id/navigation_notifications"
android:icon="@drawable/ic_notifications_black_24dp"
android:title="@string/title_notifications" /> </menu>

然后在代码中设置BottomNavigationView.setOnNavigationItemSelectedListener,判断当前选中的菜单项,手动切换Fragment,需要用到FragmentTransaction。如下示例

    override fun onClick(view: View?) {
val trans = activity.supportFragmentManager.beginTransaction()
val fragments = activity.supportFragmentManager.fragments
fragments.forEach {
if (it.isVisible) {
trans.hide(it) //隐藏当前显示的fragment
}
}
val tag = (view as TextView).text.toString()
val thumbnailsFragment = ThumbnailsFragment.newInstance(tag)
//fragment_main_container就是居中的那块区域,用于显示各个fragment
trans.add(R.id.fragment_main_container, thumbnailsFragment, tag)
trans.show(thumbnailsFragment)
trans.addToBackStack(null) //将本次操作入栈
trans.commitAllowingStateLoss() //提交
}

注意addToBackStack方法,该方法是为了实现回退时——用户按返回按钮或程序执行回退(配合popBackStack)——界面能返回到本次操作前的状态。也可指定tag,在跨[多次]操作回退时有用。注意此处入栈的是操作信息,而非fragment。

Navigation

上述手动控制Fragment的切换太麻烦。2018 I/O大会上,Google隆重推出一个新的架构组件:Navigation。它提供了多Fragment之间的转场、栈管理。在抽屉式/底部/顶部导航栏的需求中都可以使用这个组件。

使用:在res目录下新建navigation文件夹,然后新建一个navigation graph设为bottom_navigation:

<navigation 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:id="@+id/mobile_navigation"
app:startDestination="@+id/navigation_home"> <fragment
android:id="@+id/navigation_home"
android:name="com.eixout.presearchapplication.ui.home.HomeFragment"
android:label="@string/title_home"
tools:layout="@layout/fragment_home" /> <fragment
android:id="@+id/navigation_dashboard"
android:name="com.eixout.presearchapplication.ui.dashboard.DashboardFragment"
android:label="@string/title_dashboard"
tools:layout="@layout/fragment_dashboard" /> <fragment
android:id="@+id/navigation_notifications"
android:name="com.eixout.presearchapplication.ui.notifications.NotificationsFragment"
android:label="@string/title_notifications"
tools:layout="@layout/fragment_notifications" />
</navigation>

注意每个fragment的id要和之前定义的menu的id保持一致。可以设置转场动画,还可以设置每个fragment跳转的目标(destination),目标可以是 Activity或Fragment,也可以自定义。

然后在Activity布局文件中添加一个Fragment,设置name属性为android:name="androidx.navigation.fragment.NavHostFragment"。在传统的单Activity多Fragment场景中,我们往往需要为Activity添加一个FrameLayout作为Fragment的容器。在Navigation中HavHostFragment就是Fragment的容器(HavHostFragment继承了NavHost。The NavHost interface enables destinations to be swapped in and out.),其中设置app:navGraph="@navigation/bottom_navigation"使之与navigation graph建立联系。

    <fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/bottom_navigation"
other_config="..." />

app:defaultNavHost: If set to true, the navigation host will intercept the Back button.

最后将导航栏与Navigation关联

val navController = findNavController(R.id.nav_host_fragment)
bottomNavigationView.setupWithNavController(navController)

如此便大功告成了。

如果不依赖导航栏,而是手动跳转,则可以使用NavController的相关方法,比如在Activity里navController.navigate(actionId)

问题

Navigation和FragmentTransaction方式最好不要同时使用,它俩的返回堆栈似乎不是同一个,回退时会有问题。不能同时使用还使得下面两个问题不好解决。

  1. 使用Navigation,Fragment可以相互跳转没问题,但状态丢失了。比如A下滑一定距离后跳转到B,B回退到A,A的下滑状态丢失,仍是从头部开始显示。

  2. 每次点击BottomNavigationView的菜单项,对应的Fragment会recreate,这其实不是我们想要的,我们预期的应该是Fragment第一次创建后就一直复用,既保留了当前状态也不会对后端造成不必要的调用。

如果使用FragmentTransaction很好处理,只要缓存一个Fragment集合即可(若要保留Fragment的状态,比如滑动位置,可以使用supportFragmentManager.saveFragmentInstanceState(fragment)fragment.setInitialSavedState(savedState)加载,也可以使用hide/show(fragment)的方式),但用了Navigation后就没办法了。可以看看Navigation, Saving fragment state评论区的吐槽,里面也有临时的一些解决方案(不实用)。

FragmentTransaction本身也有对状态信息的处理考量,参看commit(), commitNow()和commitAllowingStateLoss()

参考资料

嵌套Fragment的使用及常见错误

Fragment 生命周期和使用

Android解惑 - 为什么要用Fragment.setArguments(Bundle bundle)来传递参数

BottonNavigationView+Fragment切换toolbar标题栏

手把手教你使用Android官方组件Navigation

Playing with Navigation Architecture Components

The Navigation Architecture Component Tutorial: Getting Started

Handle Complex Navigation Flow with Single-Activity Architecture and Android Jetpack’s Navigation component

导航到目的地-popUpTo 和 popUpToInclusive

Difference between add(), replace(), and addToBackStack()

【从零开始撸一个App】Fragment和导航中的使用的更多相关文章

  1. 【从零开始撸一个App】Kotlin

    工欲善其事必先利其器.像我们从零开始撸一个App的话,选择最合适的语言是首要任务.如果你跟我一样对Java蹒跚的步态和僵硬的语法颇感无奈,那么Kotlin在很大程度上不会令你失望.虽然为了符合JVM规 ...

  2. 【从零开始撸一个App】PKCE

    一个成功的App背后肯定有一堆后端服务提供支撑,认证授权服务(Authentication and Authorization Service,以下称AAS)就是其中之一,它是约束App.保障资源安全 ...

  3. 【从零开始撸一个App】Dagger2

    Dagger2是一个IOC框架,一般用于Android平台,第一次接触的朋友,一定会被搞得晕头转向.它延续了Java平台Spring框架代码碎片化,注解满天飞的传统.尝试将各处代码片段串联起来,理清思 ...

  4. 【从零开始撸一个App】RecyclerView的使用

    目标 前段时间打造了一款简单易用功能全面的图片上传组件,现在就来将上传的图片以图片集的形式展现到App上.出于用户体验考虑,加载新图片采用[无限]滚动模式,Android平台上我们优选Recycler ...

  5. Expo大作战(四)--快速用expo构建一个app,expo中的关键术语

    简要:本系列文章讲会对expo进行全面的介绍,本人从2017年6月份接触expo以来,对expo的研究断断续续,一路走来将近10个月,废话不多说,接下来你看到内容,讲全部来与官网 我猜去全部机翻+个人 ...

  6. 鼠标经过导航中li时,一个彩色模块跟着鼠标移动

    1.鼠标经过导航中li时,一个活动的li跟随鼠标移动,最终移动到鼠标的停留的位置.(如需鼠标离开后让活动的li回到初始位置,则用jq hover事件,当鼠标离开时,给活动的li设置left是0) 2. ...

  7. Android在应用中依据包名启动另外一个APP

    以下为TestIntentData工程 MainActivity如下: package cn.testintentdata; import java.util.List; import android ...

  8. Electron: 从零开始写一个记事本app

    Electron介绍 简单来说,Electron就是可以让你用Javascript.HTML.CSS来编写运行于Windows.macOS.Linux系统之上的桌面应用的库.本文的目的是通过使用Ele ...

  9. Eclipse创建第一个Servlet(Dynamic Web Project方式)、第一个Web Fragment Project(web容器向jar中寻找class文件)

    创建第一个Servlet(Dynamic Web Project方式) 注意:无论是以注解的方式还是xml的方式配置一个servlet,servlet的url-pattern一定要以一个"/ ...

随机推荐

  1. flutter--Dart基础语法(一)

    一.前言 Flutter 是 Google 开源的 UI 工具包,帮助开发者通过一套代码库高效构建多平台精美应用,Flutter 开源.免费,拥有宽松的开源协议,支持移动.Web.桌面和嵌入式平台. ...

  2. 一:整合shiro

    整合shiro 1.原生的整个 1.1 创建项目 1.2 创建Realm 1.3 配置shiro 2.使用Shiro Starter 2.1 项目创建 2.2 创建Realm 2.3 配置Shiro ...

  3. Fastjson使用实例

    Fastjson使用实例 一.FastJson使用范例 1.1FastJson三个核心类 1.2Maven依赖 1.3Scala API 1.3.1反序列化 1.3.2序列化 1.4Java API ...

  4. 希尔伯特曲线python3实现

    需要OpenGL库:https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyopengl #coding:utf-8 from OpenGL.GL import * ...

  5. C++的匿名函数(lambda表达式)

    总述  C++11提供了对匿名函数的支持,称为Lambda函数(也叫Lambda表达式). 它是定义和使用匿名函数对象的一种简便的方式.匿名函数是我们需要用到的一个函数,但是又不想去费力命名一个函数的 ...

  6. Java多线程操作同一份资源

    现在两个线程,可以操作初始值为零的一个变量,实现一个线程对该变量加1,一个线程对该变量减1,实现交替,来10轮,变量初始值为零. package com.yangyuanyuan.juc1205; i ...

  7. 2019牛客暑期多校训练营(第三场) F.Planting Trees(单调队列)

    题意:给你一个n*n的高度矩阵 要你找到里面最大的矩阵且最大的高度差不能超过m 思路:我们首先枚举上下右边界,然后我们可以用单调队列维护一个最左的边界 然后计算最大值 时间复杂度为O(n*n*n) # ...

  8. 牛客练习赛64 D【容斥+背包】

    牛客练习赛64 D.宝石装箱 Description \(n\)颗宝石装进\(n\)个箱子使得每个箱子中都有一颗宝石.第\(i\)颗宝石不能装入第\(a_i\)个箱子.求合法的装箱方案对\(99824 ...

  9. hdu 1696 Oulipo(KMP算法)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1686 题意 查询字符串 $p$ 在字符串 $s$ 中出现了多少次,可重叠. 题解 KMP模板题. Ti ...

  10. Codeforces Round #646 (Div. 2) A. Odd Selection(数学)

    题目链接:https://codeforces.com/contest/1363/problem/A 题意 判断是否能从 $n$ 个数中选 $x$ 个数加起来和为奇数. 题解 首先 $n$ 个数中至少 ...