完整代码Gitee地址:kotlin-demo: 15天Kotlin学习计划
第二天学习内容代码:Chapter2
目录
知识点1:手动创建Activity
Activity是最容易吸引用户的地方,它是一种可以包含用户界面的组件,主要用于和用户进行交互。一个应用程序中可以包含零个或多个Activity。
1,手动创建Activity,继承AppCompatActivity,初始化onCreate方法;
package com.example.kotlin_demo
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
/**
* Created by: PeaceJay
* Created date: 2021/10/27
* Description: 第二天 探究Activity
* 知识点1:手动创建Activity
*/
class Learn2Activity : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_learn2)
}
}
2,创建布局文件activity_learn2,并在onCreate里使用setContentView()方法引用;
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>
创建布局文件activity_main,并增加点击按钮控件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity"
android:padding="10dp">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/chapter2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Chapter2" />
</LinearLayout>
3,新建的Activity都需要在AndroidManifest.xml里引用;
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.kotlin_demo">
<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.Kotlindemo">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 第二天 探究Activity -->
<activity android:name=".Learn2Activity"/>
</application>
</manifest>
4,MainActivity布局增加点击按钮,并跳转Learn2Activity;5,设置themes.xml文件,DarkActionBar样式改为NoActionBar;
5,设置themes.xml文件,DarkActionBar样式改为NoActionBar;
<style name="Theme.Kotlindemo" parent="Theme.MaterialComponents.DayNight.NoActionBar">
运行效果:
知识点2:Activity的基本用法
①Toast的使用
Toast是Android系统提供的一种非常好的提醒方式,来看下Kotlin如何使用;
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/but_toast"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Toast"/>
第一个参数是Context,也就是Toast要求的上下文,由于Activity本身就是一个Context对象,因此这里直接传入this即可。第二个参数是Toast显示的文本内容。第三个参数是Toast显示的时长,有两个内置常量可以选择:Toast.LENGTH_SHORT和Toast.LENGTH_LONG。
//①Toast的使用
val butToast: Button = findViewById(R.id.but_toast)
butToast.setOnClickListener {
Toast.makeText(this, "Toast提示", Toast.LENGTH_SHORT).show()
}
②Menu的使用
由于menu是在ActionBar上的,这里需要把样式修改回来:
<style name="Theme.Kotlindemo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
首先在res目录下新建一个menu文件夹,在menu文件夹→New→Menu resource file,新建main_menu.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/add_item"
android:title="新增用户"/>
<item
android:id="@+id/delete_item"
android:title="删除用户"/>
<item
android:id="@+id/select_item"
android:title="查看日志"/>
</menu>
在Learn2Activity里重写onCreateOptionsMenu()方法
//创建菜单
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
//menuInflater使用了语法糖,它实际上是调用了父类的getMenuInflater()方法
//再调用它的inflate()方法,就可以给当前Activity创建菜单了
menuInflater.inflate(R.menu.main_menu, menu)
return true
}
仅仅让菜单显示出来是不够的,还要再定义菜单响应事件 ,在Learn2Activity里重写onOptionsItemSelected()方法
//菜单响应事件
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.add_item -> Toast.makeText(this, "增加用户", Toast.LENGTH_SHORT).show()
R.id.delete_item -> Toast.makeText(this, "删除用户", Toast.LENGTH_SHORT).show()
R.id.select_item -> Toast.makeText(this, "查看日志", Toast.LENGTH_SHORT).show()
}
return true
}
我们来看一下运行效果:
③隐式Intent的用法
<androidx.appcompat.widget.AppCompatButton
android:visibility="gone"
android:id="@+id/but_intent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="隐式Intent"/>
val butIntent: Button = findViewById(R.id.but_intent)
butIntent.setOnClickListener {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse("https://blog.csdn.net/qq_30998053/category_11436257.html")
startActivity(intent)
}
④向下一个Activity传递数据
Intent中提供了一系列putExtra()方法的重载,可以把我们想要传递的数据暂存在Intent中,在启动另一个Activity后,只需要把这些数据从Intent中取出就可以了。在MainActivity跳转方法上增加putExtra方法,示例如下:
button.setOnClickListener {
startActivity(Intent(this,Learn2Activity::class.java)
.putExtra("dataName","chapter2"))
}
在Learn2Activity中接收,代码如下:
val dataName = intent.getStringExtra("dataName")
Log.i("TAG", "dataName = $dataName")
输出结果为:
⑤返回数据给上一个Activity
在A界面MainActivity,使用startActivityForResult()方法,
startActivityForResult(
Intent(this, Learn2Activity::class.java)
.putExtra("dataName", "chapter2"), 9527
)
在B界面Learn2Activity增加一个返回按钮:
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/but_finish"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="返回数据"/>
val butFinish: Button = findViewById(R.id.but_finish)
butFinish.setOnClickListener {
val intent = Intent()
intent.putExtra("data_return","这是9527返回值")
setResult(RESULT_OK,intent)
finish()
}
但这样写,物理返回按键无法监听到,在onBackPressed返回方法中处理
override fun onBackPressed() {
super.onBackPressed()
val intent = Intent()
intent.putExtra("data_return","这是9527返回值")
setResult(RESULT_OK,intent)
}
最后在A界面MainActivity,并使用onActivityResult方法接收:
//第一个参数requestCode,即我们在启动Activity时传入的请求码;
//第二个参数resultCode,即我们在返回数据时传入的处理结果;
//第三个参数data,即携带着返回数据的Intent
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
9527 ->
if (resultCode == RESULT_OK) {
val data = data?.getStringExtra("data_return")
Log.i("TAG", "onActivityResult: $data")
}
}
}
打印结果为:
可以看到,我们在MainActivity中成功得到了从Learn2Activity传递过来的数据
⑥Activity被回收了怎么办
当一个Activity进入了停止状态,是有可能被系统回收的。打个比方,MainActivity中如果有一个文本输入框,现在你输入了一段文字,然后启动Learn2Activity,这时MainActivity由于系统内存不足被回收掉,过了一会你又点击了Back键回到MainActivity,你会发现刚刚输入的文字都没了,因为MainActivity被重新创建了。
Activity中还提供了一个onSaveInstanceState()回调方法,这个方法可以保证在Activity被回收之前一定会被调用,代码示例:
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
val userName = "PeaceJay"
val userPwd = "123456"
outState.putString("userName", userName)
outState.putString("userPwd", userPwd)
}
数据是已经保存下来了,onCreate()方法其实也有一个Bundle类型的参数,那里进行恢复
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//恢复数据
if (savedInstanceState != null) {
val userName = savedInstanceState.getString("userName")
val userPwd = savedInstanceState.getString("userPwd")
Log.i("TAG", "恢复数据:userName=$userName userPwd=$userPwd")
}
}
知识点3:随时随地退出程序
新建一个单例类ActivityCollector作为Activity的集合,代码如下所示:
object ActivityCollector {
private val activityList = ArrayList<Activity>()
//添加activity到集合
fun addActivity(activity: Activity) {
activityList.add(activity)
}
//从集合里面移除
fun removeActivity(activity: Activity){
activityList.remove(activity)
}
//关闭所有activity
fun finishAll(){
for (activity in activityList){
if (!activity.isFinishing){
activity.finish()
}
}
activityList.clear()
}
}
提供了一个addActivity()方法,用于向ArrayList中添加Activity;提供了一个removeActivity()方法,用于从ArrayList中移除Activity;最后提供了一个finishAll()方法,用于将ArrayList中存储的Activity全部销毁 。
接下来创建BaseActivity,并添加与移除activity:
open class BaseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.i("BaseActivity", javaClass.simpleName)
//添加activity
ActivityCollector.addActivity(this)
}
override fun onDestroy() {
super.onDestroy()
//销毁activity
ActivityCollector.removeActivity(this)
}
}
把MainActivity、Learn2Activity继承改为 BaseActivity。
例如在Learn2Activity界面想通过点击按钮直接退出程序,如下所示:
val butAllFinish: Button = findViewById(R.id.but_all_finish)
butAllFinish.setOnClickListener {
ActivityCollector.finishAll()
}
知识点4:启动Activity的最佳写法
比如Learn2Activity并不是由你开发的,但现在你负责开发的部分需要启动Learn2Activity,而你却不清楚启动Learn2Activity需要传递哪些数据。添加跳转页面模型,如下所示:
companion object {
fun actionStart(context: Context, data1: String, data2: String) {
context.startActivity(Intent(context,Learn2Activity::class.java)
.putExtra("data1",data1)
.putExtra("data2",data2)
)
}
}
在这里我们使用了一个新的语法结构companion object,并在companion object中定义了一个actionStart()方法。之所以要这样写,是因为Kotlin规定,所有定义在companion object中的方法都可以使用类似于Java静态方法的形式调用。
val butStart: Button = findViewById(R.id.but_start)
butStart.setOnClickListener {
actionStart(this,"data1","data2")
}
养成一个良好的习惯,给你编写的每个Activity都添加类似的启动方法,这样不仅可以让启动Activity变得非常简单,还可以节省不少同事阅读代码时间。
Kotlin扩展:标准函数、静态方法
标准函数with、run、apply
Kotlin的标准函数指的是Standard.kt文件中定义的函数,任何Kotlin代码都可以自由地调用所有的标准函数。with标准写法:
val result = with(obj) {
//obj的上下文方法
"value"//返回值
}
①with函数
with函数接收两个参数:第一个参数可以是一个任意类型的对象,第二个参数是一个Lambda表达式,比如一个集合打印字符串,普通方法:
val list = listOf("ABC", "FGH", "HJK", "ZXC")
val buffer = StringBuffer()
for (str in list) {
buffer.append("$str ")
}
Log.i("buffer1", buffer.toString())
使用with函数打印字符串:
val with = with(StringBuffer()) {
for (str in list) {
append("$str ")
}
//转为字符串
toString()
}
Log.i("buffer2", with)
其实很好理解,首先我们给with函数的第一个参数传入了一个StringBuilder对象,那么接下来整个Lambda表达式的上下文就会是这个StringBuilder对象。于是我们在Lambda表达式中就不用再像刚才那样调用builder.append()和builder.toString()方法了,而是可以直接调用append()和toString()方法。Lambda表达式的最后一行代码会作为with函数的返回值返回,最终我们将结果打印出来。
②run函数
run函数的用法和使用场景其实和with函数是非常类似的,只是稍微做了一些语法改动而已。首先run函数通常不会直接调用,而是要在某个对象的基础上调用;其次run函数只接收一个Lambda参数,并且会在Lambda表达式中提供调用对象的上下文。run函数标注写法:
val result = obj.run {
//obj的上下文方法
"value"//返回值
}
val run = StringBuffer().run {
for (str in list) {
append("$str ")
}
//转为字符串
toString()
}
Log.i("buffer3", run)
总体来说变化非常小,只是将调用with函数并传入StringBuilder对象改成了调用StringBuilder对象的run方法,其他都没有任何区别,这两段代码最终的执行结果是完全相同的。
③apply函数
由于apply函数无法指定返回值,只能返回调用对象本身,因此这里的result实际上是一个StringBuilder对象,所以我们在最后打印的时候还要再调用它的toString()方法才行。
val result = obj.apply {
//obj的上下文方法
}
result == obj
val apply = StringBuffer().apply {
for (str in list) {
append("$str ")
}
}
Log.i("buffer4", apply.toString())
定义静态方法
静态方又叫作类方法,指的就是那种不需要创建实例就能调用的方法,所有主流的编程语言都会支持静态方法这个特性。Java中定义一个静态方法非常简单,只需要在方法上声明一个static关键字就可以了:
public class Chapter2Utils {
public static String getString(){
return "这是一个静态方法";
}
}
但是和绝大多数主流编程语言不同的是,Kotlin却极度弱化了静态方法这个概念, 想要在Kotlin中定义一个静态方法反倒不是一件容易的事。像工具类这种功能,在Kotlin中就非常推荐使用单例类的方式来实现:
object Chapter2KtUtils {
fun getString(): String {
return "这是一个静态方法"
}
}
虽然这里的doAction()方法并不是静态方法,但是我们仍然可以使用Util.doAction()的方式来调用,这就是单例类所带来的便利性。
不过,使用单例类的写法会将整个类中的所有方法全部变成类似于静态方法的调用方式,而如果我们只是希望让类中的某一个方法变成静态方法的调用方式该怎么办呢?这个时候就可以使用刚刚在最佳实践环节用到的companion object了,示例如下:
class Chapter2KtUtils {
companion object {
fun getString(): String {
return "这是一个静态方法"
}
}
}
如果确确实实需要定义真正的静态方法, Kotlin仍然提供了两种实现方式:注解和顶层方法
先来看注解,前面使用的单例类和companion object都只是在语法的形式上模仿了静态方法的调用方式,实际上它们都不是真正的静态方法。因此如果你在Java代码中以静态方法的形式去调用的话,你会发现这些方法并不存在。而如果我们给单例类或companion object中的方法加上@JvmStatic注解,那么Kotlin编译器就会将这些方法编译成真正的静态方法,如下所示:
companion object {
//加上注解@JvmStatic才算是真正的静态方法,JAVA、Kotlin都可调用
@JvmStatic
fun getString(): String {
return "这是一个静态方法"
}
}
由于getString()方法已经成为了真正的静态方法,那么现在不管是在Kotlin中还是在Java中,都可以使用Chapter2KtUtils.getString()的写法来调用了。
再来看看顶层方法,New → KotlinFile/Class,新建HelperUtlis,定义一个方法:
fun helperGetString() {
println("这是一个静态方法")
}
最后在代码中调用:
private fun extendView2() {
//在Java中定义一个静态方法非常简单,只需要在方法上声明一个static关键字就可以了
Log.i("TAG", "Chapter2Utils: " + Chapter2Utils.getString())
Log.i("TAG", "Chapter2KtUtils: " + Chapter2KtUtils.getString())
helperGetString()
}
打印结果为:
总结
本章的收获非常多,不管是理论型还是实践型的东西都涉及了,从Activity的基本用法,到启动Activity和传递数据的方式,以及Activity的启动模式,几乎已经学会了关于Activity所有重要的知识点。在最后,还学习了几种可以应用在Activity中的最佳实践技巧。另外还学习了Kotlin标准函数的用法,以及静态方法的定义方式。