继上一篇文章介绍了项目中所使用的 Kotlin 特性, 本文继续整理当前项目所用到的特性.
一. apply 函数 和 run 函数
with,apply,run 函数都是 Kotlin 标准库中的函数. with 在第一篇文章中已经介绍过.
1.1 apply 函数
apply 函数是指在函数块内可以通过 this 指代该对象, 返回值为该对象自己. 在链式调用中, 可以考虑使用它来不破坏链式.
- /**
- * Calls the specified function [block] with `this` value as its receiver and returns `this` value.
- */
- @kotlin.internal.InlineOnly
- public inline fun <T> T.apply(block: T.() -> Unit): T {
- contract {
- callsInPlace(block, InvocationKind.EXACTLY_ONCE)
- }
- block()
- return this
- }
举个例子:
/**
* Created by tony on 2018/4/26.
*/
- object Test {
- @JvmStatic
- fun main(args: Array<String>) {
- val result ="Hello".apply {
- println(this+"World")
- this+"World"
- }
- println(result)
- }
- }
执行结果:
Hello World Hello
第一个字符串是在闭包中打印的, 第二个字符串是 result 的结果, 它仍然是 "Hello".
1.2 run 函数
run 函数类似于 apply 函数, 但是 run 函数返回的是最后一行的值.
- /**
- * Calls the specified function [block] with `this` value as its receiver and returns its result.
- */
- @kotlin.internal.InlineOnly
- public inline fun <T, R> T.run(block: T.() -> R): R {
- contract {
- callsInPlace(block, InvocationKind.EXACTLY_ONCE)
- }
- return block()
- }
举个例子:
/**
* Created by tony on 2018/4/26.
*/
- object Test {
- @JvmStatic
- fun main(args: Array<String>) {
- val result ="Hello".run {
- println(this+"World")
- this + "World"
- }
- println(result)
- }
- }
执行结果:
- Hello World
- Hello World
第一个字符串是在闭包中打印的, 第二个字符串是 result 的结果, 它返回的是闭包中最后一行的值, 所以也打印 "Hello World".
1.3 项目中的使用
在 App 的反馈页面中, 需要输入邮箱, 主题, 内容才能完成反馈按钮的提交.
最初的写法是这样:
- if (viewModel.email.value!!.isEmpty()) {
- toast(resources.getString(R.string.you_have_not_completed_the_email_address)).show()
- return@onClickRight
- }
- if (!Util.checkEmail(viewModel.email.value!!)) {
- toast(resources.getString(R.string.the_email_format_you_have_filled_is_incorrect)).show()
- return@onClickRight
- }
- if (viewModel.subject.value!!.isEmpty()) {
- toast(resources.getString(R.string.you_have_not_completed_the_feedback_subject)).show()
- return@onClickRight
- }
- if (viewModel.content.value!!.isEmpty()) {
- toast(resources.getString(R.string.you_have_not_completed_the_details)).show()
- return@onClickRight
- }
修改成只使用 apply 函数
- viewModel.apply {
- if (email.value!!.isEmpty()) {
- toast(resources.getString(R.string.you_have_not_completed_the_email_address)).show()
- return@onClickRight
- }
- if (!Util.checkEmail(email.value!!)) {
- toast(resources.getString(R.string.the_email_format_you_have_filled_is_incorrect)).show()
- return@onClickRight
- }
- if (subject.value!!.isEmpty()) {
- toast(resources.getString(R.string.you_have_not_completed_the_feedback_subject)).show()
- return@onClickRight
- }
- if (content.value!!.isEmpty()) {
- toast(resources.getString(R.string.you_have_not_completed_the_details)).show()
- return@onClickRight
- }
- }
感觉不够 cool, 可以结合 run 和 apply 函数一起使用
- viewModel.email.run {
- if (value!!.isEmpty()) {
- toast(resources.getString(R.string.you_have_not_completed_the_email_address)).show()
- return@onClickRight
- }
- if (!Util.checkEmail(value!!)) {
- toast(resources.getString(R.string.the_email_format_you_have_filled_is_incorrect)).show()
- return@onClickRight
- }
- viewModel
- }.subject.run {
- if (viewModel.subject.value!!.isEmpty()) {
- toast(resources.getString(R.string.you_have_not_completed_the_feedback_subject)).show()
- return@onClickRight
- }
- viewModel
- }.content.apply {
- if (value!!.isEmpty()) {
- toast(resources.getString(R.string.you_have_not_completed_the_details)).show()
- return@onClickRight
- }
- }
二. data class
Kotlin 的 data class 有点类似于 Scala 的 case class.
以下的 Java Bean 代码
/**
* Created by tony on 2018/4/27.
*/
- public class User {
- public String userName;
- public String password;
- }
等价于
data class User (var userName: String? = null,var password: String? = null)
可以看到采用 data class 能够简化 Java Bean 类. 我们的 App 采用了 MVVM 的架构, 因此对应 Model 类全部使用 data class.
三. 无需使用 findViewById 或者 butterknife
使用 Kotlin Android Extensions 插件即可实现该功能, 它是 Kotlin 插件的组成之一, 无需再单独安装插件.
我们在各个 modules 的 build.gradle 中添加该插件, 即可使用.
apply plugin: 'kotlin-android-extensions'
布局文件中的 id, 可以直接在代码中使用. 首先, 按照 import kotlinx.android.synthetic.main. 布局文件名 * 的方式导入.
例如 MainActivity, 它的布局文件是 activity_main.xml 则按照如下的方式进行 import
import kotlinx.android.synthetic.main.activity_main.*
那么 activity_main.xml 中控件的 id, 可以直接在 MainActivity 中使用, 无需使用 findViewById 或者 butterknife. 是不是特别方便?
四. 点击事件的埋点处理
App 的埋点, 使用自己家的产品 -- 魔窗的 sdk 来做事件的埋点.
如果使用 Java 来开发 App, 可以使用 AOP 来实现埋点. 由于我们的 App 采用 Kotlin 编写, Kotlin 可以将事件的点击简化成如下的形式
- view.setOnClickListener {
- ....
- }
这种简化了的 lambda 表达式, 所以我还是老老实实的使用传统方式进行埋点.
使用 Kotlin 的通常做法:
- view.setOnClickListener {
- TrackAgent.currentEvent().customEvent(eventName)
- ....
- }
或者
- view.setOnClickListener {
- TrackAgent.currentEvent().customEvent(eventName, trackMap)
- ....
- }
后来, 我写了一个 View 的扩展函数 click, 后来经过同事的优化. 可以查看简书的这篇文章 利用扩展函数优雅的实现 "防止重复点击" https://www.jianshu.com/p/7118226ecba9
目前, 已经将该扩展函数放入我的 Kolin 的工具类库 https://github.com/fengzhizi715/SAF-Kotlin-Utils
此时, 埋点的代码变成这样
- view.click {
- TrackAgent.currentEvent().customEvent(eventName)
- ....
- }
或者
- view.click {
- TrackAgent.currentEvent().customEvent(eventName, trackMap)
- ....
- }
进一步的优化处理, 对于 View 增加扩展函数 clickWithTrack 专门用于埋点的点击事件.
- package cn.magicwindow.core.ext
- import android.view.View
- import cn.magicwindow.TrackAgent
- import com.safframework.ext.clickWithTrigger
/**
*
* @FileName:
* cn.magicwindow.core.ext.ViewExt.java
* @author: Tony Shen
* @date: 2018-04-24 17:17
* @version V1.0 < 描述当前版本功能 >
*/
- fun <T : View> T.clickWithTrack(eventName: String, time: Long = 600, block: (T) -> Unit) = this.clickWithTrigger(time) {
- TrackAgent.currentEvent().customEvent(eventName)
- block(it as T)
- }
- fun <T : View> T.clickWithTrack(eventName: String, trackMap: HashMap<String, String>, time: Long = 600, block: (T) -> Unit) = this.clickWithTrigger(time) {
- TrackAgent.currentEvent().customEvent(eventName, trackMap)
- block(it as T)
- }
此时埋点可以这样使用:
- view.clickWithTrack(key) {
- ....
- }
或者
- view.clickWithTrack(key,trackMap) {
- ....
- }
总结
Kotlin 有很多的语法糖, 使用这些语法糖可以简化代码以及更优雅地实现功能.
先前的文章: 使用 Kotlin 高效地开发 Android App(一)
来源: https://juejin.im/post/5ae31d85518825671c0e4b83