Android笔记(三)
1. Fragment
可以嵌入Activity的UI片段(fragment:片段)
1.1 Fragment使用方式
简单用法:
- 新建两个Fragment布局left_fragment.xml和right_fragment.xml
- 新建LeftFragment类和RightFragment类继承AndroidX库的Fragment(AndroidX库中的可以使其特性在所有Android系统版本中保持一致)
class LeftFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInsatnceState: Bundle?): View? {
return inflater.inflate(R.layout.left_fragment. container, false)
}
}
- 编写activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout height="match parent">
<fragment
android:id="@+id/leftFrag"
android:name="com.example.fragmenttest.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout weight="1" />
<fragment
android: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中的按钮,使得右侧的Fragment变换
再创建一个another_right_fragment.xml和AnotherRightFragment类
将上述activity_main.xml第二个fragment标签修改为FrameLayout标签
<FrameLayout
android:id="@+id/rightLayout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
</FrameLayout>
给左侧布局中的按钮添加点击事件,实现动态添加Fragment
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
replaceFragment(RightFragment())
button.setOnClickListener {
replaceFragment(AnotherRightFragment())
}
}
private fun replaceFragment(fragment: Fragment) {
val fragmentManager = supportFragmentManager
val transaction = fragmentManager.beginTransaction()
transaction.replace(R.id.rightLayout, fragment)
transaction.commit()
}
}
动态添加Fragment步骤:
- 创建待添加Fragment实例
- 获取FragmentManager,在Activity中可以直接调用getSupportFragmentManager()方法获取
- 开启一个事务,通过调用beginTransaction()方法开启
- 像容器添加或替换Fragment,一般用replace()方法实现,参数为容器id和带传入的Fragment实例
- 提交事务
实现返回栈:
FragmentTransaction提供给了一个addToBackStack()方法,可以用于将一个事务添加到返回栈中,参数接收一个名字用于描述返回栈状态,一般传入null
Fragment和Activity交互:
-
Activity调用Fragment
- FragmentTransaction提供了一个类似findViewById()方法用于从布局文件中获取Fragment实例
val fragment = supportFragmentManager.findFragmentById(R.id.leftFrag) as LeftFragment -
Fragment调用Activity
- 通过调用getActivity()方法得知当前相关联的Activity实例
val activity = activity as MainActivity - 注意:getActivity()可能会返回null,需要进行判空处理
-
不同Fragment之间进行通信
- 一个Fragment找到相关联的Activity,在通过这个Activity去获取另一个Fragment实例
1.2 Fragment的生命周期
状态: Fragment的状态与其相关联的Activity相同
- 运行状态
- 暂停状态
- 停止状态:或者通过事务的remove()、replace()方法将Fragment从Activity移除;在事务提交之前调用了addToBackStack()方法
- 销毁状态
回调方法: 除与Activity类似的回调方法外
- onAttach():当Fragment与Activity建立关联时调用
- onCreateView():为Fragment创建视图(加载布局)时调用
- onActivityCreated():确保与Fragment相关联的Activity创建完毕时调用
- onDestoryView():当与Fragment相关联的视图被移除时调用
- onDetach():当Fragment和Activity解除关联时调用
动态加载布局技巧:
使用限定符,限定符是在资源目录名的后添加的,比如layout_large资源目录,会在屏幕被认为是large的设备上自动加载该文件夹下的布局,小设备则会加载layout资源目录下的布局
大小限定符 | 描述 |
---|
small | 提供给小屏幕设备的资源 | normal | ……中等屏幕…… | large | ……大屏幕…… | xlarge | ……超大屏幕…… |
分辨率限定符 | 描述 |
---|
ldpi | 低分辨率(~120dpi) | mdpi | 中等分辨率(120dpi~160dpi) | hdpi | 高分辨率(160dpi~240dpi) | xhdpi | 超高分辨率(240dpi~320dpi) | xxhdpi | 超超高分辨率(320dpi~480dpi) |
最小宽度限定符:更加灵活地为不同设备加载布局,屏幕的宽度指定一个最小值(dp),如layout_sw600dp
相关知识:
《Android Fragment 非常详细的一篇》 - 简书
Fragment - Android 开发者
2. 广播机制
分类:
- 标准广播:
- 有序广播:
- 同步执行,同一时刻只有一个会接收到(优先级高的先接收到)
- BroadcastReceiver有先后顺序,可以被截断(broadcast:广播)
2.1 接收系统广播
动态注册监听时间变化:
class MainActivity : AppCompatActivity() {
lateinit var timeChangeReceiver: TimeChangeReceiver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val intentFilter = IntentFilter()
intentFilter.addAction("android.intent.action.TIME_TICK")
timeChangeReceiver = TimeChangeReceiver()
registerReceiver(timeChangeReceiver, intentFilter)
}
override fun onDestory() {
super.onDestory()
unregisterReceiver(timeChangeReceiver)
}
inner class TimeChangeReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent, Intent) {
Toast.makeTest(context, "Time has changed", Toast.LENGTH_SHORT).show()
}
}
}
查看完整的系统广播列表可以查看:
<Android SDK>/platforms/<任意android api版本>/data/broatcast_actions.text
静态注册实现开机启动:
注:动态注册具有很强的灵活性,但必须在程序启动之后才能接收广播,静态注册可以在程序未启动时接收广播
静态广播在安全性有很大的缺陷,所以Android 8.0之后所有隐式广播(大部分都为)不允许使用静态注册的方式来接收,只有少数可以
使用Android Studio快捷方式创建接收者类,会在清单文件中自动进行注册(创建时的Exported选项表示是否允许这个BroadcastReceiver接收本程序以外的广播,Enabled表示是否启动这个BroadcastReceiver)
静态注册使用<intent-filter> 标签过滤接收的广播,开机启动的广播值为android.intent.action.BOOT_COMPLETED
所有敏感功能都需要在清单文件中使用<uses-permission> 标签生命权限,开机权限为android.permission.RECEIVER_BOOT_COMPLETED
注意:不要在onReceive()方法中添加过多逻辑或耗时操作,因为BroadcastReceiver中不允许开启线程,当该方法运行时间过长没有结束时,程序就会出现错误
2.2 发送自定义广播
标准广播:
接收者和发送逻辑我们定义在同一个程序(项目)中,接收者通过静态注册方式接收广播(这些代码省略)
class MainActivity : AppCompatActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener {
val intent = Intent("com.example.broadcasttest.MY_BROADCAST")
intent.setPackage(packageName)
sendBroadcast(intent)
}
}
}
前面听到过静态注册不能接收隐式广播,而自定义的广播都是隐式广播,所以这个调用setPackage()方法,指定该广播发送给哪个应用程序,从而变成显示广播,否则接收不到该广播
有序广播:
有序广播发送广播的方法为sendOrderedBoradcast()(order:订单、顺序)
- 第一个参数:intent
- 第二个参数:一个与权限相关的字符串
优先级高的接收者可以先接收到广播,通过在<intent-filter> 的android:priority 属性指定优先级(priority:优先级、优先的)
在BroadcastReceiver的onReceive()方法中可以通过abortBroadcast()方法截断广播
相关知识:
隐式广播例外情况 - Android 开发者
Android中action启动方法大全 - CSDN博客
|