Fragment 概念

把 Activity 中的一段 UI 和逻辑封装到一个 Fragment 中,实现可拔插,减少对 Activity 代码的侵入。

Fragment 定义和管理自己的布局,具有自己的生命周期,并且可以处理自己的输入事件。Fragment 不能独立存在,而是必须由 Activity 或另一个 Fragment 托管。

图中 Example1,一共有三个 Fragment,蓝色的 Fragment 嵌套了两个子 Fragment。Example2 是一个 swipe 视图,蓝色的 Fragment 有两个以上的 Fragment。

Fragment 应该管理它自己的界面所需的逻辑,避免让一个 Fragment 依赖于另一个 Fragment 或从一个 Fragment 操控另一个 Fragment。

  • 模块:可以在同一 Activity 或多个 Activity 中使用同一 Fragment;或者把一个 Activity 划分为多个 Fragment 进行布局。
  • 嵌套:Fragment 可以作为另一个 Fragment 的父级。

创建 Fragment

FragmentContainerView

创建一个 Fragment XML 视图 fragment_index.xml:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"> <TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Index Fragment"
android:textSize="30sp" /> </FrameLayout>

创建一个 Fragment 实现类,IndexFragment:

class IndexFragment : Fragment(R.layout.fragment_index) {
}

在 activity_main.xml 添加一个 FragmentContainerView:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:background="#FFC107"
tools:context=".MainActivity"> <androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container_view"
android:name="com.example.chap04.IndexFragment"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginBottom="531dp"
android:background="#009688"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>

属性 name 添加 IndexFragment,启动 Activity 时会把 fragment_index.xml 视图插入到 FragmentContainerView 中。

通过上图得知,绿色区域是 IndexFragment,橙色区域是 Activity,我们通过 Fragment 实现了划分 Activity 布局的管理,接下来将通过 Kotlin 代码实现对 Fragment 的替换、删除、增加操作,进一步了解 Fragment。

FragmentManager

FragmentManager 可以替换、删除、增加 Fragment。在 activity_main.xml 中,删除 FragmentContainerView 的 name 属性,而是通过 FragmentManager 把 Fragment 的视图插入到 Activity 中:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:background="#FFC107"
tools:context=".MainActivity"> <androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container_view"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginBottom="531dp"
android:background="#009688"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>

你需要获取 FragmentManager,并调用 beginTransaction() 得到 FragmentTransaction。调用 add() 添加 Fragment 到指定 View 中,这里是资源 ID 为 fragment_container_view 的 View(也就是 FragmentContainerView)。最后,调用 commit() 提交 FragmentTransaction。

class MainActivity : AppCompatActivity(R.layout.activity_main) {

  override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) supportFragmentManager
.beginTransaction()
.setReorderingAllowed(true)
.add(R.id.fragment_container_view, IndexFragment(), null)
.commit()
} }

当 FragmentManager 执行 FragmentTransaction 时,特别是堆栈上运行并运行动画和转换时(Fragment 切换其他 Fragment 时执行的动画效果),应该总是使用 setReorderingAllowed(true)。调用这个方法可确保如果同时执行多个 FragmentTransaction 时,任何一个 Fragment 都不会经历生命周期更改或执行其动画或转换。了解更多关于 setReorderingAllowed 的信息,请看官方文档的 Fragment Transaction。简而言之,调用这个函数可以提升性能,避免过多更改堆栈上其他 Fragment 的生命周期。

Bundle 传递数据

Fragment 可以接收从 Activity 传递过来的 Bundle 类型数据。在 add() 方法被调用时传入参数,Fragment 通过 requireArguments() 就可以获取到。

MainActivity.kt:

class MainActivity : AppCompatActivity(R.layout.activity_main) {

  override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) if (savedInstanceState == null) {
val bundle = Bundle()
bundle.putInt("param", 20);
supportFragmentManager
.beginTransaction()
.setReorderingAllowed(true)
.add(R.id.fragment_container_view, IndexFragment().javaClass, bundle, null)
.commit()
}
} }

IndexFragment.kt:

class IndexFragment : Fragment(R.layout.fragment_index) {

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val int = requireArguments().getInt("param")
Log.d("Test", int.toString())
} }

接下来对if (savedInstanceState == null)进行一个解释。直接通过现象来观察一个问题:

1️⃣去掉这一个判断,来看看会发生什么:

我切换了黑夜/白昼模式,Logcat 中就打印了多次,说明我们的 onCreate 函数多次被调用,同时也多次重复添加 Fragment。

2️⃣加上这一个判断,再来看看会发生什么:

屏幕反转、屏幕黑夜/白昼模式切换都会触发onCreate(),而 savedInstanceState 能记录 Activity 的变化,当 Activity 初始时它才是 null。所以要加上一个判断,Activity 第一次创建时才增加 Fragment 到视图中,避免重复添加 Fragment。

实现 TabBar

前面已经简单地了解了 FragmentManager 和 FragmentTransaction。现在通过 Tabbar 案例来实现 Fragment 的切换。

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"> <androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container_view"
android:name="com.example.chap04.IndexFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/tabbar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> <LinearLayout
android:id="@+id/tabbar"
android:layout_width="0dp"
android:layout_height="50dp"
android:gravity="center_horizontal|center_vertical"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"> <TextView
android:id="@+id/nav_index"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="首页" /> <TextView
android:id="@+id/nav_dynamic"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="动态" /> <TextView
android:id="@+id/nav_person"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="我的" />
</LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>

MainActivity.kt

class MainActivity : AppCompatActivity(R.layout.activity_main) {

  override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) if (savedInstanceState == null) {
findViewById<TextView>(R.id.nav_index).setOnClickListener {
supportFragmentManager
.beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.fragment_container_view, IndexFragment().javaClass, null)
.commit()
} findViewById<TextView>(R.id.nav_dynamic).setOnClickListener {
supportFragmentManager
.beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.fragment_container_view, DynamicFragment().javaClass, null)
.commit()
} findViewById<TextView>(R.id.nav_person).setOnClickListener {
supportFragmentManager
.beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.fragment_container_view, PersonFragment().javaClass, null)
.commit()
}
} } }

实现效果

这种 TabBar 的切换方式是通过 FragmentManager 和 FragmentTransaction 替换视图中的 FragmentContainerView,还有一种是不借助这两个类实现页面切换,通过 ViewPager2 来实现页面,而且也是实现页面滑动的最简单方式。

借助 ViewPager2 实现 TabBar

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"> <androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager2"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/tabbar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> <LinearLayout
android:id="@+id/tabbar"
android:layout_width="0dp"
android:layout_height="50dp"
android:gravity="center_horizontal|center_vertical"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"> <TextView
android:id="@+id/nav_index"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="首页" /> <TextView
android:id="@+id/nav_dynamic"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="动态" /> <TextView
android:id="@+id/nav_person"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="我的" />
</LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>

页面和上面的没多大区别,只是把 FragmentContainerView 替换成了 ViewPager2。

ViewPager2Adapter.kt

class ViewPage2Adapter(fragmentActivity: FragmentActivity, fragments: List<Fragment>) : FragmentStateAdapter(fragmentActivity) {

  private var fragments: List<Fragment>

  init {
this.fragments = fragments
} override fun getItemCount(): Int {
return fragments.size
} override fun createFragment(position: Int): Fragment {
return fragments[position]
} }

MainActivity.kt

class MainActivity : AppCompatActivity(R.layout.activity_main) {

  override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) if (savedInstanceState == null) {
val pager = findViewById<ViewPager2>(R.id.view_pager2)
val navBars = listOf<TextView>(findViewById(R.id.nav_index), findViewById(R.id.nav_dynamic), findViewById(R.id.nav_person))
val fragments = listOf(IndexFragment(), DynamicFragment(), PersonFragment())
pager.adapter = ViewPage2Adapter(this, fragments) var lastPosition = 0
pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(currPosition: Int) {
navBars[lastPosition].setBackgroundResource(R.color.white)
navBars[currPosition].setBackgroundResource(R.color.nav_selected)
lastPosition = currPosition
}
}) navBars[0].setOnClickListener {
pager.currentItem = 0
} navBars[1].setOnClickListener {
pager.currentItem = 1
} navBars[2].setOnClickListener {
pager.currentItem = 2
}
}
} }

切换 ViewPager2 的项时也需要改变底部导航栏的背景颜色,所以需要给 ViewPager2 注册一个监听器(registerOnPageChangeCallback),动态地得到 position,从而实现鼠标滑动时也改变底部导航栏对应的背景颜色。

实现效果

Gitee 仓库地址:Chap04-Fragment

Android:Fragment的更多相关文章

  1. Android:Fragment+ViewPager实现Tab滑动

    public class FragAdapter extends FragmentPagerAdapter { private List<Fragment> fragments ; pub ...

  2. Android:手把手教你 实现Activity 与 Fragment 相互通信,发送字符串信息(含Demo)

    前言Activity 与 Fragment 的使用在Android开发中非常多今天,我将主要讲解 Activity 与 Fragment 如何进行通信,实际上是要解决两个问题: Activity 如何 ...

  3. Android:理解Fragment

    最近都在公司搞测试,静不下心来学android.今天就把Fragment搞懂吧. Fragment的几点要点: 1.用于大屏幕平板,容纳更多组件,可复用2.Fragment必须嵌入Activity中 ...

  4. android 学习随笔二十三(动画:Fragment )

    Fragment * 用途:在一个Activity里切换界面,切换界面时只切换Fragment里面的内容 * 在一个Activity中切换多个界面,每个界面就是一个Fragment* Fragmnen ...

  5. android UI:Fragment碎片

    碎片(Fragment) 嵌入与活动中的UI片段,为了合理的分配布局而存在,这是我的简单理解.多用于兼顾手机与平板的UI,也适用于灵活高级的UI制作. Demo 简单的按键切换两片不同的Demo 新建 ...

  6. Android用户界面开发:Fragment

    Android用户界面开发:Fragment 1:注意事项  3.0以前的Android 版本要使用FragmentActivity 来装载Fragment ,使用到support v4包.  3.0 ...

  7. 【转】 Pro Android学习笔记(四三):Fragment(8):再谈Transaction和管理器

    目录(?)[-] Transaction的一些操作 再谈FragmentManager 调用其他fragment的方法 唤起activity 唤起fragment和相互通信 一些其它 Transact ...

  8. 【转】 Pro Android学习笔记(四二):Fragment(7):切换效果

    目录(?)[-] 利用setTransition 利用setCustomAnimations 通过ObjectAnimator自定义动态效果 程序代码的编写 利用fragment transactio ...

  9. 【转】 Pro Android学习笔记(四十):Fragment(5):适应不同屏幕或排版

    目录(?)[-] 设置横排和竖排的不同排版风格 改写代码 对于fragment,经常涉及不同屏幕尺寸和不同的排版风格.我们在基础小例子上做一下改动,在横排的时候,仍是现实左右两个fragment,在竖 ...

  10. 【转】 Pro Android学习笔记(四一):Fragment(6):数据保留

    目录(?)[-] 通过fragment参数实现数据保留 对TitleFragment进行修改 对DetailActivity进行修改 通过savedInstanceState进行数据保留 保留frag ...

随机推荐

  1. 【Hadoop学习】中:HDFS、shell操作、客户端API操作、数据流、1NN、2NN原理、DataNode配置

    一.概述 1.背景.定义.使用场景(一次写入.不支持修改) 2.优(容错)缺点(延迟.不支持小文件.不支持修改) 3.组成架构 NameNode:Master,管理命名空间.配置策略 DataNode ...

  2. Ubuntu20.04更换国内镜像源(阿里、网易163、清华、中科大)

    更换方法 Ubuntu采用apt作为软件安装工具,其镜像源列表记录在/etc/apt/source.list文件中. 首先将source.list复制为source.list.bak备份,然后将sou ...

  3. 下载kali以及安装

    一.kali下载 下载地址:https://www.kali.org/get-kali/#kali-virtual-machines 二.kali安装 1.打开VMware,打开文件,选择刚刚下载并解 ...

  4. week_4

    Andrew Ng 机器学习笔记---by Orangestar Week4_Neural Networks : Representation 1. Non-linear Hypotheses 当特征 ...

  5. git remote update origin --prune命令失败

    1.问题描述 我使用git remote update origin --prune命令更新远程分支上的代码,结果出现如下报错. 2.产生原因 本地关联的远程仓库失效了,需要重新再关联一下. 3.解决 ...

  6. GitHub车牌检测识别项目调研

    一,EasyOCR 1.1,仓库介绍 1.2,使用记录 二,HyperLPR 2.1,HyperLPR 概述 2.3,使用记录 2.3,使用建议 三,simple-car-plate-recognit ...

  7. C#开发的插件程序 - 开源研究系列文章

    上次编写了一个家庭小助手的小工具软件,也对其中的线程池及管理器进行了介绍(见博文:C#开发的线程池和管理器 - 开源研究系列文章),这次把里面的插件部分的功能单独放出来进行介绍,让更多的朋友能够进行学 ...

  8. 再讲Floyd变形:传递闭包类问题

    题目 今天上课老师讲到一道传递闭包的问题,由于蒟蒻之前讲的不详细再来讲一遍. 传送门 思路 建图,注意是有向图. 能确定名次指的是:在图上由这个点可以到达的点数+在图上可以到达这个点的点数=n-1 对 ...

  9. 1.5万字长文:从 C# 入门 Kafka

    目录 1, 搭建 Kafka 环境 安装 docker-compose 单节点 Kafka 的部署 Kafka 集群的部署 2, Kafka 概念 基本概念 关于 Kafka 脚本工具 主题管理 使用 ...

  10. ORM哪家强?java,c#,php,python,go 逐一对比, 网友直呼:全面客观

    前言 最近一段时间,我使用golang开发了一个新的ORM库. 为了让这个库更好用,我比较研究了各语言的主流ORM库,发现有一些语言的ORM库确实很好用,而有另外一些语言的库那不是一般的难用. 然后我 ...