Android 自 3.0 版本开始引入了 Fragment 的概念,Fragment 是一种可以嵌入在活动当中的 UI 片段,它能让程序更加合理和充分地利用大屏幕的空间,因而在平板上应用得非常广泛。
动态添加 Fragment 主要分为 5 步:
- 创建待添加 Fragment 的实例。
- 获取 FragmentManager,在 Activity 中可以直接通过调用
getSupportFragmentManager()
方法获取。 - 开启一个事务,通过调用
beginTransaction()
方法开启。 - 向容器内添加或替换 Fragment,一般使用
replace()
方法实现,需要传入容器的 id 和待添加的 Fragment 实例。 - 提交事务,调用
commit()
方法来完成。
private fun replaceFragment(fragment: Fragment) {
val fragmentManager = supportFragmentManager
val transaction = fragmentManager.beginTransaction()
transaction.replace(R.id.rightLayout, fragment)
// transaction.addToBackStack(null)
transaction.commit()
}
FragmentTransaction 中提供了一个 addToBackStack()
方法,可以用于将一个事务添加到返回栈中。它可以接收一个名字用于描述返回栈的状态,一般传入null 即可。
**Activity 中获取 Fragment **
为了方便 Fragment 和 Activity 之间进行通信,FragmentManager 提供了一个类似于 findViewById()
的方法,专门用于从布局文件中获取碎片的实例。
val fragment = supportFragmentManager.findFragmentById(R.id.leftFrag) as LeftFragment
kotlin-android-extensions
插件也对 findFragmentById()
方法进行了扩展,允许我们直接使用布局文件中定义的 Fragment id 名称来自动获取相应的 Fragment 实例。
val fragment = leftFrag as LeftFragment
**Fragment 中调用 Activity **
在每个 Fragment 中都可以通过调用 getActivity()
方法来得到和当前 Fragment 相关联的 Activity 实例。当 Fragment 中需要使用 Context 对象时,也可以使用该方法,因为获取到的 Activity 本身就是一个 Context 对象。
if (activity != null) {
val mainActivity = activity as MainActivity
}
Fragment 之间通信
首先在一个 Fragment 中可以得到与它相关联的 Activity,然后再通过这个 Activity 去获取另外一个 Fragment 的实例,这样也就实现了不同 Fragment 之间的通信功能。
-
运行状态
当一个 Fragment 是可见的,并且它所关联的 Activity 正处于运行状态时,该 Fragment 也处于运行状态。
-
暂停状态
当一个 Activity 进入暂停状态时(由于另一个未占满屏幕的 Activity 被添加到了栈顶),与它相关联的可见 Fragment 就会进入到暂停状态。
-
停止状态
当一个 Activity 进入停止状态时,与它相关联的 Fragment 就会进入到停止状态,或者通过调用 FragmentTransaction 的
remove()
、replace()
方法将 Fragment 从 Activity 中移除,但如果在事务提交之前调用addToBackStack()
方法,这时的 Fragment 也会进入到停止状态。总的来说,进入停止状态的 Fragment 对用户来说是完全不可见的,有可能会被系统回收。 -
销毁状态
当 Activity 被销毁时,与它相关联的 Fragment 就会进入到销毁状态。或者通过调用 FragmentTransaction 的
remove()
、replace()
方法将碎片从活动中移除,但在事务提交之前并没有调用addToBackStack()
方法,这时的碎片也会进入到销毁状态。
Fragment 类中也提供了一系列的回调方法,以覆盖碎片生命周期的每个环节。Activity 中有的回调方法,Fragment 中几乎 都有,Fragment 还提供了一些附加的回调方法。
-
onAttach()
当 Fragment 和 Activity 建立关联的时候调用。
-
onCreateView()
为 Fragment 创建视图(加载布局)时调用。
-
onActivityCreated()
确保与 Fragment 相关联的 Activity 已经创建完毕时调用。
-
onDestroyView()
当与 Fragment 关联的视图被移除的时候调用。
-
onDetach()
当 Fragment 和 Activity 解除关联的时候调用。
Android 中一些常见的 qualifier 表示。
屏幕特征 | 限定符 | 描述 |
大小 | small | 提供给小屏幕设备的资源 |
---|---|---|
normal | 提供给中等屏幕设备的资源 | |
large | 提供给大屏幕设备的资源 | |
xlagre | 提供给超大屏幕设备的资源 | |
分辨率 | ldpi | 提供给低分辨率设备的资源(120dpi 以下) |
mdpi | 提供给中等分辨率设备的资源(120dpi-160dpi) | |
hdpi | 提供给高分辨率设备的资源(160dpi-240dpi) | |
xhdpi | 提供给超高分辨率设备的资源(240dpi-320dpi) | |
xxhdpi | 提供给超超高分辨率设备的资源(320dpi-480dpi) | |
方向 | land | 提供给横屏设备的资源 |
port | 提供给竖屏设备的资源 |
smallest-width qualifier
最小宽度限定符允许我们对屏幕的宽度指定一个最小值(以 dp 为单位),然后以这个最小值为临界点,屏幕宽度大于这个值的设备就加载一个布局,屏幕宽度小于这个值的设备就加载另一个布局。
在 res 目录下新建 layout-sw600dp
文件夹,然后在这个文件夹下新建 activity_main.xml
布局。
当程序运行在屏幕宽度大于等于 600dp 的设备上时,会加载 layout-sw600dp/activity_main
布局,当程序运行在屏幕宽度小于 600dp 的设备上时,则仍然加载默认的 layout/activity_main
布局。
MainActivity
- activity_main.xml(layout) startActivity()
- <fragment> NewsTitleFragment-------------------------
| - news_title_frag.xml |
| - <RecyclerView> |
| - news_item.xml |
| - <TextView newsTitle> |
| |
| |
| NewsContentActivity
| - activity_news_content.xml
| |
- activity_main.xml(layout-sw600dp) | |
- <fragment> NewsContentFragment ---------------------|
- news_content_frag.xml
- <TextView newsTitle>
- <TextView newsContent>
通过限定符动态加载 /layout/activity_main.xml
或 /layout-sw600dp/activity_main.xml
。
普通分辨率下加载展示 title 的 fragment,高分辨率下同时加载 title 、content 两个 fragment。
title 的 fragment 样式为 news_title_frag.xml
使用了 RecyclerView ,具体样式在 news_item.xml
中。
content 的 fragment 样式为 news_content_frag.xml
。
title 的点击事件中通过判断单页还是双页,执行不同的操作。
对于普通分辨率模式,启动 NewsContentActivity,样式 activity_news_content.xml
中使用的还是 content 的 fragment。
对于高分辨率模式,直接刷新 content 的 fragment 中的内容即可。
扩展函数表示即使在不修改某个类的源码的情况下,仍然可以打开这个类,向该类添加新的函数。
定义扩展函数的语法结构:
fun ClassName.methodName(param1: Int, param2: Int): Int {
return 0
}
相比定义普通函数,定义扩展函数只需要在函数名前加上一个 ClassName.
的语法结构,就表示将该函数添加到指定的类当中了。
建议向哪个类中添加扩展函数,就定义一个同名的 Kotlin 文件,便于以后查找。
扩展函数也是可以定义在任何一个现有类当中的,并不一定非要创建新文件。
最好将它定义成顶层方法,这样可以让扩展函数拥有全局的访问域。
fun String.lettersCount(): Int {
var count = 0
for (char in this) {
if (char.isLetter()) {
count++
}
}
return count
}
函数中自动拥有了 String 实例的上下文,this 就代表着字符串本身。
统计某个字符串中的字母数量:
val count = "ABC123xyz!@#".lettersCount()
Kotlin 允许我们将所有的运算符甚至其他的关键字进行重载,从而扩展这些运算符和关键字的用法。
运算符重载使用的是 operator
关键字,只要在指定的函数前面算加上 operator 关键字,就可以实现运算符重载的功能了。
比如加号运算符对应的是 plus()
函数,以此为例,它的语法结构如下:
class Obj {
operator fun plus(obj: Obj): Obj {
// 处理相加的逻辑
}
}
关键字 operator 和函数名 plus 都是固定不变的,而接收的参数和函数返回值可以根据你的逻辑自行设定。
对于 obj1 + obj2
,它会在编译的时候被转换成 obj1.plus(obj2)
的调用方式。
Kotlin 允许我们对同一个运算符进行多重重载。
class Money(val value: Int) {
operator fun plus(money: Money): Money {
val sum = value + money.value
return Money(sum)
}
operator fun plus(newValue: Int): Money {
val sum = value + newValue
return Money(sum)
}
}
语法糖表达式和实际调用函数对照表
语法糖表达式 | 实际调用函数 |
---|---|
a + b | a.plus(b) |
a - b | a.minus(b) |
a * b | a.times(b) |
a / b | a.div(b) |
a % b | a.rem(b) |
a++ | a.inc() |
a-- | a.dec() |
+a | a.unaryPlus() |
-a | a.unaryMinus() |
!a | a.not() |
a == b | a.equals(b) |
a > b | |
a < b | |
a >= b | a.compareTo(b) |
a <= b | |
a..b | a.rangeTo(b) |
a[b] | a.get(b) |
a[b] = c | a.set(b, c) |
a in b | b.contains(a) |