Kotlin带接收者的Lambda介绍和应用(封装DialogFragment)

先来看一个具体应用:假设我们有一个App,App中有一个退出应用的按钮,点击该按钮后并不是立即退出,而是先弹出一个对话框,询问用户是否确定要退出,用户点了确定再退出,点取消则不退出,示例代码如下:

button.setOnClickListener {AlertDialog.Builder(this).setTitle("提示").setMessage("是否要退出应用?").setPositiveButton("确定") { _, _ -> exitApp() }.setNegativeButton("取消", null).show()
}

我们可能经常需要用到对话框,每次都要写很多重复代码,所以就会想要简化一下,比如,首先把链式调用中的 “.” 给去掉,通过使用apply扩展函数,这就是一个带接收者的扩展函数,所以在apply中的接收者this可以省略不写,这样就能不写 “.” 了,如下:

button.setOnClickListener {AlertDialog.Builder(this).apply {setTitle("提示")setMessage("是否要退出应用?")setPositiveButton("确定") { _, _ -> exitApp() }setNegativeButton("取消", null)show()}
}

这里面有一些代码是每次都一模一样的,所以还可以优化,Builder每次都要创建,show()函数每次都要调用,这些可以封装到函数中,如下:

button.setOnClickListener {showDialog(this) { builder ->builder.setTitle("提示").setMessage("是否要退出应用?").setPositiveButton("确定") { _, _ -> exitApp() }.setNegativeButton("取消", null)}
}fun showDialog(context: Context, init: (AlertDialog.Builder) -> Unit) {val builder = AlertDialog.Builder(context)init(builder)builder.show()
}

如上代码所示,我们把Builder的创建和show()方法的调用封装到了方法里面,这样重复的代码就只写一次,不用每次都写了。但是感觉还不是很优雅,每次调用方法都要写 “.”,通过apply可以消除 “.” 的调用,如下:

showDialog(this) { builder ->builder.apply {setTitle("提示")setMessage("是否要退出应用?")setPositiveButton("确定") { _, _ -> exitApp() }setNegativeButton("取消", null)}
}

感觉还是不够优雅,每次都要从builder上调用apply,这也是重复代码,能不能优化成如下这样:

showDialog(this) {setTitle("提示")setMessage("是否要退出应用?")setPositiveButton("确定") { _, _ -> exitApp() }setNegativeButton("取消", null)
}

这就需要用到带接收者的Lambda了,让我们的Lambda接收Builder对象,这样Lambda代码块中默认就有this,这个this对象就是Builder,修改代码如下:

showDialog(this) {setTitle("提示")setMessage("是否要退出应用?")setPositiveButton("确定") { _, _ -> exitApp() }setNegativeButton("取消", null)
}fun showDialog(context: Context, init: AlertDialog.Builder.() -> Unit) val builder = AlertDialog.Builder(context)init(builder)builder.show()
}

OK,这样就完美了,对比showDialog中的参数:

init: (AlertDialog.Builder) -> Unit  // 带参数的Lambda
init: AlertDialog.Builder.() -> Unit // 带接收者的Lambda

从对比上来看,长得差不多,带接收者就是把括号中的参数移到括号前面,并加了一个 “.” ,带 参数 和带 接收者 两种方式能都能实现把Builder的共同代码封装起来,把Builder的不同部分留给调用者设置,但是明显带接收者的Lambda的方式更优雅,带接收者的Lambda还有一点不同,它可像扩展函数那样调用,如下:

fun showDialog(context: Context, init: AlertDialog.Builder.() -> Unit) {val builder = AlertDialog.Builder(context)builder.init()builder.show()
}

如上代码,init是一个Lambda参数的变量名,但是使用时可以像扩展函数一样在接收者Builder的身上直接调用,所以上面代码再结合apply可以简化成一行,如下:

fun showDialog(context: Context, init: AlertDialog.Builder.() -> Unit) {AlertDialog.Builder(context).apply(init).show()
}

啊,这实在是太优雅了,读起来也好理解,对于代码:AlertDialog.Builder(context).apply(init).show(),按顺序理解就行了:创建一个Builder对象,并用init这个LambdaBuilder对象进行初始化,最后调用show进行显示。

总结一下:

  • 无参数的Lambda,调用Lambda时无需传参数,如下:

    val printMessage: () -> Unit = {println("Hello Kotlin")
    }printMessage()
    
  • 带参数的Lambda,调用Lambda时需要传参数,如下:

    val printMessage: (String) -> Unit = { message ->println(message)
    }printMessage("Hello Kotlin")
    

    这里的 message -> 可以省略不写,不写默认参数名为it

  • 带接收者的Lambda,调用Lambda时需要传参数,如下:

    val printMessage: String.() -> Unit = {println(this)
    }
    printMessage("Hello Kotlin")
    

    接收者参数 的区别,我个人感觉功能上是一样的,都是给Lambda使用的一个参数,只不过叫 接收者 的参数有点特别,它在Lambda的代码块中使用this来访问这个参数,而this又是可以省略不写的,基于这个特点,我们不能给带接收者的Lambda起别的参数名,比如下面是错误的,IDE会直接报错:

    val printMessage: String.() -> Unit = { message ->println(message)
    }
    

    如果这样允许的话,那this就不见了,这跟带参数的Lambda就没有区别了,那你为何不直接使用带参数的Lambda呢,所以不允许这样写,带接收者的Lambda就只能用this代表参数,无需多此一举,如果你真的有特殊需求即要用到this,又要同时用到另起一个参数名的话,只能这样:

    val printMessage: String.() -> Unit = {val message = thisprintln(message)println(this)
    }
    

    另外,带接收者的Lambda在调用时可以像扩展函数那样在接收者对象上调用,但是我们知道它实际上就是给Lambda传递了一个this参数,如:

    val printMessage: String.() -> Unit = {val message = thisprintln(message)
    }
    "Hello Kotlin".printMessage() // 等同于:printMessage("Hello Kotlin")
    

现代开发中,显示对话框推荐的做法是使用DialogFragment,但是用了这个感觉变得麻烦了许多,还得先创建一个类继承DialogFragment,如果有多种类型的对话框,那是不是就得创建多个类去继承DialogFragment,以实现多种不同形式?这样做是可以的,但是真的很麻烦,能不能像AlertDialog.Builder那样,一个类就通用,那样多好,学会了带接收者的Lambda后,实现这个功能就太简单了,代码如下:

class MyDialog(private val builder: AlertDialog.Builder) : DialogFragment() {override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {return builder.create()}companion object {fun show(context: Context, fragmentManager: FragmentManager, tag: String, init: AlertDialog.Builder.() -> Unit) {MyDialog(AlertDialog.Builder(context).apply(init)).show(fragmentManager, tag)}fun dismiss(fragmentManager: FragmentManager, tag: String) {(fragmentManager.findFragmentByTag(tag) as? MyDialog)?.dismissAllowingStateLoss()// dismiss()函数在状态保存期间调用会抛异常,比如屏幕旋转时系统会保存界面UI状态,此时调用dismiss()就会抛出异常,// 而调用dismissAllowingStateLoss()则不会抛异常。但是你既然都走到要dismiss的// 地步了,说明不需要对话框了,也不需要系统去恢复了。所以有点搞不懂为什么需要两种取消对话枉的函数}}}

虽然需要声明一个类继承DialogFragment,但是我们只需要声明一次就行,这个类创建对话框时也可以使用AlertDialog.Builder,所以我们通过封装方法把AlertDialog.Builder设置通过带接收者的Lambda交给用户处理即可,使用上也很简单,示例如下:

MyDialog.show(this, supportFragmentManager, "ExitAppTipDialog") {setTitle("提示")setMessage("确定要退出App吗?")setPositiveButton("确定") { dialog, which ->  }setNegativeButton("取消") { dialog, which ->  }
}

这里,对于 “确定” 和 “取消” 按钮也是很常用的,而且不管你是点了确定还是取消,点击之后对话框都会自动取消,所以上面代码中的dialogwhich参数大多数情况下都是用不到的,但是每次都要写也很麻烦,而且函数名setPositiveButtonsetNegativeButton也不好记,常常因为不记得方法名而浪费很多时间去查找方法名,所以我们可以给AlertDialog.Builder增加两个扩展函数来解决这个问题,代码如下:

fun AlertDialog.Builder.setOk(name: String, listener: (() -> Unit)? = null) {setPositiveButton(name) { _, _ -> listener?.invoke() }  
}fun AlertDialog.Builder.setNo(name: String, listener: (() -> Unit)? = null) {setNegativeButton(name) { _, _ -> listener?.invoke() }   
}

当需要显示对话框时代码就简单多了,如下:

MyDialog.show(this, supportFragmentManager, tag) {setTitle("提示")setMessage("确定要退出App吗?")setOk("确定") {  exitApp() }setNo("取消")
}

还能再优化,比如每次要传supportFragmentManager,又长又占空间,它的获取基本上就是从FragmentActivity或者Fragemnt中获取,那就增加重载函数接收这两个类型即可。

还有每次要传tag,早期我们使用AlertDialog的时候也没用过tag啊,所以tag应该可空,当我们没传tag时自动生成一个。示例代码如下:

class MyDialog(private val builder: AlertDialog.Builder) : DialogFragment() {override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {return builder.create()}companion object {private var count = 0fun show(activity: FragmentActivity, tag: String? = null, init: AlertDialog.Builder.() -> Unit) {show(activity, activity.supportFragmentManager, tag, init)}fun show(fragment: Fragment, tag: String? = null, init: AlertDialog.Builder.() -> Unit) {val context = fragment.context ?: returnshow(context, fragment.parentFragmentManager, tag, init)}fun show(context: Context, fragmentManager: FragmentManager, tag: String? = null, init: AlertDialog.Builder.() -> Unit) {val dialogTag = tag ?: "DialogTag_${count++}"MyDialog(AlertDialog.Builder(context).apply(init)).show(fragmentManager, dialogTag)}fun dismiss(fragmentManager: FragmentManager, tag: String) {(fragmentManager.findFragmentByTag(tag) as? MyDialog)?.dismissAllowingStateLoss()}}}fun AlertDialog.Builder.setOk(name: String, listener: (() -> Unit)? = null) {setPositiveButton(name) { _, _ -> listener?.invoke() }
}fun AlertDialog.Builder.setNo(name: String, listener: (() -> Unit)? = null) {setNegativeButton(name) { _, _ -> listener?.invoke() }
}

调用:

MyDialog.show(this) {setTitle("提示")setMessage("确定要退出App吗?")setOk("确定") {  exitApp() }setNo("取消")
}

Ok,事已至此,已经无可挑剔了!

2025-05-26,还能再优化,既然DialogFragment一般用在FragmentActivityFragment,那就可以给这两个对象添加扩展函数,使用起来更简洁,如下:

class MyDialog(private val builder: AlertDialog.Builder) : DialogFragment() {override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {return builder.create()}companion object {private var count = 0fun show(context: Context, fragmentManager: FragmentManager, tag: String? = null, init: AlertDialog.Builder.() -> Unit) {val dialogTag = tag ?: "DialogTag_${count++}"MyDialog(AlertDialog.Builder(context).apply(init)).show(fragmentManager, dialogTag)}fun dismiss(fragmentManager: FragmentManager, tag: String) {(fragmentManager.findFragmentByTag(tag) as? MyDialog)?.dismissAllowingStateLoss()}}}fun AlertDialog.Builder.setOk(name: String, listener: (() -> Unit)? = null) {setPositiveButton(name) { _, _ -> listener?.invoke() }
}fun AlertDialog.Builder.setNo(name: String, listener: (() -> Unit)? = null) {setNegativeButton(name) { _, _ -> listener?.invoke() }
}fun FragmentActivity.showMyDialog(tag: String? = null, init: AlertDialog.Builder.() -> Unit) {MyDialog.show(this, this.supportFragmentManager, tag, init)
}fun Fragment.showMyDialog(tag: String? = null, init: AlertDialog.Builder.() -> Unit) {val context = this.context ?: returnMyDialog.show(context, this.parentFragmentManager, tag, init)
}

FragemntActivityFragment中要显示对话框时:

showMyDialog {setTitle("提示")setMessage("确定要退出App吗?")setOk("确定") {  exitApp() }setNo("取消")
}

OK,这次真的没得再优化了吧!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/81849.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ES6/ES11知识点 续一

模板字符串 在 ECMAScript(ES)中,模板字符串(Template Literals)是一种非常强大的字符串表示方式,它为我们提供了比传统字符串更灵活的功能,尤其是在处理动态内容时。模板字符串通过反引号&…

【C++】智能指针RALL实现shared_ptr

个人主页 : zxctscl 专栏 【C】、 【C语言】、 【Linux】、 【数据结构】、 【算法】 如有转载请先通知 文章目录 1. 为什么需要智能指针?2. 内存泄漏2.1 什么是内存泄漏,内存泄漏的危害2.2 内存泄漏分类(了解)2.3 如何…

ROS2 开发踩坑记录(持续更新...)

1. 从find_package(xxx REQUIRED)说起,如何引用其他package(包) 查看包的安装位置和include路径详细文件列表 例如,xxx包名为pluginlib # 查看 pluginlib 的安装位置 dpkg -L ros-${ROS_DISTRO}-pluginlib | grep include 这条指令的目的是…

系统思考:困惑源于内心假设

不要怀疑,你的困惑来自你的假设。 你是否曾经陷入过无解的困境,觉得外部环境太复杂,自己的处境无法突破?很多时候,答案并不在于外部的局势,而是来自我们内心深处的假设——那些我们理所当然、从未质疑过的…

GitHub修炼法则:第一次提交代码教学(Liunx系统)

前言 github是广大程序员们必须要掌握的一个技能,万事开头难,如果成功提交了第一次代码,那么后来就会简单很多。网上的相关资料往往都不是从第一次开始,导致很多新手们会在过程中遇到很多权限认证相关的问题,进而被卡…

沥青路面裂缝的目标检测与图像分类任务

文章题目是《A grid‐based classification and box‐based detection fusion model for asphalt pavement crack》 于2023年发表在《Computer‐Aided Civil and Infrastructure Engineering》 论文采用了一种基于网格分类和基于框的检测(GCBD)&#xff…

【Flask】ORM模型以及数据库迁移的两种方法(flask-migrate、Alembic)

ORM模型 在Flask中,ORM(Object-Relational Mapping,对象关系映射)模型是指使用面向对象的方式来操作数据库的编程技术。它允许开发者使用Python类和对象来操作数据库,而不需要直接编写SQL语句。 核心概念 1. ORM模型…

C/C++滑动窗口算法深度解析与实战指南

C/C滑动窗口算法深度解析与实战指南 引言 滑动窗口算法是解决数组/字符串连续子序列问题的利器,通过动态调整窗口边界,将暴力解法的O(n)时间复杂度优化至O(n)。本文将系统讲解滑动窗口的核心原理、C/C实现技巧及经典应用场景,助您掌握这一高…

Vuex使用指南:状态管理

一、什么是状态管理?为什么需要 Vuex? 1. 状态管理的基本概念 在 Vue 应用中,状态指的是应用中的数据。例如: 用户登录状态购物车中的商品文章列表的分页信息 状态管理就是对这些数据的创建、读取、更新和删除进行有效管理。 …

【信息系统项目管理师-论文真题】2007下半年论文详解(包括解题思路和写作要点)

更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 试题1:大型项目的计划与监控1、写作要点2、解题思路大型信息系统项目的组织制订大型信息系统项目进度计划的方法试题2:组织级项目管理的绩效考核1、写作要点2、解题思路在项目考核过程中会遇到哪些问题项目的…

项目管理学习-CSPM(1)

01引言 最近在学习CSPM的课程,有部分的内容自己还是受益匪浅的,建议有需要提升项目管理能力的同学可以以考促学的方式进行学习,下面整理了一部分内容和大家分享和学习。CSPM全称 China Standards Project Management,中文名项目管…

介绍分治、动态规划、回溯分别是什么?有什么联系和区别?给出对象的场景和java代码?

一、分治算法(Divide and Conquer) 概念: 分治算法是将一个复杂问题分成若干个子问题,每个子问题结构与原问题类似,然后递归地解决这些子问题,最后将子问题的结果合并得到原问题的解。 特点:…

解锁DeepSeek模型微调:从小白到高手的进阶之路

目录 一、DeepSeek 模型初相识二、探秘微调原理2.1 迁移学习基础2.2 微调的参数更新机制 三、数据准备3.1 数据收集3.2 数据标注3.3 数据预处理 四、模型选择与加载4.1 选择合适的预训练模型4.2 加载模型 五、微调训练实战5.1 确定微调策略5.2 设置训练参数5.3 训练过程 六、模…

端到端观测分析:从前端负载均衡到后端服务

前言 我们在做系统运维保障的时候,关注从前端负载均衡到后端服务的流量情况是很有必要的,可以了解每个后端服务实例接收的流量大小,这有助于确定资源分配是否合理,能够帮助找出后端服务中的性能瓶颈。同时,当系统出现…

Ubuntu下OCC7.9+Qt5 快速搭建3D可视化框架

Ubuntu下OCC7.9+Qt5搭建简易的项目框架 近两年国产CAD替代如日中天,而几何内核作为CAD软件的核心组件之一,当前有且仅有唯一开源的几何内核库即OCCT;这里为各位自立于投入CAD开发或正在学习OCC库的小伙伴们奉献上一个快速搭建QT+OCC的项目框架; 本文介绍了Qt5+Occ 显示几何…

C++类与对象—下:夯实面向对象编程的阶梯

9. 赋值运算符重载 9.1 运算符重载 在 C 里,运算符重载能够让自定义类型的对象像内置类型那样使用运算符,这极大地提升了代码的可读性与可维护性。运算符重载本质上是一种特殊的函数,其函数名是 operator 加上要重载的运算符。 下面是运算…

【深度学习-Day 6】掌握 NumPy:ndarray 创建、索引、运算与性能优化指南

Langchain系列文章目录 01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南 02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖 03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南 04-玩转 LangChai…

工程师 - 汽车分类

欧洲和中国按字母对汽车分类: **轴距**:简单来说,就是前轮中心点到后轮中心点之间的距离,也就是前轮轴和后轮轴之间的长度。根据轴距的大小,国际上通常把轿车分为以下几类(德国大众汽车习惯用A\B\C\D分类&a…

[低代码 + AI] 明道云与 Dify 的三种融合实践方式详解

随着低代码平台和大语言模型工具的不断发展,将企业数据与智能交互能力融合,成为提高办公效率与自动化水平的关键一步。明道云作为一款成熟的低代码平台,Dify 则是一个支持自定义工作流的开源 LLM 应用框架。两者结合,可以实现灵活、高效的智能化业务处理。 本文将详解明道…

鼠标悬浮特效:常见6种背景类悬浮特效

鼠标悬浮特效:常见6种背景类悬浮特效 前言背景闪现效果预览代码展示 元素阴影效果预览代码展示 元素悬浮阴影效果预览代码展示 元素上浮阴影效果预览代码展示 元素边框阴影效果预览代码展示 元素卷角效果预览代码展示 结语 前言 在之前的文章中,我们介绍…