Fragment介绍
为了让界面可以在平板上更好地展示,Android在3.0版本引入了Fragment (碎片) 功能,它非常类似于Activity,可以像Activity一样包含布局。
Fragment是一种可以嵌入在Activity当中的UI片段,可以让程序更合理和充分的利用大屏幕空间。
Fragment的使用方式
简单用法
如果要显示如下图所示的界面,需要在一个Activity中添加两个Fragment。
新建布局
首先要准备左侧Fragment的布局left_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Button"
/>
</LinearLayout>
右侧同理新建布局文件right_fragment.xml
,代码略
新建类中加载布局
接着需要新建一个LeftFragment
类,并让他继承于Fragment
,注意此处继承的是AndroidX.fragment.app.Fragment
!
//上面省略一些此处不重要的 import
import androidx.fragment.app.Fragment //注意是这个库!!
class LeftFragment : Fragment() {
private var _binding: LeftFragmentBinding ?= null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = LeftFragmentBinding.inflate(inflater,container,false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
右侧同理新建类RightFragment
,代码略
在主布局文件中插入
在activity_main.xml
中使用<fragment>
标签在布局中添加Fragment
<androidx.fragment.app.FragmentContainerView>
可以用做放置fragment
的容器
android:name
属性来显式声明要添加的Fragment类名,注意一定要将类的包名也加上。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:baselineAligned="false">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/leftFrag"
android:name="com.example.fragmenttest.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
/>
<androidx.fragment.app.FragmentContainerView
aandroid:id="@+id/rightFrag"
android:name="com.example.fragmenttest.RightFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
/>
</LinearLayout>
然后就会出现上图中的效果了。
动态添加Fragment
Fragment的强大之处在于,它可以在程序运行时动态地添加到Activity当中。根据具体情况来动态地添加Fragment,就可以将程序界面定制得更加多样化。
比如想实现,点击左侧的button
后,右边的Fragment
自动更改:
准备要替换的fragment
步骤跟上面一样,新建一个another_right_fragment.xml
,新建对应的类AnotherRightFragment
在主布局文件中放置容器
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:baselineAligned="false">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/leftFrag"
android:name="com.example.fragmenttest.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
/>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/rightLayout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
/>
</LinearLayout>
在MainActivity中添加指令
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
override fun onResume() {
super.onResume()
//val button = supportFragmentManager.findFragmentById(R.id.leftFrag)?.view?.findViewById<Button>(R.id.button1)
binding.leftFrag.rootView.findViewById<Button>(R.id.button1)?.setOnClickListener {
replaceFragment(AnotherRightFragment())
}
replaceFragment(RightFragment())
}
private fun replaceFragment(fragment: Fragment) {
val fragmentManager = supportFragmentManager
val transaction = fragmentManager.beginTransaction()
transaction.replace(R.id.rightLayout,fragment)
transaction.commit()
}
}
其中注释掉的是不使用 viewbinding的方法
运行结果
添加返回栈
FragmentTransaction
中提供了一个addToBackStack()
方法,可以用于将一个事务添加到返回栈中,接收⼀个名字用于描述返回栈的状态, ⼀般传入null
即可
点下返回键,右边部分就会返回到RightFragment
与Activity之间的交互
调用FragmentManager
的findFragmentById()
方法,可以在Activity中得到相应Fragment的实例,然后就能轻松地调用Fragment里的方法了
val fragment = supportFragmentManager?.findFragmentById(R.id.leftFrag) as LeftFragment
Fragment的生命周期
状态
- 运行状态:
当一个 fragment 是可见的,并且它所关联的活动也正处于运行状态时,那么该 fragment 也处于运行状态。 - 暂停状态
当一个 Activity 进入暂停状态时( 弹出一个对话框或被另一个未占满屏幕的活动覆盖后 ),与它相关联的 fragment 也会进入到暂停状态 - 停止状态
当一个 Activity 进入停止状态时,与它相关联的 Fragment 就会进入到停止状态,或者通过调用FragmentTransaction
的remove()
、replace()
方法将 fragment 从活动中移除,但如果在事务提交之前( 也就是 commit 之前 )调用addToBackStack()
方法,这时的 fragment 也会进入到停止状态。总的来说,进入停止状态的 fragment 对用户来说是完全不可见的,有可能会被系统回收。 - 销毁状态
fragment 总是依附于活动而存在的,因此当活动被销毁时,与它相关联的 fragment 就会进入到销毁状态。或者通过调用FragmentTransaction
的remove()
、replace()
方法将 fragment 从活动中移除,但如果在事务提交之前没有调用addToBackStack()
方法,这时的 fragment 也会进入到销毁状态。
回调
onAttach(Activity)
:当 fragment 和 Activity 建立关联的时候调用。onCreateView(LayoutInflater, ViewGroup, Bundle)
:为 fragment 创建视图 (加载布局) 时调用。onActivityCreated(Bundle)
:确保与 fragment 关联的活动一定已经创建完毕的时候调用,也就是当 Activity 中的onCreate()
方法返回时调用。onDestroyView()
:与onCreateView()
相对应,在 fragment 的视图被移除时调用。onDetach()
:与onAttach()
方法相对应,当 fragment 与 Activity 解除关联的时候调用。
图解
动态加载布局技巧
限定符
原理
书中表5.1 Android中常见的限定符
实践
在res
目录下新建一个layout-large
,并在该文件夹下新建一个布局activity_main.xml
layout
文件夹中的activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:baselineAligned="false">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/leftFrag"
android:name="com.example.fragmenttest.LeftFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
layout
文件夹中的activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:baselineAligned="false">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/leftFrag"
android:name="com.example.fragmenttest.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
/>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/rightFrag"
android:name="com.example.fragmenttest.RightFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"
/>
</LinearLayout>
手机模拟器:
平板模拟器:
large
就是一个限定符,那些屏幕被认为是large的设备就会自动加载layout-large
文件夹下的布局,小屏幕的设备则还是会加载layout
文件夹下的布局。(如上图)
使用最小宽度限定符
最小宽度限定符允许我们对屏幕的宽度指定一个最小值(以dp为单位),然后以这个最小值为临界点,屏幕宽度大于这个值的设备就加载一个布局,屏幕宽度小于这个值的设备就加载另一个布局。
比如:在res
目录下新建一个layout-sw600dp
,并在该文件夹下新建一个布局activity_main.xml
这就意味着,当程序运行在屏幕宽度大于等于600dp的设备上时,会加载layout-sw600dp/activity_main
布局,当程序运行在屏幕宽度小于600dp的设备上时,则仍然加载默认的layout/activity_main
布局。
最佳实践
显示一个新闻展示界面
在平板上运行左边显示新闻标题列表,右边显示内容。
在手机上运行只显示标题,点进去显示内容
一个份代码但是在手机平板运行是不同的效果。
Kotlin课堂
扩展函数
扩展函数,表示即使在不修改某个类的源码的情况下,仍然可以打开这个类,向这个类添加新的函数。
语法结构
fun ClassName.methodName(param1: Int, param2: Int): Int{
return 0;
}
使用
扩展函数可以定义在任何地方,但最好建议是被添加扩展函数的类的同名kotlin文件,这样方便查找。
通常定义为顶层方法,可以拥有全局的访问域
fun String.lettersCount(): Int {
var count = 0
for(char in this){
if(char.isLetter()){
count++
}
}
return count
}
这样在使用时就可以
val count = "ABC123xyz#".lettersCount()
运算符重载
关键字
operator
语法结构
class Obj{
operator fun plus(obj: Obj): Obj{
//处理相加逻辑
}
}
除了关键字和函数名时固定不变的,接收的参数和函数的返回值都可以自行设定。
普通使用
val obj1 = Obj()
val obj2 = Obj()
val obj3 = obj1 + obj2 //相当于 obj1.plus(obj2)
多重加载
定义Money类
class Money(val value: Int)
使用重载
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)
}
}
使用
val money1 = Money(5)
val money2 = Money(10)
val money3 = money1 + money2 //15
val money4 = money3 + 20 //35
语法糖表
语法糖表达式 | 实际调用函数 |
---|---|
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.unaryPlus() |
-a | a.unaryMinus() |
!a | a.not() |
a b 比较大小 | a.rangeTo(b) |
a…b | a.plus(b) |
a[b] | a.get(b) |
a[b] = c | a.set(b, c) |
a in b | b.contains(a) |
a <= b | a.compareTo(b) |