基于Android Studio的用户行程记录APK开发指南(一):项目基础配置与速通Kotlin
前言
- 最近博主在unity开发独立游戏,UE5系列的相关长期教程先暂时不更新了~ 请大家多多谅解~
- 本系列教程我们来看看如何使用Android Studio去开发一个APK用于用户的实时行程记录。
- 本期我们来项目基础配置与速通
Kotlin
安装Android Studio
-
我们进入Android Studio的官网,选择下载官网链接
-
下载后点击exe运行安装,这里我们要选择安卓虚拟设备
-
根据提示一路accept和next,这边会进行Android SDK的安装
-
安装完成后我们在创建页面找到virtual Device Mannager
-
我们可以看到这里以及预先帮我们下载好了一个虚拟设备,点击绿色箭头即可运行,如下图
新建项目
-
点击新建项目,选择模板empty activity
-
简单进行命名创建即可,注意语言我们选择默认推荐的Koltlin
-
打开以后会有如下界面!请添加图片描述
可能的报错
-
值得一提的是,初次创建项目有可能会出现下述报错
-
网上的解决方法很多,一般是网络问题,我们可以重新点击左侧reload进行下载即可(我这边这样就解决了,如果不能解决的可以自行搜寻,网上教程挺多的~)
运行第一个程序
- 点击绿色箭头运行默认程序,右侧的虚拟设备输出Hello Android
Kotlin
介绍
-
Kotlin 是一种在 Java 虚拟机(JVM)上运行的静态类型编程语言,由 JetBrains 开发。Kotlin 旨在与 Java 语言完全兼容,并为其提供一些额外的特性,例如更简洁的语法、空安全、扩展函数、协程等
-
以下是一些 Kotlin 的关键特性:
- 简洁的语法:Kotlin 语法设计得非常简洁,可以减少代码量,提高开发效率。
- 空安全:Kotlin 提供了空安全特性,可以减少空指针异常。
- 扩展函数:Kotlin 允许开发者扩展现有类的功能,而不需要继承它们或使用设计模式,如装饰器。
- 协程:Kotlin 协程是一种轻量级的并发编程抽象,可以简化异步编程。
- 与 Java 的互操作性:Kotlin 与 Java 完全兼容,这意味着你可以无缝地在 Kotlin 和 Java 之间切换。
- 数据类:Kotlin 提供了数据类,可以自动为常见的数据类任务(如生成 toString、equals、hashCode 和 copy 方法)提供实现。
- 属性委托:Kotlin 支持属性委托,允许你为属性提供自定义的行为。
- 类型安全构建器:Kotlin 支持类型安全构建器模式,可以创建复杂的对象层次结构。
Kotlin基础语法
- 这是攻,这是防,这是苇名弦一郎。我们来快速速通Kotlin的基础语法。
- Kotlin 文件以
.kt
为后缀。
基础数据类型
- 整数类型
- Byte: 8 位,范围从 -128 到 127。
- Short: 16 位,范围从 -32,768 到 32,767。
- Int: 32 位,范围从 -2^31 到 2^31 - 1。
- Long: 64 位,范围从 -2^63 到 2^63 - 1。
- 浮点数类型
- Float: 32 位,单精度,带有 6-7 位有效数字。
- Double: 64 位,双精度,带有 15-16 位有效数字。
- 字符类型
- Char: 16 位的 Unicode 字符。
- 布尔类型
- Boolean: 有两个值:
true
和false
。
- Boolean: 有两个值:
- 字符串类型
- String: 一系列字符的序列。
- 数组类型
- IntArray: 存储
Int
类型的数组。 - DoubleArray: 存储
Double
类型的数组。 - Array< T>:泛型数组,可以存储任意类型。
- IntArray: 存储
- 常量
var a:Int=10
- 常量
val x=10 //系统自动推断为Int
- 字符串模板
- $ 表示一个变量名或者变量值
- $varName 表示变量值
- ${varName.fun()} 表示变量的方法返回值:
var a = 1
val s1 = "a is $a"
函数定义
fun sum(a:Int,b:Int):Int{return a+b}
fun sum(a: Int, b: Int) = a + b
fun printSum(a: Int, b: Int): Unit {
print(a + b)
}
- 可变参数函数:
vararg
关键字
fun vars(vararg v:Int){
for(vt in v){
print(vt)
}
}
- lambda表达式
val sumLambda:(Int,Int)->Int={x,y->x+y}
NULL检查机制
- 对于声明可为空的参数,在使用时要进行空判断处理
- 字段后加!!抛出空异常
- 字段后加?可不做处理返回值为 null 或配合 ?: 做空判断处理
val ages = age!!.toInt() //如果为空抛出空指针异常
val ages1 = age?.toInt() //如果为空返回null
val ages2 = age?.toInt() ?: -1
//返回值可能为空的函数
fun parseInt(str: String): Int? {
return str.toIntOrNull()
}
- 类型检测:通过下述函数可以返回传入参数的类型
fun getTypeName(obj: Any): String?
{
return obj::class.simpleName
}
- 区间:
..
的 rangeTo 函数辅以 in 和 !in 形成。
for (i in 1..4) print(i) // 输出“1234”
for (i in 1..4 step 2) print(i) // 输出“13”
for (i in 4 downTo 1 step 2) print(i) // 输出“42”
for (i in 1 until 10) { // i in [1, 10) 排除了 10
println(i)
}
条件语句
- if
- when(相当于switch)
if{}else if(){}else{}
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}
- when 如果不提供参数,所有的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}
循环语句
for(i in 1..10){]}
for (item in collection){print(item)}
while( 布尔表达式 ) { }
do { }while(布尔表达式);
- 结构化跳转表达式:
- return。默认从最直接包围它的函数或者匿名函数返回。
- break。终止最直接包围它的循环。
- continue。继续下一次最直接包围它的循环。
标签
- 在Kotlin中,标签用于在存在多个返回点的情况下指定从哪个循环或lambda表达式返回。标签可以是隐式的也可以是显式的。
- *隐式标签:
val ints = listOf(1, 0, 2, 3, 0, 4, 5) fun foo() { ints.forEach { if (it == 0)return@forEach print(it) } }
- *显式标签:
val ints = listOf(1, 0, 2, 3, 0, 4, 5) fun foo() { ints.forEach lit@ { if (it == 0) return@lit print(it) } }
类
- 类,Kotlin 中没有 new 关键字
class MyClass{}
var myClass=MyClass()
构造函数
- 值得一提的是主构造器中不能包含任何代码,初始化代码可以放在初始化代码段中,初始化代码段使用 init 关键字作为前缀。
//有参构造
class Person constructor(firstName: String, lastName: String) {}
//无参构造
class Person { }
//初始化成员变量
class Person(val firstName: String, val lastName: String) {}
//初始化代码段
class Person constructor(firstName: String) {
init {
println("FirstName is $firstName")
}
}
- 次级构造函数:Kotlin中的次构造函数可以看作是构造函数的重载。
- 需要加前缀 constructor
- 当类有主构造函数时,每个次构造函数都必须直接或间接地调用主构造函数
class Person(val name: String, val age: Int) {
// 主构造函数
// 次构造函数
constructor(name: String) : this(name, 0) {
// 初始化逻辑
}
// 另一个次构造函数
constructor() : this("John Doe") {
// 初始化逻辑
}
}
lateinit
- 在Kotlin中,
lateinit
关键字用于延迟初始化一个非空的可变属性(var
属性),这意味着你可以在构造函数之外的其他地方初始化该属性,而不是在声明属性时就必须初始化它。lateinit
关键字只适用于var
属性,不能用于val
属性,因为val
属性必须在声明时初始化或者通过构造函数初始化。
class MyService
{
lateinit var service: Any // 声明一个lateinit属性
fun doSomething()
{
// 在这里使用service属性
println(service)
}
}
getter和setter
getter
和setter
:在Kotlin中,getter和setter是用于属性访问和修改的幕后方法。它们是属性抽象化的关键组成部分,允许你以声明性的方式定义如何获取(读取)和设置(写入)属性的值。Kotlin中的属性可以自动提供getter和setter,也可以自定义它们。- 当你声明一个属性时,Kotlin会自动为你生成一个getter和setter。
- val不允许设置setter函数,因为它是只读的。
var name: String = "John" // 自动生成 getter 和 setter
- 自定义getter和setter
var score: Int
get() = field // 自定义 getter
set(value) {
if (value >= 0)
{
field = value // 自定义 setter
}else {
throw IllegalArgumentException("Score cannot be negative")
}
}
feild
field
关键字来引用属性的BackingField。BackingField是编译器为每个属性生成的幕后字段,用于存储属性的值。它只能在访问器(getter 或 setter)内部使用,并且仅在至少使用一个访问器的默认实现,或者自定义访问器通过 field 标识符引用它时才存在。
//name 属性有一个自定义的 setter,它检查传入的值是否为空。如果不为空,则使用 field 标识符将值分配给 backing field。这样,当我们尝试将空字符串分配给 name 属性时,它的值不会更改。
class Person {
var name: String = "initial value"
set(value) {
if (value.isNotEmpty()) {
field = value
}
}
}
抽象类
- 不用多说这是啥了吧,抽象是面向对象编程的特征之一,类本身,或类中的部分成员,都可以声明为abstract的
abstract class Derived : Base() {
override abstract fun f()
}
类的修饰符
- 类的修饰符包括 classModifier 和_accessModifier_:
- classModifier: 类属性修饰符,标示类本身特性。
abstract
// 抽象类final
// 类不可继承,默认属性enum
// 枚举类open
// 类可继承,类默认是final的annotation
// 注解类
- accessModifier: 访问权限修饰符
private
// 仅在同一个文件中可见protected
// 同一个文件中或子类可见public
// 所有调用的地方都可见internal
// 同一个模块中可见
- classModifier: 类属性修饰符,标示类本身特性。
open class Animal
{
protected open val protectedMember = "Protected in Base"
open fun makeSound()
{
println("Some sound")
}
}
嵌套类
- 在Kotlin中,嵌套类是指定义在另一个类内部的类。嵌套类可以访问外部类的成员,就像它们是自己的成员一样。但是,嵌套类并不持有对外部类的引用,因此它们不是Java中的内部类(inner classes)。
class OuterClass {
val outerClassValue = "I am from the outer class"
class NestedClass {
fun accessOuterClassMember() {
println(outerClassValue)
}
}
}
fun main() {
val nestedClassInstance = OuterClass.NestedClass()
nestedClassInstance.accessOuterClassMember()
}
内部类
- 在Kotlin中,内部类是指定义在另一个类内部的类。与Java中的内部类不同,Kotlin中的内部类默认不是
static
的,这意味着它们持有对外部类的引用。因此,内部类可以访问外部类的私有成员,并且可以访问外部类的实例状态。
class OuterClass {
private val outerClassValue = "I am from the outer class"
inner class InnerClass {
fun accessOuterClassMember() {
println(outerClassValue)
}
}
}
fun main() {
val outerClassInstance = OuterClass()
val innerClassInstance = outerClassInstance.InnerClass()
innerClassInstance.accessOuterClassMember()
}
- 区别:
- 访问权限:
- 嵌套类默认是静态的,不能访问外部类的私有成员,除非使用
inner
关键字。 - 对象表达式和lambda表达式可以访问定义它们的作用域内的任何变量。
- 嵌套类默认是静态的,不能访问外部类的私有成员,除非使用
- 实例化:
- 嵌套类可以通过外部类实例化。
- 对象表达式和lambda表达式是表达式的一部分,不需要显式实例化。
- 访问权限:
继承
- Kotlin 中所有类都继承该 Any 类,它是所有类的超类
- 使用冒号(
:
)来指定子类继承自哪个父类
open class ParentClass {
// ...
}
class ChildClass : ParentClass() {
// ...
}
构造
- 如果子类有主构造函数, 则基类必须在主构造函数中立即初始化。
open class Person(var name : String, var age : Int){// 基类
}
class Student(name : String, age : Int, var no : String, var score : Int) : Person(name, age) {
}
- 如果子类没有主构造函数,则必须在每一个二级构造函数中用 super 关键字初始化基类,或者在代理另一个构造函数。
open class Person(var name: String, var age: Int) { // 基类
// ...
}
// 子类没有主构造函数,必须代理到基类构造函数
class Student : Person {
var no: String
var score: Int
constructor(name: String, age: Int, no: String, score: Int) : super(name, age) {
this.no = no
this.score = score
}
// 如果子类有多个构造函数,它们都必须直接或间接地代理到基类构造函数
constructor(name: String, age: Int) : this(name, age, "", 0) {
// 在这里可以执行更多的初始化操作
}
}
重写
- 在Kotlin中,函数重写(Function Overriding)和属性重写(Property Overriding)是面向对象编程中的两个重要概念,它们允许子类继承并修改父类中的成员。
- 函数重写是指子类提供与父类中具有相同名称和签名的方法的新实现。
open class Base {
open fun display() {
println("Display from Base")
}
}
class Derived : Base() {
override fun display() {
println("Display from Derived")
}
}
- 属性重写是指子类提供与父类中具有相同名称的属性的新实现。
open class Base {
open var name: String = "Base"
}
class Derived : Base() {
override var name: String = "Derived"
}
- 重写属性的 getter 或 setter
open class Base {
open var name: String = "Base"
get() = "Base $field"
}
class Derived : Base() {
override var name: String = "Derived"
get() = "Derived $field"
set(value) {
field = "Overridden $value"
}
}
接口
- 在Kotlin中,接口(Interface)是一种引用类型,它定义了一个完全抽象的类,其中只包含抽象方法和抽象属性。
- 接口使用
interface
关键字定义,可以包含抽象方法、默认方法和静态方法。
interface MyInterface {
fun bar() // 抽象方法
fun foo() { // 默认方法
// 默认实现
}
companion object { // 静态方法
fun staticMethod() {
// 静态方法实现
}
}
}
class MyClass : MyInterface {
override fun bar() {
// 实现抽象方法
}
// foo()方法已经由接口提供了默认实现,因此可以不重写
}
- 接口可以包含属性,但它们必须是抽象的,并且不能有后端字段。
interface MyInterfaceWithProperty {
val property: Int // 抽象属性
}
class MyClassWithProperty : MyInterfaceWithProperty {
override val property: Int = 42 // 提供属性实现
}
- 值得一提的是,Kotlin 不支持传统意义上的多继承,即一个类直接继承自多个类。
interface A {
fun display() {
println("A")
}
}
interface B {
fun display() {
println("B")
}
}
// 类 C 实现了接口 A 和 B,需要处理 display 方法的冲突
class C : A, B {
override fun display() {
super<A>.display() // 调用 A 接口的 display 方法
super<B>.display() // 调用 B 接口的 display 方法
}
}
fun main() {
val c = C()
c.display() // 输出 "A" 然后 "B"
}
Kotlin 扩展
- Kotlin 可以对一个类的属性和方法进行扩展,且不需要继承或使用 Decorator 模式。(wc好东西)
扩展函数
- 扩展函数允许你向一个类添加新的方法,而无需修改该类的源代码。扩展函数的定义使用了特殊的语法:
fun
关键字后跟一个接收者类型,然后是一个点,接着是方法名和参数。
fun String.spaceToCamelCase(): String {
return this.split(" ").joinToString("") { it.capitalize() }
}
fun main() {
val s = "hello world"
println(s.spaceToCamelCase()) // 输出 "HelloWorld"
}
扩展属性
- 与扩展函数类似,扩展属性允许你向一个类添加新的属性。但与扩展函数不同,扩展属性不能有初始化器,因为它们没有后端字段,并且只能被声明为
val
。
val String.lastChar: Char
get() = this[length - 1]
fun main() {
val s = "hello"
println(s.lastChar) // 输出 "o"
}
扩展的作用域
- Kotlin 允许你将扩展定义在特定的作用域中,这样它们只在该作用域内可见。这可以通过使用
file
作用域、package
作用域或object
作用域来实现。
// 文件作用域
fun Any.println() = println(this)
// 包作用域
package com.example.util
fun String.reverse(): String = this.reversed()
// 对象作用域
object Extensions {
fun String.removeSpaces(): String = this.replace(" ", "")
伴生对象的扩展
- 可以伴生对象定义扩展函数和属性,就像为其他对象定义扩展一样。这些扩展函数和属性可以在伴生对象的作用域内被调用,就像它们是伴生对象的一部分一样。
class MyClass {
companion object {
fun companionMethod() {
println("Companion method")
}
}
}
// 为 MyClass 的伴生对象定义扩展函数
fun MyClass.Companion.companionExtension() {
println("Companion extension")
}
fun main() {
MyClass.companionMethod() // 输出 "Companion method"
MyClass.companionExtension() // 输出 "Companion extension"
}
其他扩展
- 扩展函数如何访问扩展接收者类型的成员以及定义扩展函数的类的成员:
class A {
fun display() {
println("A display")
}
}
class B {
fun display() {
println("B display")
}
}
class C {
fun display() {
println("C display")
}
// 扩展函数,扩展A类
fun A.extendedDisplay() {
display() // 调用A的display方法
this@C.display() // 调用C的display方法
}
// 扩展函数,扩展B类
fun B.extendedDisplay() {
display() // 调用B的display方法
this@C.display() // 调用C的display方法
}
}
fun main() {
val a = A()
val b = B()
val c = C()
c.extendedDisplay(a) // 输出 "A display" 和 "C display"
c.extendedDisplay(b) // 输出 "B display" 和 "C display"
}
数据类与密封类
数据类
- 在Kotlin中,数据类(Data Class)是一种特殊的类,它主要用于数据持有,通常包含多个属性,并且编译器会为它自动生成一些常用的方法,比如
toString()
、equals()
、hashCode()
、copy()
等。 - 要定义一个数据类,你只需要在类定义前加上
data
修饰符。例如:
data class User(val name: String, val age: Int)
- 在这个例子中,
User
是一个数据类,它有两个属性:name
和age
。 - 数据类的特性
- 自动生成的方法:
toString()
:生成一个包含所有属性值的字符串表示形式。equals()
:实现equals()
方法,使得两个数据类实例可以通过其属性值来判断是否相等。hashCode()
:生成一个基于所有属性值的哈希码。copy()
:生成一个拷贝当前对象的新对象,可以修改部分属性。
- 解构声明: 数据类实例可以使用解构声明来分解为变量。
- 继承: 数据类可以继承其他类,但是它本身不能被继承。
- 自动生成的方法:
data class User(val name: String, val age: Int)
fun main() {
val user1 = User("Alice", 30)
val user2 = User("Bob", 25)
val user3 = User("Alice", 30)
println(user1) // 输出 "User(name=Alice, age=30)"
println(user1.equals(user2)) // 输出 "false"
println(user1.equals(user3)) // 输出 "true"
val (name, age) = user1
println("Name: $name, Age:$age") // 输出 "Name: Alice, Age: 30"
val copiedUser = user1.copy(age = 31)
println(copiedUser) // 输出 "User(name=Alice, age=31)"
}
密封类
- 在Kotlin中,密封类(Sealed Class)是一种用来表示受限的类继承结构的工具。当你想定义一个类,并且只允许这个类有特定的子类时,密封类非常有用。密封类被用来表示当出现多个子类时,它们的数量是有限的,并且可以被明确列举出来。
- 密封类使用
sealed
修饰符来定义,并且所有直接继承自密封类的子类都必须在同一个文件中声明。
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
- 密封类的特性
- 受限的继承: 密封类的子类必须声明在同一个文件中,这限制了类的继承结构。
- when 表达式: 密封类非常适合用于
when
表达式,因为编译器知道所有可能的密封类的子类,所以不需要提供else
子句。
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
fun eval(expr: Expr): Double = when (expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
}
fun main() {
val expr: Expr = Sum(Const(1.0), Const(2.0))
println(eval(expr)) // 输出 3.0
}
泛型
- Kotlin中的泛型允许你编写可重用的代码,它允许你定义一个类、接口或方法,可以工作于任何类型。泛型通过类型参数化来提供这种灵活性,类型参数化意味着你可以在定义类、接口或方法时指定一个或多个类型参数。
泛型类
- 泛型类使用尖括号(
<>
)来定义类型参数。例如:
class Box<T>(t: T) {
var value = t
}
val boxInt = Box<Int>(1)
val boxString = Box<String>("Hello")
泛型函数
- 泛型函数使用类型参数来定义函数可以操作的数据类型。例如:
fun <T> printArray(arr: Array<T>) {
for (item in arr) {
println(item)
}
}
printArray(arrayOf(1, 2, 3, 4, 5))
printArray(arrayOf("a", "b", "c", "d", "e"))
泛型约束
- 有时候,你可能希望对类型参数设置一些限制,这可以通过泛型约束来实现。例如,如果你只想允许
Number
类型的参数,你可以这样定义:
fun <T : Number> printNumberInfo(item: T) {
println("The number is ${item.toInt()}")
}
printNumberInfo(100) // 输出 "The number is 100"
printNumberInfo(3.14) // 输出 "The number is 3"
型变
- Kotlin提供了两种型变(Variance)的概念:协变(Covariance)和逆变(Contravariance)。这些概念主要用于泛型类和接口的继承。
- 协变(Covariance):如果你有一个类
Box<out T>
,那么你可以将Box<Int>
赋值给Box<Number>
类型的变量。这意味着Box
的类型参数T
是协变的。 - 逆变(Contravariance):如果你有一个类
Box<in T>
,那么你可以将Box<Number>
赋值给Box<Int>
类型的变量。这意味着Box
的类型参数T
是逆变的。
- 协变(Covariance):如果你有一个类
class Box<out T>(t: T) // 协变
class Box<in T> // 逆变
- 型变是Kotlin泛型的一个重要特性,它允许你在不牺牲类型安全的前提下,提供更多的灵活性。
无变(Invariant)
- 如果你没有指定协变或逆变,那么泛型类型参数是无变的(Invariant)。这意味着你不能将子类型赋值给父类型的泛型类型参数,也不能将父类型赋值给子类型的泛型类型参数。
class Box<T>(t: T) // 无变
fun main() {
val boxInt: Box<Number> = Box<Int>(1) // 错误:Box<Int> 不是 Box<Number> 的子类型
// ...
}
星号投影(Star-Projections)
- 当你不知道泛型类的确切类型参数时,可以使用星号投影(
*
)来表示。例如,如果你有一个List
类型的变量,但你不知道它具体是List<Int>
还是List<String>
,你可以使用List<*>
来表示。
fun printList(list: List<*>) {
for (item in list) {
println(item)
}
}
枚举类
- 在Kotlin中,枚举(Enum)是一种特殊的类,用于定义一组固定的常量。枚举常量在运行时是单例的,这意味着每个枚举值都是唯一的,并且可以保证在整个程序中的一致性。
- 下面是几种常用的用法
enum class Color { RED, GREEN, BLUE }
enum class Color(val r: Int, val g: Int, val b: Int) { RED(255, 0, 0), GREEN(0, 255, 0), BLUE(0, 0, 255); fun rgb() = (r * 256 + g) * 256 + b }
- 迭代和范围
for (color in Color.values()) {
println(color)
}
println(Color.valueOf("RED"))
if (color in enumValues<Color>()) {
// ...
}
对象表达式和匿名函数
对象表达式
- 对象表达式用于创建匿名内部类的实例。当你需要创建一个实现了某个接口或继承了某个类的匿名类实例时,可以使用对象表达式。对象表达式可以访问作用域中的变量,并且可以定义多个方法。
interface MyInterface { fun myMethod() }
val myObject = object : MyInterface {
override fun myMethod() {
// 实现方法
}
}
myObject.myMethod() // 输出 "Hello from object expression!"
匿名函数
- 匿名函数是一种没有名称的函数,它可以被用作值传递。匿名函数通常用于表示简短的功能或传递函数作为参数。
val myLambda: (Int, Int) -> Int = { x, y -> x + y }
委托
- 在 Kotlin 中,委托(Delegation)是一种设计模式,它允许你将一个对象的一部分功能委托给另一个对象来实现。这种模式在 Kotlin 中是通过语言特性直接支持的,使得代码更加简洁和易于维护。
委托的基本概念
- Kotlin 中的委托通常涉及到两个角色:
- 委托对象(Delegate):包含你想要委托的功能的对象。
- 被委托对象(Delegatee):将部分功能委托给委托对象的对象。
委托的类型
- Kotlin 中有几种不同类型的委托:
- 属性委托(Property Delegation): 属性委托允许你将属性的 getter 和 setter 实现委托给另一个对象。Kotlin 提供了标准委托,如
Lazy
、Observable
和Vetoable
,以及自定义委托。
import kotlin.properties.Delegates
class Example {
var p: String by Delegates.notNull()
}
在这个例子中,p
属性的 getter 和 setter 实现被委托给了 Delegates.notNull()
。
2. 方法委托(Method Delegation): 方法委托允许你将方法调用委托给另一个对象。Kotlin 中的 by
关键字用于实现方法委托。
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
class Derived(b: Base) : Base by b
在这个例子中,Derived
类将 print
方法的实现委托给了 b
对象。
3. 扩展函数委托(Extension Function Delegation): 扩展函数委托允许你将扩展函数的实现委托给另一个对象。
class Example
fun Example.print() {
println("Printed")
}
val example = Example()
example.print() // 输出 "Printed"
实战
- 我们来看看MainActivity.kt
package com.example.pathrecorderapp
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.example.pathrecorderapp.ui.theme.PathRecorderAppTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
PathRecorderAppTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}
} } }
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
PathRecorderAppTheme {
Greeting("Android")
}
}
MainActivity 类
MainActivity
类继承自 ComponentActivity
,这是一个 Jetpack Compose 兼容的 Activity 类。在这个类中,onCreate
方法被重写,用于设置应用程序的内容。
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge() // 启用边缘到边缘的屏幕显示
setContent { // 设置应用程序的内容
PathRecorderAppTheme { // 使用应用程序的主题
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting( // 调用 Greeting Composable 函数
name = "Android",
modifier = Modifier.padding(innerPadding) // 应用内边距
)
}
}
}
}
}
Greeting Composable 函数
Greeting
函数是一个 Composable 函数,用于显示一个问候信息。它接受两个参数:name
和modifier
。
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!", // 显示问候信息
modifier = modifier // 应用修饰符
)
}
GreetingPreview Composable 函数
GreetingPreview
函数是一个预览 Composable 函数,用于在 Android Studio 的预览窗口中显示 Greeting
函数的预览。
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
PathRecorderAppTheme { // 使用应用程序的主题
Greeting("Android") // 调用 Greeting Composable 函数
}
}
小结
- 下一节我们将开始书写此应用的具体逻辑,完成用户位置定位的代码设计
- 如有错误,欢迎指出,感谢大家的支持
参考
- Kotlin官网
- Kotlin 教程 | 菜鸟教程 (runoob.com)
- Kotlin文档