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.pswp.cn/diannao/81849.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/81849.shtml
英文地址,請注明出處:http://en.pswp.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種背景類懸浮特效 前言背景閃現效果預覽代碼展示 元素陰影效果預覽代碼展示 元素懸浮陰影效果預覽代碼展示 元素上浮陰影效果預覽代碼展示 元素邊框陰影效果預覽代碼展示 元素卷角效果預覽代碼展示 結語 前言 在之前的文章中,我們介紹…