AndroidUtilCodeKTX (以下簡稱 Ktx) 正式開源已經(jīng)有一個月了。到目前為止棚贾,在 Github 上收獲了 98
個 star 和 11
次 fork窖维。期間上了一次 Github Trending Kotlin 分類的榜單,也收到了一些開發(fā)者的好評以及建議妙痹。經(jīng)過這一個月的龜速更新铸史,做了一些我想添加的功能,修復(fù)了一些開發(fā)者反饋的問題怯伊。
當(dāng)前最新的版本是 0.0.6
:
implementation 'luyao.util.ktx:AndroidUtilKTX:0.0.6'
該版本的 Change log :
* 增加 log 開關(guān)
* 增加 TextView.notEmpty() 擴(kuò)展方法
* 增加 ShellExt , 提供執(zhí)行命令行相關(guān)函數(shù)
* BaseVMFragment琳轿、BaseVMActivity 中添加默認(rèn)異常處理回調(diào)
* android 版本判斷
* 判斷無障礙服務(wù)是否開啟
* RecyclerView.itemPadding
* 發(fā)送郵件
* 文件相關(guān)
* BaseActivity/BaseFragment 添加 CoroutineScope 實現(xiàn)
* 自動感知生命周期的 KtxHandler
* Activity 統(tǒng)一管理
* 應(yīng)用前后臺監(jiān)聽
* KtxSpan, 封裝常見 Span 的使用
下面簡單介紹一下 0.0.6 版本
的主要修改內(nèi)容。
一系列空判斷
得益于高階函數(shù)和 lambda 表達(dá)式的使用,我們可以充分發(fā)揮自己的想象力來封裝一些通用邏輯利赋。例如水评,對于一個可空對象,非空時執(zhí)行指定操作媚送,等于空時執(zhí)行另一操作中燥,我們的代碼中可能充斥著大量這種結(jié)構(gòu)的代碼:
if (obj == null) {
...
} else {
...
}
看看 Ktx 中是如何書寫這種邏輯的:
obj.notNull({
...
}, {
...
})
額,好像比原來優(yōu)雅了那么一點(diǎn)(容許我欺騙一下自己)塘偎。實現(xiàn)也很簡單疗涉,如下所示:
fun <T> Any?.notNull(f: () -> T, t: () -> T): T {
return if (this != null) f() else t()
}
支持返回值。忽略上面那個不是那么明顯的優(yōu)化吟秩,順著這個思路咱扣,我們可以在稍微復(fù)雜一點(diǎn)的情景下進(jìn)行類似的優(yōu)化。比如涵防,TextView
中的文字是否為空闹伪,可以定義如下擴(kuò)展函數(shù) :
fun TextView.notEmpty(f: TextView.() -> Unit, t: TextView.() -> Unit) {
if (text.toString().isNotEmpty()) f() else t()
}
如果你想到了更多的使用場景,歡迎來砸 issue壮池。
執(zhí)行 shell 命令
這個沒啥特殊的地方偏瓤,來自我的 Box 項目中讀取 linux 內(nèi)核版本的需求。雖說最后也沒讀取成功椰憋,但也加了這么一個頂層函數(shù):
fun executeCmd(command: String): String {
val process = Runtime.getRuntime().exec(command)
val resultReader = BufferedReader(InputStreamReader(process.inputStream))
val errorReader = BufferedReader(InputStreamReader(process.errorStream))
val resultBuilder = StringBuilder()
var resultLine: String? = resultReader.readLine()
val errorBuilder = StringBuilder()
var errorLine = errorReader.readLine()
while (resultLine != null) {
resultBuilder.append(resultLine)
resultLine = resultReader.readLine()
}
while (errorLine != null) {
errorBuilder.append(errorLine)
errorLine = errorReader.readLine()
}
return "$resultBuilder\n$errorBuilder"
}
BaseActivity 添加異常處理
這個來自我的 wanandroid 項目的 issue 7厅克,主要是針對 Kotlin Coroutine 的異常處理。原來也有異常處理橙依,只是 BaseViewModel
中用來接收異常的 mException
是私有的证舟,無法直接獲取。這個版本做了通用的異常處理窗骑,可以在 BaseVMActivity
和 BaseVMFragment
中直接通過 onError()
回調(diào)得到異常女责。具體可以參考 MvvmActivity 中的異常處理演示。
Android 版本判斷
fun fromM() = fromSpecificVersion(Build.VERSION_CODES.M)
fun beforeM() = beforeSpecificVersion(Build.VERSION_CODES.M)
fun fromN() = fromSpecificVersion(Build.VERSION_CODES.N)
fun beforeN() = beforeSpecificVersion(Build.VERSION_CODES.N)
fun fromO() = fromSpecificVersion(Build.VERSION_CODES.O)
fun beforeO() = beforeSpecificVersion(Build.VERSION_CODES.O)
fun fromP() = fromSpecificVersion(Build.VERSION_CODES.P)
fun beforeP() = beforeSpecificVersion(Build.VERSION_CODES.P)
fun fromSpecificVersion(version: Int): Boolean = Build.VERSION.SDK_INT >= version
fun beforeSpecificVersion(version: Int): Boolean = Build.VERSION.SDK_INT < version
判斷無障礙服務(wù)是否開啟
工作中遇到的一個小需求慧域,根據(jù)無障礙服務(wù)名稱判斷服務(wù)是否開啟鲤竹。
fun Context.checkAccessbilityServiceEnabled(serviceName: String): Boolean {
val settingValue =
Settings.Secure.getString(applicationContext.contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)
return settingValue.notNull({
var result = false
val splitter = TextUtils.SimpleStringSplitter(':')
while (splitter.hasNext()) {
if (splitter.next().equals(serviceName, true)) {
result = true
break
}
}
result
}, { false })
}
RecyclerView.itemPadding
fun RecyclerView.itemPadding(top: Int, bottom: Int, left: Int = 0, right: Int = 0) {
addItemDecoration(PaddingItemDecoration(top, bottom, left, right))
}
方便快捷添加 itemPadding 浪读,如下所示:
commonRecycleView.run {
itemPadding(5, 5, 10, 10)
layoutManager = LinearLayoutManager(this@CommonListActivity)
adapter = commonAdapter
}
單位是 dp
昔榴,內(nèi)部處理了 dp2px
的邏輯。
發(fā)送郵件
IntentExt
中添加了發(fā)送郵件的函數(shù)碘橘,支持最基本的 subject
和 text
互订。
fun Context.sendEmail(email: String, subject: String?, text: String?) {
Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:$email")).run {
subject?.let { putExtra(Intent.EXTRA_SUBJECT, subject) }
text?.let { putExtra(Intent.EXTRA_TEXT, text) }
startActivity(this)
}
}
文件相關(guān)
文件相關(guān)的部分,本來是準(zhǔn)備花大把功夫來整合的痘拆,可是 Kotlin 標(biāo)準(zhǔn)庫關(guān)于文件操作的支持實在是太完善了仰禽。我有對照 AndroidUtilCode 中的文件工具類,標(biāo)準(zhǔn)庫基本就可以滿足其中的大部分功能。為了能更全面的封裝文件管理相關(guān)的工具類吐葵,我在 Box 項目中添加了簡單的文件管理功能规揪,具體可見最新 commit。
通過這個簡單的文件管理器模塊温峭,在標(biāo)準(zhǔn)庫的基礎(chǔ)上猛铅,主要添加了以下擴(kuò)展屬性和函數(shù)。
val File.canListFiles: Boolean :是否可以獲取子文件夾
val File.totalSize: Long :總大小凤藏,包括所有子文件夾
val File.formatSize: String :格式化文件總大小奸忽,包括所有子文件夾
val File.mimeType: String :獲取文件 mimeType
/**
* [isRecursive] 是否獲取所有子文件夾
* [filter] 文件過濾器
* /
fun File.listFiles(isRecursive: Boolean = false, filter: ((file: File) -> Boolean)? = null): Array<out File> {}
/**
* [append] 是否追加
* [text] 要 write 的內(nèi)容
* [charset] 字符編碼
* /
fun File.writeText(append: Boolean = false, text: String, charset: Charset = Charsets.UTF_8)
fun File.writeBytes(append: Boolean = false, bytes: ByteArray)
/**
* [destFile] 目標(biāo)文件/文件夾
* [overwrite] 是否覆蓋
* [reserve] 是否保留源文件
*/
fun File.moveTo(destFile: File, overwrite: Boolean = true, reserve: Boolean = true)
/**
* [destFolder] 目標(biāo)文件/文件夾
* [overwrite] 是否覆蓋
* [func] 單個文件的進(jìn)度回調(diào) (from 0 to 100)
*/
fun File.moveToWithProgress(
destFolder: File,
overwrite: Boolean = true,
reserve: Boolean = true,
func: ((file: File, i: Int) -> Unit)? = null
)
fun File.rename(newName: String)
fun File.rename(newFile: File)
除此之外的一些常見操作大多已經(jīng)包含在 Kotlin Stdlib 中,讀者可以閱讀一下 /kotlin/io/Utils.kt
文件揖庄。
CoroutineScope
在 BaseActivity/BaseFragment
中添加了 CoroutineScope
實現(xiàn)栗菜,其子類中可以直接通過 launch {}
使用主線程的協(xié)程作用域,并在 onDestroy()
中會自動取消在該作用域中啟動的協(xié)程蹄梢。有過協(xié)程使用經(jīng)驗的同學(xué)應(yīng)該不難理解疙筹,實現(xiàn)也很簡單。
abstract class BaseActivity : AppCompatActivity(), CoroutineScope by MainScope() {
...
...
override fun onDestroy() {
super.onDestroy()
cancel()
}
}
沒使用過協(xié)程的也沒有關(guān)系禁炒,后續(xù)我會寫一篇文章簡單介紹如何正確的在 Android 上使用協(xié)程腌歉。
自動感知生命周期的 KtxHandler
這是一個自動感知組件生命周期的 Handler,給它注冊一個 LifecycleOwner
齐苛,它就能在組件 onDestroy()
時自動清理資源翘盖。這個想法來自我在網(wǎng)上看到的代碼,恕我實在想不起來出處了凹蜂,下次會提前記錄出處并標(biāo)注出來馍驯。
class KtxHandler(lifecycleOwner: LifecycleOwner, callback: Callback) : Handler(callback), LifecycleObserver {
private val mLifecycleOwner: LifecycleOwner = lifecycleOwner
init {
lifecycleOwner.lifecycle.addObserver(this)
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
private fun onDestroy() {
removeCallbacksAndMessages(null)
mLifecycleOwner.lifecycle.removeObserver(this)
}
}
其簡單使用可以參見 LifeCycleActivity.kt 。
Activity 統(tǒng)一管理/應(yīng)用前后臺監(jiān)聽
這個來源于 issue 區(qū)的需求玛痊。開發(fā)中的需求無非兩個汰瘫,關(guān)閉指定的 Activity,退出應(yīng)用時關(guān)閉所有 Activity 擂煞,這兩個函數(shù)定義在了單例類 KtxManager
中混弥。
fun finishActivity(clazz: Class<*>){
for (activity in mActivityList)
if (activity.javaClass == clazz)
activity.finish()
}
fun finishAllActivity() {
for (activity in mActivityList)
activity.finish()
}
同樣在 LifeCycleActivity.kt 中有這兩個函數(shù)的使用例子。
這塊的使用還是比較巧妙的对省。通過 ActivityLifecycleCallbacks
來監(jiān)聽 Activity 的生命周期蝗拿,而 ActivityLifecycleCallbacks 的一般使用方式就是在 Application
中進(jìn)行注冊。但是在 Ktx 中蒿涎,并沒有 Application 類哀托,也無需開發(fā)者在集成時書寫任何代碼。它自動完成了生命周期的注冊監(jiān)聽劳秋〔质郑可能有的同學(xué)在其他地方也見識過這個小技巧胖齐,如果不知道的話,閱讀一下 Ktx.kt嗽冒,相信你就明白了呀伙。
順帶也通過 ProcessLifecycleOwner
監(jiān)聽了應(yīng)用進(jìn)入前臺和后臺,相信你也看到了對應(yīng)的 Toast 提示添坊。
KtxSpan
最后是一個 Span 工具類区匠,先看看效果。
對這個圖很熟悉的話帅腌,說明你是 Blankji 的 AndroidUtilCode 的忠實用戶驰弄。這里我沒有再重新造輪子了,當(dāng)然也不是完全照抄 AndroidUtilCode 速客。我一直是 material-dialogs 的使用者戚篙,非常喜歡它的 API 形式,所以仿照它的 API 結(jié)構(gòu)重構(gòu)了 KtxSpan
溺职。
KtxSpan().with(spanTv).show {
text(
"SpanUtils",
foregroundColor = Color.YELLOW,
backgroundColor = Color.LTGRAY,
alignment = Layout.Alignment.ALIGN_CENTER
)
text("前景色", foregroundColor = Color.GREEN)
text("背景色", backgroundColor = Color.LTGRAY)
blockLine(spanTv.dp2px(6), true)
text("行高", lineHeight = 2 * spanTv.lineHeight, backgroundColor = Color.LTGRAY)
text(
"測試段落縮進(jìn)岔擂,首行縮進(jìn)兩字,其他行不縮進(jìn)浪耘,其他行不縮進(jìn)",
first = (spanTv.textSize * 2).toInt(),
rest = 0,
backgroundColor = Color.GREEN
)
text(
"測試引用乱灵,后面的字是為了湊到兩行的效果",
quoteColor = Color.GREEN,
quoteStripeWidth = 10,
quoteGapWidth = 10,
backgroundColor = Color.LTGRAY
)
image(
R.drawable.small,
verticalAlignment = ImageSpan.ALIGN_BOTTOM,
marginLeft = dp2px(30),
marginRight = dp2px(40)
)
}
KtxSpan 只有三個方法,但已經(jīng)足以覆蓋大部分使用場景七冲。
第一個方法 text()
痛倚,可以表示文字的大部分效果。得益于 Kotlin 的方法參數(shù)支持默認(rèn)值的特性澜躺,消滅了大部分方法重載的情況蝉稳。text() 方法的參數(shù)也相當(dāng)之多,共有 31
個參數(shù)掘鄙,除了 text
為必須的之外耘戚,其余均可以根據(jù)需求賦值。默認(rèn)每個 text()
方法都是新的一行操漠,你也可以傳入 isNewLine: Boolean = false
來使得下一行不再換行收津。
第二個方法是 image()
,可以表示圖片顯示浊伙,支持屬性有對齊方式和左右間距撞秋。
第三個方法是 blockLine()
,表示段落間距吧黄。有兩個參數(shù)部服,一個是段落間距的值唆姐,一個是是否為以后的段落都添加間距拗慨。
關(guān)于 KtxSpan 的具體使用,可見項目中的示例代碼 : KtxSpanActivity 。
Last
以上就是這次更新的全部內(nèi)容了赵抢,AndroidUtilCodeKTX 還是個孩子剧蹂,歡迎來撩 !
文章首發(fā)微信公眾號:
秉心說
烦却, 專注 Java 宠叼、 Android 原創(chuàng)知識分享,LeetCode 題解其爵。AndroidUtilCodeKTX 最新更新信息冒冬,掃碼關(guān)注我吧!