Bootstrap

记录一些项目中常见的概念、方法、控件

等记录多一点内容的时候会分个类

三个绑定类

1、ViewDataBinding

ViewDataBinding 是 Android Data Binding 库的一个特性,它允许开发者在不完全采用 Data Binding 模型的情况下,仍然能够从布局文件中获取绑定的视图。ViewDataBinding 主要是为了减少常见的 findViewById 调用,从而提高代码的可读性和可维护性。

当在项目中启用 Data Binding 后,每个布局文件都会生成一个对应的绑定类。这些类提供了对布局中所有视图的直接访问,而不需要手动查找视图。但是,ViewDataBinding 不包含 Data Binding 的完整功能,如表达式语言和数据观察者。

以下是 ViewDataBinding 接口的一些关键特性和用法:

  1. 绑定视图:ViewDataBinding 提供了对布局文件中所有绑定视图的访问。这意味着你可以在绑定类中直接访问布局文件中的视图,而不需要使用 findViewById。
  2. 数据上下文:ViewDataBinding 接口的实现类提供了一个 getBindingAdapter 方法,该方法返回一个 BindingAdapter 对象,它包含了与数据绑定相关的适配器和绑定器集合。
  3. 生命周期管理:ViewDataBinding 接口的实现类在 Activity 或 Fragment 的生命周期事件中自动处理绑定的创建和销毁。当 Activity 或 Fragment 被销毁时,相关的绑定也会被自动清理,这有助于避免内存泄漏。
  4. 执行表达式:ViewDataBinding 接口的实现类允许你执行数据绑定表达式,这些表达式定义了视图属性和数据源之间的绑定关系。
  5. 生成绑定类:当你在布局文件中使用 <layout> 标签启用数据绑定时,编译器会为每个布局文件生成一个绑定类。这个类名通常遵循 <layoutName>Binding 的命名约定,例如,对于 activity_main.xml,生成的绑定类可能是 ActivityMainBinding。
  6. 使用双向绑定:ViewDataBinding 支持双向绑定,这意味着你可以在布局文件中定义视图和数据源之间的绑定关系,当数据源发生变化时,视图会自动更新,反之亦然。
  7. 访问绑定类:你可以在 Activity 或 Fragment 中通过调用 DataBindingUtil.setContentView 方法来访问布局的绑定类。例如:
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        // 现在可以直接使用 binding 对象访问布局中的视图
        binding.someTextView.setText("Hello, Data Binding!");
    }
}

 

 

2、ViewBinding

ViewBinding 是 Android Jetpack 中另一个用于简化视图查找的库。与 DataBinding 不同,ViewBinding 专注于生成一个绑定类,该类包含对布局文件中所有视图的直接引用,但它不涉及数据的绑定。

ViewBinding 通过在模块的 build.gradle 文件中启用 viewBinding 选项来启用。启用后,对于每个XML布局文件,Android Studio 都会生成一个对应的绑定类。这个类包含了对布局中所有视图的引用,开发者可以通过这个类来访问这些视图,而无需调用 findViewById。

ViewBinding 减少了 findViewById 的调用,从而提高了代码的可读性和可维护性。虽然它不提供数据绑定的功能,但它仍然是一个很有用的工具,特别是对于大型项目和复杂的布局。

3、DataBinding

DataBinding 是 Android Jetpack 中的一个库,它允许开发者以声明方式将布局中的 UI 组件与数据对象绑定。这意味着当数据对象发生变化时,UI 会自动更新以反映这些变化,而无需编写额外的代码来手动更新视图。这大大减少了样板代码,并使得UI和数据之间的同步更加直接和高效。

DataBinding 通过布局文件中的 <layout> 标签启用,并允许在布局XML中直接使用变量和数据模型。开发者可以通过 @{} 语法在布局文件中引用数据模型中的属性,从而实现UI与数据的绑定。以下是 Data Binding 的一些关键特点和用法:

  • 减少代码:通过在布局文件中直接引用数据,减少在 Activity 或 Fragment 中的代码量。
  • 类型安全:在编译时检查数据类型,减少运行时错误。
  • 视图与数据分离:将视图的定义与数据的处理分离,提高代码的可维护性。
  • 支持双向绑定:可以自动同步视图和数据源之间的状态。


回调函数

1、作用

回调函数在编程中扮演着非常重要的角色,特别是在异步编程、事件驱动编程、函数式编程以及处理不确定何时完成的任务时。它们的主要作用包括:

  1. 异步处理

    • 回调函数允许程序在等待某个耗时操作(如网络请求、文件I/O、数据库查询等)完成的同时,继续执行其他任务,而不是阻塞整个程序。
    • 当异步操作完成时,回调函数会被调用,从而可以处理操作的结果。
  2. 事件响应

    • 在事件驱动的程序中,如图形用户界面(GUI)或游戏,回调函数通常用于响应用户输入、定时器事件、系统事件等。
    • 这些函数在特定事件发生时被调用,允许程序做出相应的反应。
  3. 分离关注点

    • 回调函数使得程序的不同部分可以独立开发和测试,因为它们允许将执行逻辑与调用逻辑分离。
    • 例如,一个函数可以执行数据处理,而另一个函数(回调函数)可以决定如何使用处理后的数据。
  4. 灵活性和扩展性

    • 回调函数提供了一种机制,可以根据需要动态地添加或更改功能,而无需修改核心代码。
    • 这对于构建可扩展和可维护的软件系统非常重要。
  5. 错误处理

    • 在处理可能失败的操作时,回调函数可以用于捕获和处理错误或异常,确保程序的健壮性和稳定性。
  6. 链式调用和流水线

    • 回调函数可以被链接在一起,形成一个处理流程或流水线,其中每个回调函数的输出作为下一个函数的输入。
    • 这种模式在数据处理和事件流中非常常见,例如在Node.js的事件循环中。
  7. 函数式编程

    • 在函数式编程范式中,回调函数是第一等公民,可以像其他数据类型一样被传递、存储和返回。
    • 它们被广泛用于map、filter、reduce等高阶函数中,以实现对集合的高级操作。

2、使用

在Kotlin中,回调函数通常是一种将函数作为参数传递给其他函数的方式,以便在某个事件发生时执行。通常以Lambda表达式或函数引用的形式出现,它们可以作为参数传递给其他函数,这样就可以在特定的时机调用。下面我将详细解释如何在Kotlin中定义、使用回调函数以及其执行流程。

定义回调函数:
在Kotlin中,你可以定义一个接受函数类型参数的函数,这个参数就是回调函数。函数类型可以显式地指定,也可以通过lambda表达式隐式推断。例如,定义一个接受回调函数的函数:

fun performAction(action: () -> Unit) {
    // 执行一些操作...
    action() // 调用回调函数
}

这里action参数就是一个回调函数,它没有参数也没有返回值(类型为() -> Unit)。

如果回调函数需要参数或返回值,你可以相应地修改类型,例如:

fun fetchData(callback: (String) -> Unit) {
    // 模拟数据加载
    val data = "Hello, World!"
    callback(data)
}

使用回调函数:

使用回调函数很简单,你只需要在调用接受回调函数的函数时,提供一个lambda表达式或函数引用作为参数。例如,使用上面定义的performAction函数:
 

performAction {
    println("Action performed!")
}
//或者使用fetchData函数:

fetchData { data ->
    println("Received data: $data")
}

执行流程:
当调用一个接受回调函数的函数时,实际的执行流程如下:

  1. 调用者准备回调函数:在调用目标函数时,提供一个lambda表达式或函数引用作为参数。
  2. 目标函数执行主体逻辑:目标函数执行其主要的逻辑,这可能包括异步操作、计算等。
  3. 调用回调函数:在适当的时机,目标函数会调用之前提供的回调函数,传递必要的参数(如果有)。
  4. 回调函数执行:回调函数执行其定义的逻辑,这可能是处理数据、更新UI等。
  5. 控制返回:回调函数执行完毕后,控制权返回到目标函数,目标函数继续执行(如果有剩余逻辑),最后控制权返回到最初的调用者。
示例

import kotlinx.coroutines.*

suspend fun loadData(callback: (String) -> Unit) {
    delay(1000L) // 模拟耗时操作
    val data = "Data loaded successfully"
    callback(data)
}

fun main() {
    runBlocking {
        loadData { data ->
            println("Received data: $data")
        }
    }
}


在这个例子中,loadData函数是挂起函数,它模拟了耗时操作,并在数据加载完成后调用回调函数。main函数使用runBlocking来运行协程,确保loadData函数及其回调函数在协程作用域中执行。


abstract

abstract 关键字用于定义抽象类和抽象方法。以下是 abstract 的一些关键点:

1、抽象类:

  • 定义:使用 abstract 关键字定义的类称为抽象类。抽象类不能被实例化,但可以被继承。
  • 目的:抽象类通常用作基类,为子类提供公共的接口和/或实现。


2、抽象方法:

  • 定义:在抽象类中,使用 abstract 关键字定义的方法称为抽象方法。抽象方法没有实现,必须在子类中被重写。
  • 目的:抽象方法强制子类提供具体实现,确保所有子类都遵循相同的协议。


3、关键特点:

  • 不能实例化:不能创建抽象类的实例,但可以创建其子类的实例。
  • 包含抽象方法:抽象类可以包含抽象方法,这些方法没有实现,必须由子类实现。
  • 包含具体方法:除了抽象方法,抽象类也可以包含有具体实现的方法。
  • 被子类继承:子类通过继承抽象类,必须实现所有的抽象方法。
  • 用于共享代码:抽象类用于共享代码和定义一些通用行为。


4、使用场景

  • 定义协议:当你想要定义一个协议或一组行为,但不希望提供具体实现时。
  • 代码复用:当你想要在多个子类之间共享代码时。
  • 多态性:抽象类允许你通过抽象方法实现多态性。


protected open

在Kotlin中,protected open 是用来修饰类成员(如方法、属性或构造函数)的关键字组合,具体含义如下:

  1. protected:表示该成员在当前类及其子类中可见。如果一个类成员被声明为 protected,则它不能被同一个包中的非子类访问,但可以在子类中访问。
  2. open:表示该成员可以被子类重写(override)。如果一个类成员被声明为 open,则子类可以提供一个不同的实现。

使用场景

  1. 基类中的方法:当你希望一个方法在子类中可以有不同的实现,但同时在基类中提供一个默认实现时,可以使用 protected open。
  2. 基类中的属性:如果你希望子类可以访问并修改基类中的属性,但同时限制其他类访问,可以使用 protected open。
  3. 构造函数:在某些情况下,你可能希望子类能够调用基类的构造函数,但不希望外部直接通过基类的构造函数创建子类的实例。这时,可以将基类的构造函数声明为 protected。


companion object

在 Kotlin 中,companion object 是一种特殊的非匿名内部类,它属于其外围类的一部分。companion object 主要用于实现与 Java 静态方法和字段类似的功能,但同时也具有面向对象的特性。companion object 可以定义在类内部,但不是该类的实例成员。这意味着你可以在不创建类的实例的情况下访问 companion object 中的属性方法,就像它们是类的静态成员一样。

companion object 是一种特殊的对象声明,它与类或接口关联,并提供静态功能。以下是 companion object 的一些关键特点和用法:

特点:

  • 单例:companion object 在其宿主类的整个生命周期内是单例的,即只有一个实例。
  • 静态访问:可以通过类名直接访问 companion object 的成员,而不需要创建类的实例。
  • 访问宿主类的成员:companion object 可以访问其宿主类的所有成员,包括私有成员。
  • 常用于工厂方法:companion object 常用于实现工厂方法,提供创建类实例的静态方法。


service

在 Android 开发中,Service 是一个运行在后台的组件,它允许应用在没有用户界面的情况下执行长时间运行的操作,甚至是在屏幕关闭或用户切换到其他应用时。服务对于执行后台任务(如音乐播放、文件下载、网络通信等)非常有用。

以下是一些关于 Android Service 的关键点:

1、生命周期:

  1. onCreate():服务创建时调用。
  2. onStartCommand():每次服务启动时调用,可以通过 startService() 方法启动服务。
  3. onBind():当其他组件请求绑定到服务时调用。
  4. onUnbind():当所有客户端都解绑时调用。
  5. onDestroy():服务被销毁时调用。

2、类型:

  1. START_STICKY:如果系统杀死了服务,系统会在内存足够时重启服务。
  2. START_NOT_STICKY:如果系统杀死了服务,不会重启服务。
  3. START_REDELIVER_INTENT:如果服务被系统杀死,系统会尝试重新传递最后一次的 Intent 给服务。

3、绑定服务:
服务可以通过 bindService() 方法绑定到其他组件(如 Activity 或 BroadcastReceiver)。绑定服务允许客户端与服务进行交互,发送请求和接收结果。

4、通信:

使用 Intent 在服务和客户端之间传递请求和结果。
使用 Messenger 或 Binder 在服务和客户端之间进行更复杂的通信。
5、前台服务:
如果服务需要长时间运行,建议将其作为前台服务运行,这样可以减少被系统杀死的可能性。前台服务需要在 onCreate() 或 onStartCommand() 方法中调用 startForeground()。

6、服务的启动:

使用 startService(Intent) 启动服务,服务会一直运行,直到调用 stopService(Intent) 或服务自己调用 stopSelf()。
使用 bindService(Intent, ServiceConnection, int) 绑定服务,服务会与客户端建立连接,并在客户端解绑时停止。
7、服务的停止:

服务可以通过调用 stopSelf() 或 stopSelfResult(int) 停止自己。
客户端可以通过调用 unbindService(ServiceConnection) 解绑服务,服务在没有绑定的客户端时可能会停止。
8、服务的权限:
服务可能需要特定的权限才能运行,这些权限需要在应用的 AndroidManifest.xml 文件中声明。


Builder

"Builder"模式是一种创建对象的设计模式,尤其适用于需要构建复杂对象的场合,其中对象的构建过程可能需要多个步骤,并且可能需要不同的配置选项。Builder模式的主要目的是将构建过程与最终产品的表示分离,这样可以更灵活地构造对象,而不会使构造代码过于臃肿或难以阅读。

Builder 模式的组成:

Builder模式通常包括以下几个部分:

  1. Product:这是最终构建出来的复杂对象。
  2. Builder:定义了构建Product的各个部分的抽象方法,通常以一系列的构建步骤来实现。
  3. Concrete Builders:实现Builder接口,提供具体的构建步骤和方法。
  4. Director:使用一个Builder对象来构建Product的实例。Director决定了构建过程的顺序。

使用场景:

Builder模式在以下场景中特别有用:

  • 构建的对象有多个可选部分或配置选项。
  • 构建的过程需要多个步骤,而且这些步骤的顺序可能会影响最终的产品。
  • 构建的对象的创建过程很复杂,需要解耦合创建逻辑和使用逻辑。

示例:

在Kotlin中,可以使用Builder模式来构建一个相对复杂的对象,例如一个详细的订单。假设有一个电子商务应用,需要创建包含各种细节的订单,如顾客信息、商品列表、支付方式和送货地址等。下面是一个使用Builder模式的示例,演示如何创建这样一个订单:

data class Customer(val name: String, val email: String)

data class Product(val id: String, val name: String, val price: Double)

data class ShippingAddress(val street: String, val city: String, val postalCode: String)

//sealed class通常用于创建类型安全的枚举或表示具有固定子类集的抽象基类。
//使用sealed class可以强制子类必须在同一个文件中定义,或者在编译时确保所有可能的子类都被覆盖
sealed class PaymentMethod {
    data class CreditCard(val number: String, val expirationMonth: Int, val expirationYear: Int) : PaymentMethod()
    data class PayPal(val email: String) : PaymentMethod()
}

data class Order(
    val customer: Customer,
    val products: List<Product>,
    val paymentMethod: PaymentMethod,
    val shippingAddress: ShippingAddress
) {
    class Builder {
        private var customer: Customer? = null
        private val products = mutableListOf<Product>()
        private var paymentMethod: PaymentMethod? = null
        private var shippingAddress: ShippingAddress? = null

        fun customer(customer: Customer) = apply { this.customer = customer }
        fun product(product: Product) = apply { products.add(product) }
        fun paymentMethod(paymentMethod: PaymentMethod) = apply { this.paymentMethod = paymentMethod }
        fun shippingAddress(shippingAddress: ShippingAddress) = apply { this.shippingAddress = shippingAddress }

        fun build(): Order {
            checkNotNull(customer) { "Customer must be set." }
            checkNotNull(paymentMethod) { "Payment method must be set." }
            checkNotNull(shippingAddress) { "Shipping address must be set." }
            return Order(customer!!, products.toList(), paymentMethod!!, shippingAddress!!)
        }
    }
}

fun main() {
    val customer = Customer("Alice", "[email protected]")
    val product1 = Product("123", "iPhone 12 Pro Max", 999.99)
    val product2 = Product("456", "AirPods Pro", 249.99)
    val shippingAddress = ShippingAddress("123 Apple Street", "Cupertino", "95014")

    val order = Order.Builder()
        .customer(customer)
        .product(product1)
        .product(product2)
        .paymentMethod(PaymentMethod.CreditCard("1234567890123456", 12, 2025))
        .shippingAddress(shippingAddress)
        .build()

    println(order)
}

//在这个例子中,Order类包含了一些必要的属性,如顾客信息、商品列表、支付方式和送货地址。
//Order.Builder类提供了构建Order实例的方法。你可以多次调用product方法来添加多个商品到订单中,
//使用customer、paymentMethod和shippingAddress方法来设定订单的其他部分。
//
//最后,在main函数中,创建了一个Customer实例,两个Product实例,
//一个ShippingAddress实例,并使用Order.Builder构建了一个完整的Order实例。
//通过这种方式,可以清晰地看到订单是如何一步步构建起来的,同时保持了代码的整洁和可读性。


示例代码:

使用 AlertDialog.Builder:

val builder = AlertDialog.Builder(this)
builder.setTitle("提示")
builder.setMessage("确定要退出应用吗?")
builder.setPositiveButton("确定") { dialog, _ ->
    // 执行退出操作
    finish() // 关闭Activity
}
builder.setNegativeButton("取消") { dialog, _ ->
    dialog.dismiss()
}
builder.create().show()


//    在这个示例中:
//
//    使用 AlertDialog.Builder 创建对话框构建器。
//    设置对话框的标题和消息。
//    为“确定”和“取消”按钮设置点击事件。
//    使用 create() 方法创建 AlertDialog 实例。
//    使用 show() 方法显示对话框。

 使用 Notification.Builder:

val builder = Notification.Builder(applicationContext)
    .setContentTitle("标题")
    .setContentText("这是一条通知")
    .setSmallIcon(R.drawable.ic_notification)
    .setContentIntent(pendingIntent)

val notification = builder.build()
NotificationManagerCompat.from(applicationContext)
    .notify(NOTIFICATION_ID, notification)


//    在这个示例中:
//
//    使用 Notification.Builder 创建通知构建器。
//    设置通知的标题、文本、小图标和内容意图。
//    使用 build() 方法构建 Notification 对象。
//    使用 NotificationManagerCompat 发送通知。

 


Dialog

在软件开发中,尤其是桌面和移动应用开发中,对话框(Dialog)是一种常见的用户界面元素,用于在不离开当前屏幕或窗口的情况下向用户提供信息或请求输入。对话框通常浮在应用的其余部分之上,要求用户对其进行响应,例如通过点击按钮确认或取消某个操作。

对话框的类型

对话框可以分为多种类型,每种类型都有其特定的用途:

  1. 模态对话框:阻止用户与应用的其他部分交互,直到对话框被关闭。
  2. 非模态对话框:允许用户在对话框打开时与应用的其他部分交互。
  3. 警告对话框:用于显示警告信息,通常包含一个“确定”按钮。
  4. 确认对话框:询问用户是否确认执行某项操作,通常包含“确定”和“取消”按钮。
  5. 输入对话框:请求用户输入信息,通常包含一个文本框和“确定”按钮。
  6. 信息对话框:仅显示信息给用户,通常只有一个“确定”或“关闭”按钮。

示例:

AlertDialog.Builder(this)
    .setTitle("确认删除?")
    .setMessage("您确定要删除此条目吗?")
    .setPositiveButton("确定") { _, _ ->
        // 用户点击了“确定”按钮,执行删除操作
        deleteEntry()
    }
    .setNegativeButton("取消") { dialog, _ ->
        // 用户点击了“取消”按钮,关闭对话框
        dialog.cancel()
    }
    .setIcon(android.R.drawable.ic_dialog_alert)
    .show()


Provider

在安卓(Android)开发中,"Provider"主要指的是“Content Provider”。Content Provider 是 Android 平台上用于存储和检索数据的一种机制,特别设计用于在多个应用程序之间共享数据。它是 Android 四大组件之一,其它三大组件分别是 Activity、Service 和 BroadcastReceiver。

1、Content Provider 的用途
Content Provider 被设计来提供跨应用的数据访问,这使得一个应用可以提供数据给其它应用使用,同时也可以让一个应用访问另一个应用的数据。例如,联系人列表、日历事件、浏览器书签等通常都是通过 Content Provider 来管理和共享的。

2、Content Provider 的工作原理
Content Provider 通过一组标准的 CRUD(创建、读取、更新、删除)接口来操作数据。数据通常是以类似于 SQL 数据库的形式存储的,虽然实际上可以使用任何类型的持久化存储。

3、主要组成部分
Content Resolver:这是 Android 系统提供的一个接口,应用程序通过它来访问 Content Provider。
Content URI:用于唯一标识数据的 URI。这些 URI 通常是形如 content://com.example.provider/table 的形式。
MIME Type:定义了数据的类型,如 vnd.android.cursor.dir/vnd.com.example.provider.table 或 vnd.android.cursor.item/vnd.com.example.provider.table,分别表示目录(多行记录)或单个项目(单行记录)。
4、使用 Content Provider 的步骤

  1. 创建 Content Provider:继承自 ContentProvider 类,实现必须的方法,如 query(), insert(), update(), delete(), 和 getType()。
  2. 声明 Content Provider:在 AndroidManifest.xml 文件中声明你的 Content Provider。
  3. 定义 URI:创建 URI 用于识别数据,通常以 content:// 开头,后跟包名和路径。
  4. 使用 Content Resolver:在需要访问 Content Provider 的应用中,使用 getContentResolver() 方法来执行 CRUD 操作。


bean

在Android项目中,数据模型类(有时被称为“bean”类,因为它们遵循了JavaBean的编码约定)通常会被放在java源代码目录下的某个包内,比如model、entities或data这样的文件夹下。这些类往往用来封装数据,比如用户信息、商品信息、订单详情等,它们通常包含私有属性和对应的公共getter和setter方法。

  


RadioGroup / RadioButton

RadioGroup是Android中用于包含一组互斥的RadioButton的布局容器。

当一个RadioButton被选中时,同一RadioGroup内的其他RadioButton都会被自动取消选中,确保任何时候只有一项被选中。这在需要用户从多个选项中选择单一答案的场景中非常有用,例如性别选择、问卷调查中的单选题等。

例如:

<RadioGroup
    android:id="@+id/radio_group"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <RadioButton
        android:id="@+id/radio_male"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Male" />

    <RadioButton
        android:id="@+id/radio_female"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Female" />

    <RadioButton
        android:id="@+id/radio_other"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Other" />

</RadioGroup>


post方法

这是View类提供的一个方法,用于将Runnable对象加入到View的事件队列中,然后在下一次重绘(repaint)之前执行Runnable中的代码。这是因为View的绘制和事件处理都是在UI线程中完成的,post方法确保了任何对UI的操作都在UI线程中执行,避免了潜在的线程安全问题和应用崩溃。

这种方法确保了在复杂的异步操作之后,对UI的更新能够正确地在主线程中执行,不会导致应用无响应或者崩溃


Handler

在Android开发中,Handler是用于线程间通信的重要机制之一,尤其是在主线程(UI线程)与非UI线程(如网络请求、耗时计算等后台任务所在的线程)之间的消息传递。Handler的设计目的是解决异步任务与UI更新之间的同步问题,因为Android中对UI的操作必须在主线程中执行。

Handler的基本原理

  1. Handler的工作原理基于消息循环(Message Loop),它包括以下几个主要组件:
  2. Looper:每个线程都有一个Looper对象,负责管理该线程的消息队列(Message Queue)。Looper通常在主线程中通过Looper.prepare()和Looper.loop()初始化和启动。
  3. Message Queue:Looper维护的消息队列,其中存储了所有等待处理的消息。
  4. Message:消息对象,包含what、arg1、arg2、obj等字段,用于携带数据。
  5. Handler:用于发送消息到消息队列,并且在适当的时候处理消息。

使用Handler的基本步骤:

  1. 创建Handler:在需要接收消息的线程中创建Handler实例,通常是在UI线程中创建。
  2. 发送消息:在其他线程中,使用sendMessage()或sendEmptyMessage()方法将消息发送到消息队列。
  3. 处理消息:当消息到达消息队列的头部时,Looper会调用Handler的handleMessage()方法来处理消息。
     
import android.os.Handler
import android.os.Looper
import android.os.Message
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

    private lateinit var handler: Handler

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 初始化Handler
        initHandler()

        // 模拟按钮点击事件,触发后台任务
        simulateButtonClicked()
    }

    private fun initHandler() {
        handler = object : Handler(Looper.getMainLooper()) {
            override fun handleMessage(msg: Message) {
                super.handleMessage(msg)
                when (msg.what) {
                    1 -> {
                        // 更新UI,例如设置TextView的文本
                        textView.text = msg.obj as String
                    }
                }
            }
        }
    }

    private fun simulateButtonClicked() {
        Thread {
            // 执行耗时操作,例如网络请求
            val result = performNetworkRequest()

            // 在主线程中更新UI
            handler.obtainMessage(1, result).sendToTarget()
        }.start()
    }

    private fun performNetworkRequest(): String {
        // 模拟网络请求,这里只是简单地等待几秒钟
        Thread.sleep(2000)
        return "Hello from network"
    }
}


SpannableString

SpannableString是Android中用于处理富文本(rich text)的一个强大工具,它允许对字符串的不同部分应用不同的样式,如字体大小、颜色、下划线、链接等。SpannableString是一个可变的字符序列,它继承自Spannable,后者是一个接口,定义了如何在文本中插入和管理 "span"。在SpannableString中,“span”是一个标记,可以被附加到文本的某个区域,以控制该区域的显示方式。例如,你可以创建一个TextAppearanceSpan来改变文本的外观,或者使用URLSpan来将文本转换为一个超链接。

1、创建SpannableString:
创建一个SpannableString很简单,只需要传入你想要格式化的文本即可:

val spannableString = SpannableString("Hello, World!")

2、应用Span:
一旦有了SpannableString,你就可以使用setSpan方法来添加各种spans:

val spannableString = SpannableString("Hello, World!")

// 创建一个TextAppearanceSpan
val textAppearanceSpan = TextAppearanceSpan(context, R.style.TextStyleBold)

// 将span应用于文本的一部分
spannableString.setSpan(
    textAppearanceSpan,
    0, // 开始索引
    5, // 结束索引(不包括)
    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE // span的范围规则
)

在这个例子中,"Hello"这部分文本将被设置为粗体。

3、Span的范围规则:

在setSpan方法中,最后一个参数决定了span如何与其他span共存。常见的规则有:

  1. SPAN_EXCLUSIVE_EXCLUSIVE:span不会覆盖其他span的边界。
  2. SPAN_INCLUSIVE_EXCLUSIVE:span的开始边界是包容的,但结束边界不是。
  3. SPAN_EXCLUSIVE_INCLUSIVE:反之亦然,开始边界不是包容的,但结束边界是。
  4. SPAN_INCLUSIVE_INCLUSIVE:span的两个边界都是包容的。

4、使用SpannableString

一旦设置了spans,你就可以将SpannableString对象设置为TextView的文本:

textView.text = spannableString

这样,TextView就会按照你设置的spans来渲染文本了。

SpannableString的强大之处在于它允许你在一个字符串中结合多种格式化选项,而无需分割文本或使用多个TextView。这对于创建复杂和动态的用户界面非常有用,特别是在需要响应用户输入或动态数据的情况下。


BitmapFactory.Options

BitmapFactory是 Android SDK 中的一个类,它提供了静态方法来从各种来源(如文件、字节流、资源等)解码位图(图像)。这些方法通常用于加载图像数据并将其转换为Bitmap对象,以便在应用程序中显示或进一步处理。

以下是一些BitmapFactory的常用方法:

  1. decodeResource(Resources res, int id):这个方法用于从应用资源中解码一个图像。你需要传入资源管理器和图像资源的ID。
  2. decodeStream(InputStream is):从输入流中解码一个位图。这通常用于从网络下载的图像数据。
  3. decodeFile(String pathName):从文件系统中的指定路径解码一个位图。
  4. decodeByteArray(byte[ ] data, int offset, int length):从字节数组中解码一个位图,通常用于从内存中读取图像数据。
  5. decodeResource(Resources res, int id, BitmapFactory.Options opts):这个方法与decodeResource类似,但它允许你传递一个Options对象,该对象可以控制解码过程,例如只解码图像的尺寸而不需要完整像素数据。

BitmapFactory.Options是一个辅助类,用于控制位图的解码过程。它的一些关键属性包括:

inSampleSize:一个整数值,用于指定解码时的下采样比例,这可以显著减少内存消耗,尤其是当图像很大时。
inJustDecodeBounds:一个布尔值,当设置为true时,BitmapFactory会跳过实际像素的解码,仅计算出图像的边界尺寸(宽度和高度),而实际上不加载图像像素数据到内存中。这用于节省内存,尤其是在处理大图像时。。

在Kotlin中使用BitmapFactory加载和显示图像的例子如下:

假设我们有一个ImageView,我们需要从文件系统中加载一个图像并显示出来,同时考虑到内存使用和性能,我们可以使用BitmapFactory.Options来优化加载过程。

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 图片文件的路径
        val imagePath = "/path/to/your/image.jpg"

        // 使用BitmapFactory.Options来预加载图像尺寸,避免OOM
        val options = BitmapFactory.Options()
        options.inJustDecodeBounds = true
        BitmapFactory.decodeFile(imagePath, options)

        // 计算合适的inSampleSize
        options.inSampleSize = calculateInSampleSize(options, 100, 100)

        // 实际加载图像
        options.inJustDecodeBounds = false
        val bitmap = BitmapFactory.decodeFile(imagePath, options)

        // 显示图像
        imageView.setImageBitmap(bitmap)
    }

    /**
     * 计算合适的inSampleSize,以避免内存溢出,同时保证图像质量
     */
    private fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
        // 获取原始图像尺寸
        val height = options.outHeight
        val width = options.outWidth
        var inSampleSize = 1

        if (height > reqHeight || width > reqWidth) {
            // 计算最接近的inSampleSize,以保持图像长宽比
            val halfHeight = height / 2
            val halfWidth = width / 2

            while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
                inSampleSize *= 2
            }
        }

        return inSampleSize
    }
}


ActivityResultContracts

ActivityResultContracts是Android Jetpack中Activity Result API的一部分,它简化了在Android应用程序中处理活动结果的过程。在使用传统方法时,你必须在onActivityResult方法中处理返回的结果,同时还要记住每个启动的活动请求码,这可能导致代码混乱且容易出错。ActivityResultContracts提供了一种更简洁、类型安全的方式,让你可以更清晰地管理从一个活动到另一个活动的结果传递。

使用ActivityResultContracts的步骤:

  1. 导入依赖:首先,确保你的项目依赖于Jetpack的Activity或Fragment库,它们包含了ActivityResult API。
  2. 注册Launcher:在你的Activity或Fragment中,使用registerForActivityResult方法注册一个ActivityResultLauncher。这通常涉及创建一个ActivityResultContracts.StartActivityForResult实例,它是ActivityResultContracts中最常用的合同之一,用于启动一个常规的活动并等待其结果。

    private val someActivityResultLauncher = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result ->
        if (result.resultCode == Activity.RESULT_OK) {
            // 处理返回的数据
        }
    }

  3. 启动活动:要启动一个活动并等待结果,只需调用ActivityResultLauncher的launch方法,并传递一个Intent。
    val intent = Intent(this, TargetActivity::class.java)
    someActivityResultLauncher.launch(intent)

  4. 处理结果: 当目标活动返回时,你在第2步中注册的Lambda表达式将被调用,你可以在这里检查结果码和数据。

常见的ActivityResultContracts:
除了StartActivityForResult,还有其他几种ActivityResultContracts,用于不同的场景:

  1. TakePicturePreview:用于从相机捕获图片预览。
  2. TakePicture:用于从相机捕获图片。
  3. CaptureImage:用于从相机或相册选择图片。
  4. CaptureVideo:用于从相机捕获视频。
  5. GetContent:用于从相册选择文件。


LayoutParams

LayoutParams是Android中用于描述一个View(视图)如何在父容器中定位和大小的重要对象。每一个View都有一个LayoutParams对象,用于定义其宽度、高度以及在父容器中的位置和对齐方式。

LayoutParams对象通常有以下属性可以被修改:

  1. width:View的宽度,可以是固定的像素值,MATCH_PARENT(匹配父容器宽度),或WRAP_CONTENT(根据内容自动调整宽度)。
  2. height:View的高度,同width的选项。
  3. gravity:View内部内容的对齐方式。
  4. marginStart / marginEnd / marginTop / marginBottom:View四周的内边距。
  5. weight:在使用MATCH_PARENT时,和其他具有相同权重的兄弟View一起分配剩余空间。

LayoutParams的类型:
不同的ViewGroup子类通常会有自己的LayoutParams子类,以满足特定的布局需求。例如:

  1. LinearLayout.LayoutParams:适用于LinearLayout,可以设定宽度、高度、权重等。
  2. RelativeLayout.LayoutParams:适用于RelativeLayout,可以设定宽度、高度、以及相对于其他视图的位置关系。
  3. FrameLayout.LayoutParams:适用于FrameLayout,主要用于堆叠视图,可以设定宽度、高度、以及对齐方式。
  4. ConstraintLayout.LayoutParams:适用于ConstraintLayout,提供强大的约束布局能力,可以设定宽度、高度、以及与其他视图之间的约束关系。

 获取LayoutParams方式:

    val params = mDataBind.printActMaterialLibManagerEditLl.layoutParams

修改LayoutParams属性:

params.width = ViewGroup.LayoutParams.MATCH_PARENT
params.setMargins(10, 10, 10, 10) // 设置左边距、顶边距、右边距、底边距
myView.layoutParams = params

 


 

ViewHolder

ViewHolder 是一种设计模式,常用于 Android 开发中的列表视图(如 ListView, RecyclerView)以提高性能。当列表中有大量项需要滚动时,频繁地创建和销毁视图会消耗大量的计算资源,并可能导致界面响应变慢。使用 ViewHolder 模式可以显著减少这种开销。

ViewHolder 模式的原理:

  1. 重用 View:当列表项滑出屏幕后,对应的视图将被缓存起来而不是销毁。当新数据需要显示时,这些缓存的视图会被复用,避免了重新创建视图对象的开销。
  2. 减少 findViewById 调用:在每个列表项的视图中,通过 ViewHolder 一次性查找所有需要绑定数据的视图组件。之后每次更新数据时,直接使用 ViewHolder 中已经找到的视图组件。


使用 ViewHolder 的步骤:

  1. 定义 ViewHolder 类:创建一个静态内部类,继承自 RecyclerView.ViewHolder。在构造函数中初始化所有的视图组件并将其存储为成员变量。
  2. 创建 ViewHolder 实例:在 Adapter 的 onCreateViewHolder 方法中创建 ViewHolder 实例。设置必要的点击监听器等。
  3. 绑定数据到 ViewHolder:在 Adapter 的 onBindViewHolder 方法中,根据当前位置的数据更新 ViewHolder 的视图。

示例:

假设我们有一个简单的数据模型Item,它包含一些数据,例如名称和描述:

data class Item(val name: String, val description: String)

然后我们可以创建一个 RecyclerView 适配器 MyAdapter 和一个 ViewHolder 类 MyViewHolder。

定义 ViewHolder 类:

class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    val nameTextView: TextView = itemView.findViewById(R.id.name)
    val descriptionTextView: TextView = itemView.findViewById(R.id.description)

    fun bind(item: Item) {
        nameTextView.text = item.name
        descriptionTextView.text = item.description
    }
}

创建 RecyclerView 适配器:

class MyAdapter(private val items: List<Item>) : RecyclerView.Adapter<MyViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val itemView = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_layout, parent, false)
        return MyViewHolder(itemView)
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val currentItem = items[position]
        holder.bind(currentItem)
    }

    override fun getItemCount(): Int = items.size
}

解释:
ViewHolder 类 (MyViewHolder):

  1. 接收一个 View 对象作为构造函数参数。
  2. 初始化 nameTextView 和 descriptionTextView 成员变量。
  3. bind 函数用于将数据绑定到视图。

Adapter 类 (MyAdapter):

  1. onCreateViewHolder: 创建 ViewHolder 实例。
  2. onBindViewHolder: 绑定数据到 ViewHolder。
  3. getItemCount: 返回数据集的大小。

使用适配器
在 Activity 或 Fragment 中设置 RecyclerView 并使用适配器:

class MainActivity : AppCompatActivity() {

    private lateinit var recyclerView: RecyclerView
    private lateinit var adapter: MyAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        recyclerView = findViewById(R.id.recycler_view)
        recyclerView.layoutManager = LinearLayoutManager(this)

        val items = listOf(
            Item("Item 1", "Description for Item 1"),
            Item("Item 2", "Description for Item 2"),
            // 更多数据...
        )

        adapter = MyAdapter(items)
        recyclerView.adapter = adapter
    }
}

;