第3章 Android的Activity组件 Activity(活动)组件是Android的四大组件之一,是Android移动应用的基本组件,用 于设计移动应用界面的屏幕显示。Android移动应用往往需要多个界面,因此移动应用中 必须定义一个或多个Activity以实现界面的交互。在Android3.0以后的版本中出现的 Fragment(碎片)可以嵌入Activity的图形用户界面中。目前,采用单个Activity和多个 Fragment结合的方式已成为移动应用界面定义的首选。本章将对Activity组件以及嵌入 Activity组件的Fragment进行介绍。 3.1 Activity的创建 Activity用于创建移动界面的屏幕。它的主要作用就是实现移动界面和用户之间的交 互。Activity类用于创建和管理用户界面,一个应用程序可以有多个Activity,但在同一个 时间内只有一个Activity处于激活状态。在移动应用中,Activity之间的依赖关系低。 创建Activity时,需要完成如下几个步骤。 (1)定义Activity类。 (2)定义Activity对应的布局文件,使得布局文件成为Activity的界面定义。 (3)在配置清单文件AndroidManifest.xml中声明并配置Activity的相应属性。 1.定义Activity类 Activity必须是自己的子类或扩展子类的子类。因为Activity 的一个子类为 AppCompatActivity,它可以定义MaterialDesign风格的界面,所以在当前Android中创建 Activity一般是定义为AppComaptActivity的子类。形式如下: class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } } 在MainActivity(主活动)中定义了onCreate()函数,该函数是必须定义的回调函数,它 会在系统创建Activity时被调用。在Activity中必须使用setContentView()函数来指定 Activity的界面布局。这个布局通过资源引用R.layout.activity_main来引用资源目录下的 res/layout/activity_main.xml的布局文件。布局文件(如activity_main.xml)用于定义 Activity的外观,Activity可以显示并对布局文件的界面组件进行控制,实现交互行为,如 图3-1所示。 2.创建Activity的布局文件 布局配置文件保存在res/layout目录下,是XML文件。通过配置布局的相关属性和 ·44· 图3-1 Activity和布局之间的关系示意 元素可实现布局。布局文件中所有的元素必须配置android:layout_width和android:layout_ height属性,它们分别表示元素在屏幕的宽度和高度,一般取值如下。 (1)match_parent:表示使视图扩展至父元素大小。 (2)wrap_content:表示自适应大小,将组件元素的全部内容进行显示。 当然,也可以设置尺寸大小。Activity类通过setContentView()来引用布局资源,实现 布局在Activity中的渲染和展示,代码如下: <?xml version="1.0" encoding="utf-8"?> <TextView android: id="@+id/textView" 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: text="Hello World!" android: layout_width="match_parent" android: layout_height="match_parent" tools: context=".MainActivity" /> 在上述文件中,根元素是TextView,是一个文本标签。如果希望在Activity中对整个 TextView可以进行引用和处理,可以在布局文件中使用android:id="@+id/textView" 来标识资源。其中,"@ +id"表示增加一个资源id。如果希望在布局文件中引用这个 TextView,可以通过"@id/textView"实现。但是在Activity(例如MainActivity代码)中需 要通过这个TextView的资源id来引用,形式如下: val textView: TextView=findViewById(R.id.textView) 如果在模块的构建文件build.gradle中增加插件kotlin-android-extensions,形如: apply plugin: 'kotlin-android-extensions' 则可以直接在Activity中通过资源编号引用View(视图)组件: textView.text="Hello" //直接设置布局资源编号为textView 的文本为Hello 但是由于kotlin-android-extensions插件只能对Kotlin支持,目前已经被弃用。 3.在配置清单文件AndroidManifest.xml中注册Activity 要让创建的Activity能让移动应用识别和调用,就必须在AndroidManifest.xml系统 配置清单文件中进行声明和配置。AndroidManifest.xml文件保存在项目的manifests目 ·45· 录中。代码如下: <?xml version="1.0" encoding="utf-8"?> <manifest xmlns: android="http://schemas.android.com/apk/res/android" package="chenyi.book.android.ch03_01"> <application android: allowBackup="true" android: icon="@mipmap/ic_launcher" android: label="@string/app_name" android: roundIcon="@mipmap/ic_launcher_round" android: supportsRtl="true" android: theme="@style/Theme.Chapter03"> <activity android: name=".MainActivity"android: exported="true"> <intent-filter> <action android: name="android.intent.action.MAIN" /> <category android: name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> 在application元素(表示移动应用)中增加下级元素activity,并通过android:name属 性指定活动的类名。在manifest元素的package属性中指定了包名,所以在activity元素 的android:name中只需要定义成 android: name=".MainActivity" 即可。在activity元素下定义了intent-filter意图过滤器。intent-filter定义的意图过滤器 非常强大,通过它可以根据显式请求启动Activity,也可以根据隐式请求启动Activity。下 面代码用于指定MainActivity(主活动),它是整个移动应用的入口活动,表示应用程序可以 显示在程序列表中。 action android: name="android.intent.action.MAIN" category android: name="android.intent.category.LAUNCHER" 注意,在Android12及以上版本中使用intent-filter元素配置时,必须时Activity增加配置 android:exported属性。 3.2 Activity和Intent 一个应用可以定义多个Activity,这些Activity可以相互切换和跳转。要实现这些交 互功能,就必须了解什么是Intent(意图)。Intent表示封装执行操作的意图,是应用程序启 动其他组件的Intent对象启动组件。这些组件可以是Activity,也可以是其他基本组件,如 Service(服务)组件、BroadcastReceiver(广播接收器)组件。从一个基本组件导航到另一个组 件。通过Intent实现组件之间的跳转和关联。Intent分为显式Intent和隐式Intent两种。 3.2.1 显式Intent 显式Intent就是在Intent()函数启动组件时,需要明确指定的激活组件名称,例如: ·46· Intent(Context packageContext, Class<?>c) 其中,Intent()函数包含两个参数,packageContext用于提供启动Activity的上下文,c用于 指定想要启动的目标组件类。 例3-1 显式Intent的应用。在本应用中定义3 个Activity 类:MainActivity、 FirstActivity和SecondActivity。其中,MainActivity用于定义菜单,具体的菜单项包括“第 一个活动”“第二个活动”“退出”。根据菜单项,可跳转至不同的活动或退出应用。选中“第一 个活动”菜单项,可跳转到FirstActivity,选中“第二个活动”菜单项,可跳转到SecondActivity,选 中“退出”菜单项,则从移动应用中退出。 在新建移动应用模块时,因为本例涉及菜单的应用,所以需要先定义相关的资源文件。 (1)定义相关资源。 移动应用往往需要大量的字符串来配置相关的组件,因此可以将字符串统一定义在 res/values目录下的strings.xml配置文件中,通过设置string元素配置字符串。string元 素的name属性表示字符串的名字,string元素的内容表示字符串的取值,代码如下: <!--模块Ch03_02 定义字符串资源文件res/values/strings.xml--> <resources> <string name="app_name">Ch03_02</string> <string name="title_first_activity">第一个活动</string> <string name="title_second_activity">第二个活动</string> <string name="title_exit_app">退出</string> </resources> 在Activity中创建菜单时,必须依赖菜单资源。在res目录下创建资源目录menu,然 后在res/menu目录下创建menu.xml文件,可以直接利用编辑器在menu.xml中编辑菜单 的内容,也可以通过编辑器的Design界面来实现。在这个例题中,res/menu目录下的 menu.xml的内容如下: <!--模块Ch03_02 定义菜单资源文件res/menu/menu.xml--> <?xml version="1.0" encoding="utf-8"?> <menu xmlns: android="http://schemas.android.com/apk/res/android"> <item android: id="@+id/firstItem" android: icon= "@android: drawable/arrow_up_float" android: title="@string/title_first_activity"/> <item android: id="@+id/secondItem" android: icon="@android: drawable/arrow_down_float" android: title="@string/title_second_activity" /> <item android: id="@+id/exitItem" android: icon="@android: drawable/ic_lock_power_off" android: title="@string/title_exit_app" /> </menu> 在menu.xml文件中,menu元素定义菜单,item 元素定义菜单项。在菜单项中使用 android:id指定菜单的id资源编号,android:icon表示菜单的图标,android:title定义菜 单对应的文字标签。 ·47· 如果在移动应用中需要使用具体尺寸大小的相关资源,可以采用类似的方法创建 dimens.xml来保存尺寸资源。具体做法是,选中res/values目录并右击,在弹出的快捷菜 单中选中New|ValuesResourceFile选项,然后指定资源名,如dimens,单击OK 按钮,创 建dimens.xml的尺寸大小的资源文件,代码如下: <!--模块Ch03_02 定义尺寸资源文件res/values/dimens.xml --> <?xml version="1.0" encoding="utf-8"?> <resources> <dimen name="size_text">40sp</dimen> </resources> 指定的尺寸采用了sp单位,表示比例无关的像素单位,常常表示字体的大小。表示布 局的尺寸往往使用dp单位,dp单位代表密度无关的像素,是基于屏幕的物理密度的抽象或 虚拟单元。 (2)定义MainActivity。MainActivity是当前应用的入口。它对应的布局文件activity _main.xml文件如下: <!--模块Ch03_02 主活动对应的布局文件activity_main.xml --> <?xml version="1.0" encoding="utf-8"?> <TextView xmlns: android="http://schemas.android.com/apk/res/android" xmlns: tools="http://schemas.android.com/tools" android: layout_width="wrap_content" android: layout_height="wrap_content" android: layout_gravity="center" android: text="Hello World!" tools: context=".MainActivity"/> 在MainActivity中不但利用activity_main.xml渲染生成显示的界面,而且定义菜单, 并根据菜单实现不同活动的跳转。代码如下: //模块Ch03_02 主活动的定义文件MainActivity.kt class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) }o verride fun onCreateOptionsMenu(menu: Menu?): Boolean { //定义菜单 menuInflater.inflate(R.menu.menu,menu) //引用res/menu 目录的menu.xml 生成菜单对象menu return true }o verride fun onOptionsItemSelected(item: MenuItem): Boolean { //根据选中的菜单项进行处理 when(item.itemId){ R.id.firstItem->turnTo(FirstActivity: : class.java) R.id.secondItem->turnTo(SecondActivity: : class.java) R.id.exitItem->{ ·48· AlertDialog.Builder(this).apply{ //定义对话框 title =resources.getString(R.string.app_name) setMessage("退出应用?") setPositiveButton("确定") { _, _-> exitProcess(0) } setNegativeButton("取消",null) }.create().show() } } return super.onOptionsItemSelected(item) } private fun <T>turnTo(c: Class<T>){ //通过显式意图跳转 val intent =Intent(MainActivity@this,c) startActivity(intent) } } 在MainActivity中定义两个回调函数onCreateOptionsMenu()和onOptionsItemSelected()分 别用于创建菜单和菜单项选择的处理。 onCreateOptionsMenu()函数采用menuInflater.inflate(R.menu.menu,menu)函数引 用菜单资源res/menu/menu.xml文件渲染菜单对象menu。 onOptionsItemSelected()函数对菜单项的id值进行判断并处理,并调用了通用的显式 Intent跳转的turnTo()函数,使得从当前的MainActivity跳转到指定类的Activity中。在 退出菜单项的处理中增加了AlertDialog对话框的显示,如果选中“确定”动作,则执行 exitProcess(0)退出整个移动应用。注意,在setPositiveButton处理部分使用了两个“_”表 示匿名的参数。如果在Lambda表达式中传递的参数没有使用,可以用“_”来替换。 turnTo()函数是自定义的函数,在这个函数中使用了泛型,即定义了类型变量T,将类 型变量传递给Class<T>中,表示各种Class类型。在函数体中,通过创建显式Intent对 象,并启动startActivity()函数实现从MainActivity跳转到指定类的Activity中。如果调 用的是turnTo(FirstActivity::class.java)函数,则等价执行的代码如下: //创建从当前的MainActivity 跳转到FirstActivity 活动的意图对象 val intent =Intent(MainActivity@this, FirstActivity: : class.java) //在当前Activity 中启动Intent MainActivity@this.startActivity(intent) 其中,MainActivity@this表示MainActivity的当前对象。 (3)定义其他的Activity。移动模块中还定义了FirstActivity和SecondActivity,它们 的布局文件分别为activity_first.xml和activity_second.xml,均保存在res/layout目录下。 它们都定义了一个文本标签中显示字符串。布局文件类似activity_main.xml布局文件的 定义。这两个Activity的任务就是引用各自的布局文件显示界面内容。 ·49· //模块Ch03_02 第一个活动的定义文件FirstActivity.kt class FirstActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_first) } } //模块Ch03_02 第二个活动的定义文件SecondActivity.kt class SecondActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_second) } } (4)配置清单文件中注册Activity,代码如下: <!--模块Ch03_02 配置清单文件AndroidManifest.xml --> <?xml version="1.0" encoding="utf-8"?> <manifest xmlns: android="http://schemas.android.com/apk/res/android" package="chenyi.book.android.ch03_02"> <application .......> <!--其他配置省略--> <!--配置SecondActivity --> <activity android: name=".SecondActivity" android: label="@string/title_second_activity"/> <!--配置FirstActivity --> <activity android: name=".FirstActivity" android: label="@string/title_first_activity"/> <!--配置MainActivity --> <activity android: name=".MainActivity" android: exported="true"> <intent-filter> <action android: name="android.intent.action.MAIN" /> <category android: name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> 在配置清单文件中,配置了FirstActivity、SecondActivity 和MainActivity。其中, MainActivity作为入口,在程序加载时首先启动和显示,如图3-2所示。 运行结果中菜单项对应的图标在下列菜单列表并没有显示。有一个编程技巧,可以将 所有的菜单项向下降一个等级。将res/menu目录下的menu.xml修改成如下形式: <?xml version="1.0" encoding="utf-8"?> <menu xmlns: android="http://schemas.android.com/apk/res/android"> <item android: id="@+id/menu" android: title="Menu"> <menu> <item android: id="@+id/firstItem" android: icon="@android: drawable/arrow_up_float" ·50· 图3-2 菜单处理的运行结果 android: title="@string/title_first_activity" /> <item android: id="@+id/secondItem" android: icon="@android: drawable/arrow_down_float" android: title="@string/title_second_activity" /> <item android: id="@+id/exitItem" android: icon="@android: drawable/ic_lock_power_off" android: title="@string/title_exit_app" /> </menu> </item> </menu> 这时,可以出现显示二级菜单,菜单的图标可以显示。 3.2.2 隐式Intent 隐式Intent没有明确地指定要启动哪个Activity,而是通过Android系统分析Intent, 并根据分析结果来启动对应的Activity。执行过程如图3-3所示,即某个ActivityA 通过 startActivity()函数操作某个隐式Intent调用另外的Activity。Android系统先分析应用配 置清单AndroidManifest.xml中声明的IntentFilter内容,再搜索应用中与Intent相匹配的 IntentFilter内容,若匹配成功后,则Android系统调用匹配的Activity,用ActivityB 的 onCreate()函数启动新的Activity,实现从ActivityA 跳转到ActivityB。 一般情况下,隐式Intent需要在配置清单文件AndroidManifest.xml中指定Intent Filter的action、category和data3个属性。Android系统会对IntentFilter的3个属性进 行分析和匹配,以搜索对应Activity或其他组件。 (1)action:表示该Intent所要完成的一个抽象的Activity。 (2)category:用于为action增加额外的附加类别信息。 (3)data:向action提供操作的数据。 ·51· 图3-3 通过隐式Intent通过Android系统启动活动 但是这3个属性并不是必须全部配置,可以根据具体的需要进行组合配置。例如: <activity android: name=".CustomedActivity" android: exported="true"> <intent-filter> <action android: name="chenyi.book.android.ch03_03.ACTION" /> <!--自定义动作的名称--> <category android: name="android.intent.category.DEFAULT" /> <!--指定默认类别--> <category android: name="chenyi.book.android.ch03_03.MyCategory" /> <!--指定自定义的类别--> </intent-filter> </activity> 例3-2 使用隐式Intent的应用实例。MainActivity可以分别调用自定义的Activity 和拨打电话。 在本应用定义两个活动: 一个是自定义的CustomedActivity;另一个表示 MainActivity,用于移动应用的入口。在MainActivity中提供了两个按钮,单击后都可以实 现调用CustomedActivity和拨打电话的功能。在系统的配置清单文件中,首先配置并注册 这些Activity。 (1)在配置清单AndroidManifest.xml中注册Activity,代码如下: <?xml version="1.0" encoding="utf-8"?> <manifest xmlns: android="http://schemas.android.com/apk/res/android" package="chenyi.book.android.ch03_03"> <application.......> <!--其他配置省略--> <activity android: name=".CustomedActivity" android: exported="true"><!--注册CustomedActivity --> <intent-filter> <action android: name="chenyi.book.android.ch03_03.ACTION" /> <category android: name="android.intent.category.DEFAULT" /> <category android: name="chenyi.book.android.ch03_03.MyCategory" /> </intent-filter> </activity> <activity android: name=".MainActivity" android: exported="true"><!--注册MainActivity --> <intent-filter> ·52· <action android: name="android.intent.action.MAIN" /> <category android: name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> (2)定义CustomedActivity,代码如下: <!--模块Ch03_03 定义CustomedActivity 对应的布局activity_customed.xml --> <?xml version="1.0" encoding="utf-8"?> <ImageView android: id="@+id/imageView" 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" app: srcCompat="@mipmap/ic_launcher" tools: context=".CustomedActivity" /> 在上述的布局中定义了ImageView 组件,用于显示图片,通过app:srcCompat直接引 用图片res/mipmap目录下的ic_launcher图片资源。 //模块Ch03_03 定义CustomedActivity 的文件CustomedActivity.kt class CustomedActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_customed) } } (3)定义MainActivity,代码如下: <!--模块Ch03_03 定义MainActivity 对应的布局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"> <Button android: id="@+id/firstBtn" android: layout_width="wrap_content" android: layout_height="wrap_content" android: text="@string/title_customed_activity" app: layout_constraintBottom_toBottomOf="parent" app: layout_constraintHorizontal_bias="0.544" app: layout_constraintLeft_toLeftOf="parent" app: layout_constraintRight_toRightOf="parent" app: layout_constraintTop_toTopOf="parent" ·53· app: layout_constraintVertical_bias="0.345" /> <Button android: id="@+id/secondBtn" android: layout_width="wrap_content" android: layout_height="wrap_content" android: text="@string/title_action_view" app: layout_constraintBottom_toBottomOf="parent" app: layout_constraintHorizontal_bias="0.524" app: layout_constraintLeft_toLeftOf="parent" app: layout_constraintRight_toRightOf="parent" app: layout_constraintTop_toTopOf="parent" app: layout_constraintVertical_bias="0.461" /> </androidx.constraintlayout.widget.ConstraintLayout> 在MainActivity布局文件中定义了两个按钮,目的是实现不同Activity的跳转的 接口。 //模块Ch03_03 定义MainActivity 的文件MainActivity.kt class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val firstBtn: Button =findViewById(R.id.firstBtn) firstBtn.setOnClickListener { val intent1 =Intent("chenyi.book.android.ch03_03.ACTION") intent1.addCategory("chenyi.book.android.ch03_03.MyCategory") MainActivity@this.startActivity(intent1) } val secondBtn: Button =findViewById(R.id.secondBtn) secondBtn.setOnClickListener { val intent2 =Intent(Intent.ACTION_DIAL) startActivity(intent2) } } } 运行结果如图3-4所示。 MainActivity是移动应用中第一个调用和加载的界面,在配置清单文件AndroidManifest. xml中也注册了CustomedActivity,在activity元素定义了下级标签intent-filter,指定了 CustomedActivity的动作名称,并添加了两个类别:android.intent.category.DEFAULT 和 chenyi.book.android.ch03_03.MyCategory。android.intent.category.DEFAULT 是默认的 类别。在调用startActivity()函数时,Android 系统会自动将android.intent.category. DEFAULT添加到Intent中。chenyi.book.android.ch03_03.MyCategory是自定义的类别, 因此,需要调用addCategory()函数将这个自定义的类别添加到Intent中,即单击第一个按 钮会通过隐式intent1跳转到CustomedActivity。 注意,第二个按钮单击实现拨打电话。Intent.ACTION_DIAL内置的系统动作,对应 android.intent.action.DIAL。Android系统中定义常见的内置动作如表3-1所示。 ·54· 动作 ACTION_ANSWER ACTION_CALL ACTION_DIAL ACTION_EDIT ACTION_VIEW ACTION_SENDTO ACTION_SEND ACTION_SEARCH 图3- 4 隐式Intent实例的运行结果 表3- 1 Intent内置的动作 说明 处理来电 拨打电话,使用Intnt的号码,需要设置adod.rmsin.PHONE enripeioCALL_ 调用拨打电话的程序,使用Intent的号码,没有直接打出 编辑Intent中提供的数据 查看动作,可以浏览网页、短信、地图路肩规划,根据Intent的数据类型和数据值 决定 发送短信、电子邮件 发送信息、电子邮件 搜索,通过Intent的数据类型和数据判断搜索的动作 调用Intnt的stDta()函数可以设置Itnt的数据,用Koln的表达是inetdt eeanetitn.aa; 调用Intent的setType()函数可以设置Intent数据的类型;调用setDataAndType()函数可 以来设置数据和数据的类型。 3.3 Activity之间的数据传递 Activity之间往往需要传递数据。数据的传递可以借助Intent来实现。Intent提供了 两种实现数据传递的方式:一种是通过Intent的putExtra传递数据;另一种是通过Bundle 传递多类型数据。 ·55· 3.3.1 传递常见数据 在这里常见的数据类型包括Int(整型)、Short(短整型)、Long(长整型)、Float(单精度 实型)、Double(双精度实型)、数组、ArrayList(数组列表)等多种类型。在Activity之间,可 以将这些类型的数据进行传递。 首先,Intent提供了一系列的putExtra()函数实现数据传递。在数据的发送方,通过 putExtra()函数种指定键值对,然后启动startActivity将意图从当前的Activity跳转到 intent指定的活动,代码如下: val intent =Intent(this,OtherActivity: : class.java) //定义跳转到OtherActivity 的Intent intent.putExtra("intValue",23) //配置传递数据的键值对 startActivity(intent) //根据Intent 启动Activity 数据接收方(设为OtherActivity)调用getIntent()函数获得启动该Activity的Intent, 代码如下: val intent=getIntent() 在Kotlin中也可以直接表示成 val intent=intent 然后,根据数据的关键字,调用接收数据类型对应get×××Extra()函数来获得对应的取 值。例如: val received=intent.getIntExtra("intValue") //根据关键字intValue 获取对应的Int 数据 第二种方式结合Bundle数据包和putExtra()函数实现数据的传递。具体的执行过程 与第一种方式类似。数据的发送方创建Bundle数据包,代码如下: val bundle: Bundle=Bundle() 然后,调用Bundle对象的对应的put×××函数,设置不同类型的数据,例如: bundle.putInt("bundleIntValue",1000) bundle.putString("bundleStringValue","来自MainActivity 的问候!") 在发送方通过调用Intent的putExtra()函数,设置要传递的数据,再调用startActivity 实现Activity的跳转和数据的发送,代码如下: val intent=Intent(this,OtherActivity: : class.java) intent.putExtra(bundle) startActivity(intent) 在数据的接收方,例如OtherActivity有两种方式接收数据:一种是通过Bundle对象 的对应的get×××()函数来获得;另一种是直接通过调用启动该Activity的Intent的get ·56· ×××Extra()函数,根据关键字获得对应的取值,代码如下: val intent =intent val bundle: Bundle? =intent.extras //根据Bundle 来获得数据 val intValue =bundle?.getInt("bundleIntValue",0) //0 为默认值,取值失败时会赋值 val strValue =bundle?.getString ("bundleStringValue") //或根据Intent 直接获得数据 val intValue =intent.getIntExtra("bundleIntValue") val strValue =intent.getStringExtra("bundleStringValue") 例3-3 常见数据类型数据的传递的应用实例MainActivity分别使用两种不同的方式 传递数据到其他两个Activity,代码如下: <!--模块Ch03_04 定义MainActivity 的布局文件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"> <Button android: id="@+id/firstBtn" android: layout_width="wrap_content" android: layout_height="wrap_content" android: text="@string/title_send_int" app: layout_constraintBottom_toBottomOf="parent" app: layout_constraintEnd_toEndOf="parent" app: layout_constraintLeft_toLeftOf="parent" app: layout_constraintRight_toRightOf="parent" app: layout_constraintStart_toStartOf="parent" app: layout_constraintTop_toTopOf="parent" app: layout_constraintVertical_bias="0.327" /> <Button android: id="@+id/secondBtn" android: layout_width="wrap_content" android: layout_height="wrap_content" android: text="@string/title_send_array" app: layout_constraintBottom_toBottomOf="parent" app: layout_constraintEnd_toEndOf="parent" app: layout_constraintLeft_toLeftOf="parent" app: layout_constraintRight_toRightOf="parent" app: layout_constraintStart_toStartOf="parent" app: layout_constraintTop_toTopOf="parent" app: layout_constraintVertical_bias="0.453" /> </androidx.constraintlayout.widget.ConstraintLayout> 在MainActivity的布局文件定义了两个按钮,并分别为这两个按钮定义了相应的处理 动作,代码如下: ·57· //模块Ch03_04 定义数据的发送方的主活动MainActivity.kt class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val firstBtn: Button =findViewById(R.id.firstBtn) val secondBtn: Button =findViewById(R.id.secondBtn) firstBtn.setOnClickListener { val intent =Intent(MainActivity@this,FirstActivity::class.java) intent.putExtra("intValue",999) intent.putExtra("strValue","来自MainActivity 的问候") val strArr =arrayOf("Kotlin","Java","Scala") intent.putExtra("arrValue",strArr) startActivity(intent) } secondBtn.setOnClickListener { val bundle =Bundle() bundle.putInt("bundleIntValue",1000) bundle.putString("bundleStringValue","来自MainActivity 的问候!!") val intArr =IntArray(10) for(i in intArr.indices) intArr[i]=i+1 bundle.putIntArray("bundleIntArray",intArr) val intent =Intent(MainActivity@this,SecondActivity::class.java) intent.putExtras(bundle) startActivity(intent) } } } 作为数据的发送方,MainActivity提供了两种数据发送的方法。第一种方法是直接利 用Intent的put×××Extra()函数将数据传递出去;第二种方法是将数据通过键值对放置 在Bundle对象中,然后利用Intent将Bundle数据包整体传递出去。有数据发送,必须有一 个接收方来接收数据,否则发送数据没有任何意义。在此处,定义了FirstActivity 和 SecondActivity分别接收数据,代码如下: //模块Ch03_04 定义数据接收方FirstActivity.kt class FirstActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_first) val intent =intent val receivedInt =intent.getIntExtra("intValue",0) //获得整数,0 为默认值 val receivedStr =intent.getStringExtra("strValue") //获得字符串 val receivedStrArr =intent.getStringArrayExtra("arrValue") //获得字符串数组 val result =StringBuilder() ·58· result.append("接收: ${receivedInt}\n${receivedStr}\n${Arrays. toString(receivedStrArr)}") Toast.makeText(this,"$result",Toast.LENGTH_LONG).show() } } FristActivity中接收的数据是利用Intent调用get×××Extra()函数,通过关键字来 获得对应的数据。在代码中,接收了字符串数组,调用java.util.Arrays的toString()函数, 将数组转换成字符串的表达形式。另外,在代码中使用了Toast信息提示框。通过调用 Toast.makeText()函数可以将文本在信息提示中显示出来,代码如下: //模块Ch03_04 定义数据接收方SecondActivity.kt class SecondActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_second) val intent =intent //获得发送数据的意图 val bundle: Bundle? =intent.extras //获得Bundle 对象 val receivedIntValue =bundle?.getInt("bundleIntValue") //接收整数数据 val receivedStrValue =intent.getStringExtra("bundleStringValue") //接收字符串数据 val receivedIntArr =bundle?.getIntArray("bundleIntArray") //接收整型数组数据 val result=StringBuilder() result.append("接收的数据: ${receivedIntValue}\n${receivedStrValue}\n" ${Arrays.toString(receivedIntArr)}") Toast.makeText(this,"$result",Toast.LENGTH_LONG).show() //显示数据 } } SecondActivity接收数据的方式有两种:一种方式是启动该活动的意图获得Bundle 对象,然后从Bundle对象中获得关键字对应的数据;另一种方式是通过调用Intent的 get×××Extra()函数根据传递数据的关键字获得对应的数据,如图3-5所示。 3.3.2 Serializable对象的传递 在Activity之间传递数据有一种特殊情况,就是传递自定义类的对象或者自定义类的 对象数组或集合类型中包含自定义类的对象。需要对这些自定义类进行可序列化处理。可 序列化处理,就是将对象处理成可以传递或存储的状态,使得对象可以从临时瞬间状态保存 持久状态,即存储到文件、数据库等中。要在Java中实现可序列化,就需要用类实现java. io.Serializable接口。作为基于JVM 的语言,Kotlin也需要实现java.io.Serializable接口,这 个类的对象就可以进行序列化的处理。形式如下: class DataType: Serializable{ … } ·59· 图3-5 活动传递数据实例的运行结果 通过Serializable传递对象的过程,如图3-6所示。发送方将数据序列化成字节流发 送,在接收方,将字节流重组成对应的对象。 图3-6 Serializable方式传递对象的原理 要在Activity之间发送Serializable对象,仍需要通过Intent实现。Intent通过调用 putExtra()函数,设置“关键字”和对象之间的键值对,形式如下: val intent =Intent(this,OtherActivity…class.java) val object: Type =… intent.putExtra("object",object) startActivity(intent) Activity的接收方OtherActivity在接收数据时,首先启动该活动的意图,再通过意图 的getSerializableExtra()函数通过关键字来获得对应的Serializable对象,最后利用as操作 符转换成对应的对象。实现了根据接收的状态数据反序列化生成对应的对象。形式如下: val intent =intent val object =intent.getSerializableExtra("object") as Type 例3-4 在Activity之间传递Serializable对象应用实例。实现将学生的信息(学号、姓 名、出生日期)从一个Activity传递到另外一个Activity。 ·60· (1)定义实体类Student,代码如下: //模块Ch03_05 定义数据实体类Student data class Student(val no: String,val name: String, val birthday: LocalDate): Serializable (2)定义数据的发送方MainActivity,代码如下: <!--模块Ch03_05 MainActivity 对应的布局文件activity_main.xml --> <?xml version="1.0" encoding="utf-8"?> <Button android: id="@+id/sendBtn" xmlns: android="http://schemas.android.com/apk/res/android" xmlns: tools="http://schemas.android.com/tools" android: layout_width="wrap_content" android: layout_height="wrap_content" android: layout_gravity="center" android: text="@string/title_send_object" android: textSize="30sp" tools: context=".MainActivity"/> MainActivity发送Student对象到FirstActivity。调用Intent的putExtra()函数设置 发送数据的键值对。关键字是student,取值是Student 对象student。然后通过 startActivity(intent)启动Intent封装活动,实现数据通过Intent发送FirstActivity,代码 如下: //模块Ch03_05 发送方的主活动定义MainActivity.kt class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val sendBtn: Button =findViewById(R.id.sendBtn) sendBtn.setOnClickListener { val student: Student=Student("60012322","张三", LocalDate.of(2008,2,12)) //定义对象 val intent =Intent(this,FirstActivity::class.java) //定义Intent intent.putExtra("student",student) //Intent 设置键值对 MainActivity@this.startActivity(intent) //启动Activity } } } (3)定义数据接收方的FirstActivity,代码如下: //模块Ch03_05 接收方定义FirstActivity.kt class FirstActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_first) val intent =intent val receivedStu: Student =intent.getSerializableExtra("student") as Student //将类型强制转换成Student ·61· Toast.makeText(this,"${receivedStu.toString()}",Toast.LENGTH_LONG).show() } } 接收方的FirstActivity 用于接收对象。由于FirstActivity 中Intent 对象的 getSerializableExtra()函数会返回一个Serializable类型的对象,因此需要对接收对象进行 处理,即结合as操作符执行类型转换。形式如下: val receivedStu: Student =intent.getSerializableExtra("student") as Student 通过这种方式获得接收的对象后,整个实例的运行结果如图3-7所示。 图3-7 活动传递Serializable对象的运行结果 3.3.3 Parcelable对象的传递 通过Java的java.io.Serializable方式传递对象,将对象整体数据序列化处理,将对象转 换可传递状态进行发送。接收方将接收的状态数据重新反序列化重新生成对象。由于这种 处理方式效率低下,因此Android提供了自带的Parcelable(打包)方式实现对象的传递。 以Parcelable方式传递对象的原理是,通过一套机制,将一个完整的对象进行分解,分 解后的每一部分数据都属于Intent支持的数据类型。可以将分解后的可序列化的数据写 入一个共享内存中,其他进程通过Parcel从这块共享内存中读出字节流,并反序列化成对 象,通过这种方式来实现传递对象的功能,如图3-8所示。 要实现Parcelable方式传递对象,需要定义的类必须实现Parcelable接口。必须对其 中的方法进行覆盖重写,具体如下。 (1)writeToParcel():实现将对象的数据进行分解写入。 ·62· 图3-8 Parcelable方式传递对象 (2)describeContent():内容接口的描述,默认只需要返回为0。 (3)Parcelable.Creator:需要定义静态内部对象实现Parcelable.Createor接口的匿名 类。该接口提供了两个函数:一个是createFromParcelable()函数实现从Parcel容器中读 取传递数据值,读取数据的顺序与写入数据的顺序必须保持一致,然后封装成Parcel对象 返回逻辑层,另一个是newArray()函数创建一个指定类型指定长度的数组,供外部类反序 列化本类数组使用。下面定义了实现Parcelable接口的Student类,代码如下: data class Student(val no: String,val name: String,val birthday: LocalDate): Parcelable { constructor(parcel: Parcel): this(parcel.readString()!!,parcel.readString()!!, parcel.readSerializable() as LocalDate) override fun writeToParcel(parcel: Parcel, flags: Int) { //分解数据 parcel.writeString(no) parcel.writeString(name) parcel.writeSerializable(birthday) } override fun describeContents(): Int =0 companion object CREATOR : Parcelable.Creator<Student>{ //重组生成对象 override fun createFromParcel(parcel: Parcel): Student { return Student(parcel) } override fun newArray(size: Int): Array<Student?>{ return arrayOfNulls(size) } } } 但是,这种表示方式非常复杂,在Kotlin中提供了一种更简单的表示方式,即利用@ Parcelize标注实体类。形式如下: @Parcelize data class Student(val no: String,val name: String, val birthday: LocalDate): Parcelable ·63·