说明: 本文是郭霖《第一行代码-第3版》的读书笔记
Activity是包含用户界面的组件,主要用于和用户交互,一个应用程序中可以包含一个或多个Activity
3.2 基本用法创建一个Empty Activity,之后自己来添加Activity
手动创建Activity
project模式下,在app/src/main/java/com.example.projectname目录下新建一个Activity。
项目的任何Activity都应该重写onCreate()方法。
创建和加载布局
Android程序设计讲究逻辑和视图分离。最好每一个Activity都能对应一个布局。
如果没有在创建Activity时勾选Generate Layout File,则需要我们在res目录下手动添加一个layout文件。
在app/src/main/res/目录下创建layout文件夹,添加layout resource file,这是一个xml文件。
在onCreate()内通过setContentView()方法来传入布局文件,参数为布局文件的ID,如:
setContentView(R.layout.first_layout)
项目中添加的任何资源文件都会在R文件中生成一个相应资源的对应ID
在AndroidManifest文件中注册
所有的Activity都需要在AndroidManifest.xml中注册才能生效。但实际上Studio已经帮我们注册了FirstActivity。
除此之外,程序要想正常允许还需要配置主Activity。即在标签内添加
此时程序就可以正常运行了。
Toast
Toast是Android提供的一种提醒方式,可以将一些短小的信息通知给用户。自动消失、不占屏幕空间
现在想点击button触发toast,首先需要找到定义在Layout文件中的Button,可以根据ID来找:
findViewById()获取布局文件中控件的实例,但是在Kotlin中,会在app/buid.gradle文件头部引入一个kotlin-android-extensions插件,可以根据布局文件中控件的ID自动生成一个具有相同名称的变量,因此可以不需要调用findViewById()
var button1: Button = findViewById(R.id.button1)
//定义button的按下监听事件button1.setOnClickListener{ //使用Toast,Toast.makeText()静态方法创建一个Toast对象,再调用show()显示 //Toast.makeText()需传入三个参数:1.Context,2.显示的文本内容, 3.Toast显示的时长(内置有Toast.LENGTH_SHORT和LONG) //Activity本身就是一个Content对象,所以这里可以直接传入this Toast.makeText(this, "You clicked button1", Toast.LENGTH_SHORT).show()}
在Activity中使用Menu
首先在res文件夹下新建一个menu文件夹,新建一个Android resource file,这是一个xml文件,选择menu类型。然后可以在menu文件中添加item。可以定义item的id和title,item相当于是一个菜单项,id是它唯一的标识
<?xml version="1.0" encoding="utf-8"?>
如何添加这个menu呢?
需要在主Activity中重写onCreateOptionsMenu()方法。这里可以使用Ctrl+O快捷键得到重写下拉表
override fun onCreateOptionsMenu(menu: Menu?): Boolean { // menuInflater实际上来自getMenuInflater(),是kotlin中的语法糖 // 调用inflate()方法,给当前Activity创建菜单,第一个参数表示要使用哪一个menu文件,第二个参数指定这一个菜单项添加到哪一个 // Menu对象中,这里直接添加到传入的这个menu中menuInflater.inflate(R.menu.menu, menu)return true //true表示允许这个menu显示}
接下来可以定义菜单响应事件,
override fun onOptionsItemSelected(item: MenuItem): Boolean { // 判断传入的是哪一个Itemwhen(item.itemId) { // 如果item.itemId == R.id.add_itemR.id.add_item -> Toast.makeText(this, "You clicked Add", Toast.LENGTH_SHORT).show()R.id.remove_item -> Toast.makeText(this, "You clicked Remove", Toast.LENGTH_SHORT).show()}return true}
销毁一个Activity
只需要按一下Back键就可以销毁当前Activity, 代码实现是调用finish()方法
3.3 使用Intent在Activity之间穿梭如果有多个Activity,如何从主Activity跳到另外一个Activity呢?
再建一个Activity和相应的布局文件,Android Studio已经帮我们在AndroidManifest.xml中注册了这个新建的Activity。
另外由于这个Activity不是主Activity,也不需要配置
至此,这个新的Activity已创建完成,接下来是启动它。
Intent
1.显示Intent
Intent有多个构造函数的重载,其中一个是Intent(Context packageContext, Class<?> cls),构建出intent后,再用startActivity()方法接收intent参数,来启动Activity
// 这里的SecondActivity::class.java相当于Java中的SecondActivity::classval intent = Intent(this, SecondActivity::class.java)startActivity(intent)
2.隐式Intent
隐式Intent通过指定action和category等信息,交由系统分析启动哪个Activity。
只有和 // AndroidManifest.xml 每个Intent只能指定一个action,但能指定多个category 此时如果我们在intent上添加一个categoty val intent = Intent("com.example.activitytest.ACTION_START")// 添加categoryintent.addCategory("com.example.activitytest.MY_CATEGORY")startActivity(intent) 此时程序就相应不了了,因为我们的SecondActivity并没有这个Category 我感觉是 更多隐式Intent的用法 使用隐式的Intent,不仅可以启动本程序的Activity,也可以启动其他程序的Activity,使多个程序之间的功能共享成为可能。 // 指定Intent的Action,这是Android系统内置的Actionval intent = Intent(Intent.ACTION_VIEW)// 通过Uri.Parse()方法将网址字符串解析为Uri对象,再调用Intent的setData()方法将Uri对象传进去intent.data = Uri.parse("https://www.baidu.com/") //指定协议是httpsstartActivity(intent) //start的是哪个activity? 也可以创建一个新的Activity,指定其Action为android.intent.action.VIEW,以及data来响应这个intent: 除了https协议外,还可以指定其他协议,比如geo为地理位置,tel为拨打电话。 // 调用系统拨打电话界面val intent = Intent(Intent.ACTION_DIAL)intent.data = Uri.parse("tel:1008") //指定协议是telstartActivity(intent) //start的是哪个activity? 如何向下一个Activity传递数据 思路:Intent中提供了一系列putExtra()方法的重载,因此可以把数据先暂存在Intent中,启动另一个activity后,再将数据取出。 //传递数据val data = "Hello, SecondActivity!"val intent = Intent(this, SecondActivity::class.java)intent.putExtra("extra_data", data)//------------------------------------------//另一个Activity中取出数据val extraData = intent.getStringExtra("extra_data") // intent其实是getIntent(),即启动第二个Activity的IntentLog.d("SecondActivity","extra data is $extraData") 返回数据给上一个Activity // 首先需要在启动Activity的时候选择这个方法,这样在此Activity销毁的时候才能够回调onActivityResult方法val intent = Intent(this, SecondActivity::class.java)startActivityForResult(intent, 1) // 第二个参数是请求码,是唯一值就可以//------------------------------------------// 点击button2让其返回数据val intent = Intent() //创建一个空的Intent保存数据intent.putExtra("data_return", "Hello FirstActivity") //将要返回的数据保存到intent中setResult(Activity.RESULT_OK, intent) //这个方法专门用于向上一个Activity返回数据,第二个参数就是把intent给传递回去finish() //销毁当前Activity, 从而回调onActivityResult()方法//------------------------------------------// 第一个Activity中重写onActivityResult()方法override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) // 首先根据请求码判断数据来源,再根据resultCode判断结果是否处理正确 when (requestCode) { 1 -> if (resultCode == Activity.RESULT_OK) { val returnData = data?.getStringExtra("data_return") //这里不能用intent来获取数据 Log.d("FirstActivity", "returned data is $returnData") } }} 上述的onActivityResult()方法是在activity销毁的时候才调用的,为了点击Back键也能保存要返回的数据,需要重写onBackPressed()方法 override fun onBackPressed() { val intent = Intent() intent.putExtra("data_return", "Hello FirstActivity") setResult(Activity.RESULT_OK, intent)//finish() super.onBackPressed() //如果自己不调用这个父类的处理方法,你就要自己写 finish()} 返回栈 Android的Activity是可以层叠的,放在了栈中,启动新的Activity的时候入栈,Back的时候出栈,栈顶的Activity显示在当前界面。 Activity状态 Activity的生存期 小结: 可以在onStart()内加载用户可见的资源,在onStop()内销毁用户可见的资源可以在onResume()内加载和用户交互的资源,在onStop()内销毁和用户交互的资源 感觉生命周期还是挺重要的,可以帮助管理好资源 使用对话框Activity Activity被回收了怎么办 当Activity进入Stop状态时是有可能被系统回收的,此时再返回该Activity,临时数据和状态就会被销毁。此时执行的是onCreate()方法。我们可以重写onSaveInstanceState()方法,此方法在Activity在销毁前一定会被调用。 // 接受Bundle类型的参数,提供了一系列的方法用来保存数据override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) val tempData = "Something you just typed." val intData = 1024 outState.putString("data_key", tempData) outState.putInt("data2_key", intData)} 如何取出数据呢?如果内存被回收,则返回Activity时会调用onCreate()方法,此时就可以在onCreate()中加载保存的数据 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) Log.d(tag, "onCreate() called") // 加载保存的数据 if (savedInstanceState != null) { val tempData = savedInstanceState.getString("data_key") val intData = savedInstanceState.getInt("data2_key") Log.d(tag, "tempData is $tempData and $intData") }} 这里需要注意两点: Intent可以和Bundle一起用来传递数据,将数据存入Bundle里,再将Bundle传入Intent中,传给下一个Activity。当手机的屏幕发生旋转时,Activity也会经历一个重新创建的过程,虽然也可用上述的onSaveInstanceState()方法来保存数据,但是有更优雅的解决方案。 3.5 Activity的启动模式 Activity有四种启动模式。 使用taskId查看当前返回栈的id,本质上是getTaskID()方法的语法糖 启动模式在AndroidManifest.xml中更改 再让所有的Activity继承这个Basic类而非之前的AppCompatActivity 这样每当我们进入一个界面时就会打印当前界面对应的Activity类名。 随时随地退出程序 当我们打开了多个Activity,需要一个一个Back才能退出,这样会显得很麻烦。可以通过写一个单例类来管理这些Activity并提供一个finishAll()方法。 object ActivityCollector { private val activities = ArrayList() fun addActivity(activity: Activity) { activities.add(activity) } fun removeActivities(activity: Activity) { activities.remove(activity) } fun finishAll() { for (activity in activities) { // 判断Activity是否正在销毁 if (!activity.isFinishing) { activity.finish() } } activities.clear() }} 接下来修改之前写的BasicActivity,在onCreate()和onDestroy()方法内添加单例类的add和remove: open class BasicActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Log.d("baseActivity", javaClass.simpleName) ActivityCollector.addActivity(this) } override fun onDestroy() { super.onDestroy() ActivityCollector.removeActivities(this) }} 现在想随时退出程序,只需要调用ActivityCollector单例类的finnishAll()方法即可。 button3.setOnClickListener { //点击button3直接全部退出 ActivityCollector.finishAll() // 为保证程序安全退出,可以杀掉当前进程,killProcess()只能用于杀掉当前程序的进程。 android.os.Process.killProcess(android.os.Process.myPid())} 启动Activity的最佳写法 前面学过,通过构建Intent再调用startActicity()或者startActivityForResult()来启动Activity,数据传递也可以借用Intent和Bundle完成。 问题:当某个Activity不是由你开发的,但启动它需要传参,怎么传参呢? 要么询问作者,要么自己看代码。为了方便别人,可以给每个Activity启动时都编写启动方法如下: // 在你写的这个Activity类中加入// companion object 使得其内的方法变为静态方法companion object { fun actionStart(context: Context, data1: String, data2: String) { val intent = Intent(context, SecondActivity::class.java) intent.putExtra("param1", data1) intent.putExtra("param2", data2) context.startActivity(intent) }}// 那在启动这个Activity的时候可以简化成如下形式,则传参一目了然。SecondActivity.actionStart(this, "data1", "data2")