偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

用 Kotlin 開(kāi)發(fā) Android 項(xiàng)目是一種什么樣的感受(二)

移動(dòng)開(kāi)發(fā)
前面我已經(jīng)寫(xiě)了一篇關(guān)于Kotlin語(yǔ)言本身的特點(diǎn),而Kotlin對(duì)于Android的一些特殊支持我沒(méi)有收錄在內(nèi),已經(jīng)有朋友給我提出了建議。因此本文我們會(huì)說(shuō)的更詳細(xì),Kotlin開(kāi)發(fā)Android究竟還有一些什么讓人深感愉悅之處。

前言

前面我已經(jīng)寫(xiě)了一篇名為《用 Kotlin 開(kāi)發(fā) Android 項(xiàng)目是一種什么樣的感受?》的文章。文中多數(shù)提到的還是 Kotlin 語(yǔ)言本身的特點(diǎn),而 Kotlin 對(duì)于 Android 的一些特殊支持我沒(méi)有收錄在內(nèi),已經(jīng)有朋友給我提出了建議。于是在前文的基礎(chǔ)上,這一次我們或許會(huì)說(shuō)的更詳細(xì),Kotlin 開(kāi)發(fā) Android 究竟還有一些什么讓人深感愉悅之處。

正文

1.向 findViewById 說(shuō) NO

不同于 JAVA 中,在 Kotlin 中 findViewById 本身就簡(jiǎn)化了很多,這得益于 Kotlin 的類型推斷以及轉(zhuǎn)型語(yǔ)法后置:

  1. val onlyTv = findViewById(R.id.onlyTv) as TextView 

很簡(jiǎn)潔,但若僅僅是這樣,想必大家會(huì)噴死我:就這么點(diǎn)差距也拿出來(lái)搞事?

當(dāng)然不是。在官方庫(kù) anko 的支持下,這事又有了很多變化。

例如 

  1. val onlyTv = find<TextView>(R.id.onlyTv) 
  2. val onlyTv: TextView = find(R.id.onlyTv) 

肯定有人會(huì)問(wèn):find 是個(gè)什么鬼?

讓我們點(diǎn)過(guò)去看看 find 的源碼:

  1. inline fun <reified T : View> Activity.find(id: Int): T = findViewById(id) as T 

忽略掉其他細(xì)節(jié),原來(lái)和我們上面***種寫(xiě)法沒(méi)差別嘛,不就是用一個(gè)擴(kuò)展方法給 Activity 加了這么一個(gè)方法,幫我們寫(xiě)了 findViewById,再幫我們轉(zhuǎn)型了一下嘛。

其實(shí) Kotlin 中還有很多令人乍舌的實(shí)現(xiàn)其實(shí)都是在一些基礎(chǔ)特性的組合之上實(shí)現(xiàn)的,比如上面的 find 方法我結(jié)合一下原生提供的 lazy 代理: 

  1. class MainActivity : AppCompatActivity() { 
  2.  
  3.     val onlyTv by lazy { find<TextView>(R.id.onlyTv) } 
  4.  
  5.     override fun onCreate(savedInstanceState: Bundle?) { 
  6.         super.onCreate(savedInstanceState) 
  7.         setContentView(R.layout.activity_main) 
  8.  
  9.         onlyTv.text = "test" 
  10.     } 

以上代碼雖是筆者臨時(shí)異想天開(kāi)的一個(gè)玩法,但是經(jīng)過(guò)測(cè)試毫無(wú)問(wèn)題。

也就是說(shuō),我可以這樣子把 view 的聲明和 findViewById 一同放在聲明的地方。

而且這還只是用原生提供的 lazy 代理,如果愿意,我們完全可以達(dá)成這樣的效果:

  1. val onlyTv by myOwnDelegate(R.id.onlyTv) 

如果我們給 myOwnDelegate 取一個(gè)名字呢? 

  1. val onlyTv by find<TextView>(R.id.onlyTv) 
  2. val onlyTv by findView<TextView>(R.id.onlyTv) 
  3. val onlyTv by findViewById<TextView>(R.id.onlyTv) 

挺棒的對(duì)吧?我還要啥依(zi)賴(xing)注(che)入?

有的時(shí)候,還真的要看我們腦洞夠不夠大。正如你以為這就是我想說(shuō)的全部(其實(shí)明明是我自己寫(xiě)到這里以為這一節(jié)應(yīng)該結(jié)束了)

如果我告訴你,其實(shí)你原本一句代碼都不用寫(xiě),你信嗎?

此處為了作為證據(jù),我還是上截圖吧: 

Picture

毫無(wú) onlyTv 聲明痕跡,也不可能從 AppCompatActivity 繼承而來(lái)。而且當(dāng)你試圖 command/ctrl + 左鍵點(diǎn)擊 onlyTv 想要查看 onlyTv 的來(lái)源的時(shí)候,你會(huì)發(fā)現(xiàn)你跳到了 activity_main 的布局文件: 

Picture

也許眼尖的朋友已經(jīng)發(fā)現(xiàn)了,唯一的真相就是:

  1. import kotlinx.android.synthetic.main.activity_main.* 

請(qǐng)恕在下能力有限,暫時(shí)無(wú)法為大家講解其中緣由。但可以確定的就是,在 anko 的幫助下,你只需要根據(jù)布局的 id 寫(xiě)一句 import 代碼,然后你就可以把布局中的 id 作為 view 對(duì)象的名稱直接進(jìn)行使用。不僅 activity 中可以這樣玩,你甚至可以 viewA.viewB.viewC,所以大可不必?fù)?dān)心 adapter 中應(yīng)當(dāng)怎么寫(xiě)。

沒(méi)有 findViewById,也就減少了空指針;沒(méi)有 cast,則幾乎不會(huì)有類型轉(zhuǎn)換異常。

PS.也許有的朋友會(huì)發(fā)現(xiàn)這和 Google 出品的 databinding 實(shí)在是有異曲同工之妙,那如果我告訴你,databinding 庫(kù)本身就有對(duì) kotlin 的依賴呢?

2.簡(jiǎn)單粗暴的 startActivity

我們?cè)敬蠖际沁@樣子來(lái)做 Activity 跳轉(zhuǎn)的: 

  1. Intent intent = new Intent(LoginActivity.this, MainActivity.class); 
  2. startActivity(intent); 

為了 startActivity,我不得不 new 一個(gè) Intent 出來(lái),特別是當(dāng)我要傳遞參數(shù)的時(shí)候: 

  1. Intent intent = new Intent(LoginActivity.this, MainActivity.class); 
  2. intent.putExtra("name""張三"); 
  3. intent.putExtra("age", 27); 
  4. startActivity(intent); 

不知道大家有木有累覺(jué)不愛(ài)?

在 anko 的幫助下,startActivity 是這樣子的:

  1. startActivity<MainActivity>() 
  2. startActivity<MainActivity>("name" to "張三""age" to 27) 
  3. startActivityForResult<MainActivity>(101, "name" to "張三""age" to 27) 

無(wú)參情況下,只需要在調(diào)用 startActivity 的時(shí)候加一個(gè) Activity 的 Class 泛型來(lái)告知要到哪去。有參也好說(shuō),這個(gè)方法支持你傳入 vararg params: Pair

有沒(méi)有覺(jué)得代碼寫(xiě)起來(lái)、讀起來(lái)流暢了許多?

3.玲瓏小巧的 toast

JAVA 中寫(xiě)一個(gè) toast 大概是這樣子的:

  1. Toast.makeText(context, "this is a toast", Toast.LENGTH_SHORT).show(); 

以上代碼純屬手打,如有錯(cuò)誤請(qǐng)各位指正。

不得不說(shuō)真的是又臭又長(zhǎng),雖然確實(shí)是有很多考量在里面,但是對(duì)于使用來(lái)說(shuō)實(shí)在是太不便利了,而且還很容易忘記***一個(gè) show()。我敢說(shuō)沒(méi)有任何一個(gè)一年以上的 Android 開(kāi)發(fā)者會(huì)不去封裝一個(gè) ToastUtil 的。

封裝之后大概會(huì)是這樣:

  1. ToastUtil.showShort(context, "this is a toast"); 

如果處理一下 context 的問(wèn)題,可以縮短成這樣:

  1. ToastUtil.showShort("this is a toast"); 

有那么一點(diǎn)極簡(jiǎn)的味道了對(duì)吧?

好了,是時(shí)候讓我們看看 anko 是怎么做的了:

  1. context.toast("this is a toast"

如果當(dāng)前已經(jīng)是在 context 上下文中(比如 activity):

  1. toast("this is a toast"

如果你是想要一個(gè)長(zhǎng)時(shí)間的 toast:

  1. longToast("this is a toast"

沒(méi)錯(cuò),就是給 Context 類擴(kuò)展了 toast 和 longToast 方法,用屁股想都知道里面干了什么。只是這樣一來(lái)比任何工具類都來(lái)得更簡(jiǎn)潔更直觀。

4.用 apply 方法進(jìn)行數(shù)據(jù)組合

假設(shè)有如下 A、B、C 三個(gè) class: 

  1. class A(val b: B)  
  2. class B(val c: C)  
  3. class C(val content: String) 

可以看到,A 中有 B,B 中有 C。在實(shí)際開(kāi)發(fā)的時(shí)候,我們有的時(shí)候難免會(huì)遇到比這個(gè)更復(fù)雜的數(shù)據(jù),嵌套層級(jí)很深。這種時(shí)候,用 JAVA 初始化一個(gè) A 類數(shù)據(jù)會(huì)變成一件非常痛苦的事情。例如: 

  1. C c = new C("content"); 
  2. B b = new B(c); 
  3. A a = new A(b); 

這還是 A、B、C 的關(guān)系很單純的情況下,如果有大量數(shù)據(jù)進(jìn)行組合,那么我們會(huì)需要初始化大量的對(duì)象進(jìn)行賦值、修改等操作。如果我描述的不夠清楚的話,大家不妨想一想用 JAVA 代碼布局是一種什么樣的感覺(jué)?

當(dāng)然,在 JAVA 中也是有解決方案的,比如 Android 中常用的 Dialog,就用了 Builder 模式來(lái)進(jìn)行相應(yīng)配置。(說(shuō)到這里,其實(shí)用 Builder 模式基本上也可以說(shuō)是 JAVA 語(yǔ)言的 DSL)

但是在更為復(fù)雜的情況下,即便是有設(shè)計(jì)模式的幫助,也很難保證代碼的可讀性。那么 Kotlin 有什么好方法,或者說(shuō)小技巧來(lái)解決這個(gè)問(wèn)題嗎?

Kotlin 中有一個(gè)名為 apply 的方法,它的源碼是這樣子的: 

  1. @kotlin.internal.InlineOnly 
  2. public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this } 

沒(méi)有 Kotlin 基礎(chǔ)的小伙伴看到這里一定會(huì)有點(diǎn)暈。我們先忽略一部分細(xì)節(jié),把關(guān)鍵的信息提取出來(lái),再改改格式看看: 

  1. public fun <T> T.apply(block: T.() -> Unit): T { 
  2.     block() 
  3.     return this 
  1. 首先,我們可以看出 T 是一個(gè)泛型,而且后面沒(méi)有給 T 增加約束條件,那么這里的 T 可以理解為:我這是在給所有類擴(kuò)展一個(gè)名為『apply』的方法;
  2. ***行***的: T 表明,我最終是要返回一個(gè) T 類。我們也可以看到方法內(nèi)部***的 return this 也能說(shuō)明,其實(shí)***我就是要返回調(diào)用方法的這個(gè)對(duì)象自身;
  3. 在 return this 之前,我執(zhí)行了一句 block(),這意味著 block 本身一定是一個(gè)方法。我們可以看到,apply 方法接收的 block 參數(shù)的類型有點(diǎn)特殊,不是 String 也不是其他什么明確的類型,而是 T.() -> Unit ;
  4. T.() -> Unit 表示的意思是:這是一個(gè) ①上下文在 T 對(duì)象中,②返回一個(gè) Unit 類對(duì)象的方法。由于 Unit 和 JAVA 中的 Void 一致,所以可以理解為不需要返回值。那么這里的 block 的意義就清晰起來(lái)了:一個(gè)執(zhí)行在 T,即調(diào)用 apply 方法的對(duì)象自身當(dāng)中,又不需要返回值的方法。

有了上面的解析,我們?cè)賮?lái)看一下這句代碼: 

  1. val textView = TextView(context).apply { 
  2.     text = "這是文本內(nèi)容" 
  3.     textSize = 16f 

這句代碼就是初始化了一個(gè) TextView,并且在將它賦值給 textView 之前,將自己的文本、字體大小修改了。

或許你會(huì)覺(jué)得這和 JAVA 比起來(lái)并沒(méi)有什么優(yōu)勢(shì)。別著急,我們慢慢來(lái): 

  1. layout.addView(TextView(context).apply { 
  2.     text = "這是文本內(nèi)容" 
  3.     textSize = 16f 
  4. }) 

這樣又如何呢?我并不需要聲明一個(gè)變量或者常量來(lái)持有這個(gè)對(duì)象才能去做修改操作。

上面的A、B、C 問(wèn)題用 Kotlin 來(lái)實(shí)現(xiàn)是可以這么寫(xiě)的: 

  1. val a = A().apply { 
  2.     b = B().apply { 
  3.         c = C("content"
  4.     } 

我只聲明了一個(gè) a 對(duì)象,然后初始化了一個(gè) A,在這個(gè)初始化的對(duì)象中先給 B 賦值,然后再提交給了 a。B 中的 C 也是如此。當(dāng)組合變得復(fù)雜的時(shí)候,我也能保持我的可讀性: 

  1. val a = A().apply { 
  2.     b = B().apply { 
  3.         c = C("content"
  4.     } 
  5.  
  6.     d = D().apply { 
  7.         b = B().apply { 
  8.             c = C("test"
  9.         } 
  10.  
  11.         e = E("test"
  12.     } 

上面的代碼用 JAVA 實(shí)現(xiàn)會(huì)是如何一番場(chǎng)景?反正我是想一想就已經(jīng)暈了。說(shuō)到底,這個(gè)小技巧也就是 ①擴(kuò)展方法 + ②高階函數(shù) 兩個(gè)特性組合在一起實(shí)現(xiàn)的效果。

5.利用高階函數(shù)搞事情

先看代碼 

  1. inline fun debug(code: () -> Unit) { 
  2.     if (BuildConfig.DEBUG) { 
  3.         code() 
  4.     } 
  5. ... 
  6. // Application 中 
  7. debug { 
  8.     Timber.plant(Timber.DebugTree()) 

上述代碼是先定義了一個(gè)全局的名為 debug 的方法,這個(gè)方法接收一個(gè)方法作為參數(shù),命名為 code。然后在方法體內(nèi)部,我先判斷當(dāng)前是不是 DEBUG 版本,如果是,再調(diào)用傳入的 code 方法。

而后我們?cè)?Application 中,debug 方法就成為了依據(jù)條件執(zhí)行代碼的關(guān)鍵字。僅當(dāng) DEBUG 版本的時(shí)候,我才初始化 Timber 這個(gè)日志庫(kù)。

如果這還不夠體現(xiàn)有點(diǎn)的話,那么可以再看看下面一段: 

  1. supportsLollipop { 
  2.     window.statusBarColor = Color.TRANSPARENT 
  3.     window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE 

當(dāng)系統(tǒng)版本在 Lollipop 之上時(shí)才去做沉浸式狀態(tài)欄。系統(tǒng) api 經(jīng)常會(huì)有版本的限制,相對(duì)于一個(gè) supportsLollipop 關(guān)鍵字, 我想一定不是所有人都希望每次都去寫(xiě): 

  1. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 
  2.     // do something 

諸如此類的場(chǎng)景和可以自創(chuàng)的 關(guān)鍵字/代碼塊 還有很多。

例如: 

  1. inline fun handleException(code : () -> Unit) { 
  2.     try { 
  3.         code() 
  4.     } catch (e : Exception) { 
  5.         e.printStackTrace() 
  6.     } 
  7. ... 
  8. handleException { 
  9.      println(Integer.parseInt("這明顯不是數(shù)字")) 

雖然大都可以用 if(xxxxUtil.isxxxx()) 來(lái)湊合,但是既然有了更好的方案,那還何必湊合呢?

6.用擴(kuò)展方法替代工具類

曾幾何時(shí),我做字符串判斷的時(shí)候一定會(huì)寫(xiě)一個(gè)工具類,在這個(gè)工具類里充斥著各種各樣的判斷方法。而在 Kotlin 中,可以用擴(kuò)展方法來(lái)替代。下面是我項(xiàng)目中 String 擴(kuò)展方法的一部分: 

  1. fun String.isName(): Boolean { 
  2.     if (isEmpty() || length > 10 || contains(" ")) { 
  3.         return false 
  4.     } 
  5.  
  6.     val reg = Regex("^[a-zA-Z0-9\u4e00-\u9fa5]+$"
  7.     return reg.matches(this) 
  8.  
  9. fun String.isPassword(): Boolean { 
  10.     return length in 6..12 
  11.  
  12. fun String.isNumber(): Boolean { 
  13.     val regEx = "^-?[0-9]+$" 
  14.     val pat = Pattern.compile(regEx) 
  15.     val mat = pat.matcher(this) 
  16.  
  17.     return mat.find() 
  18. ... 
  19.  
  20. println("張三".isName()) 
  21. println("123abc".isPassword()) 
  22. println("123456".isNumber()) 

7.自動(dòng) getter、setter 使得代碼更精簡(jiǎn)

以 TextView 舉例,JAVA 代碼中獲取文本、設(shè)置文本的代碼分別為: 

  1. String text = textView.getText().toString(); 
  2. textView.setText("new text"); 

Kotlin 中是這樣寫(xiě)的: 

  1. val text = textView.text  
  2. textView.text = "new text" 

如果 TextView 是一個(gè)原生的 Kotlin class,那么是沒(méi)有 getText 和 setText 兩個(gè)方法的,而是一個(gè) text 屬性。盡管此處的TextView 是 JAVA class,源碼中有g(shù)etText 和 setText 兩個(gè)方法,Kotlin 也做了類似映射的處理。當(dāng)這個(gè) text 屬性在等號(hào)右邊的時(shí)候,就是在提取 text 屬性(此處映射為 getText);當(dāng)在等號(hào)左邊的時(shí)候,就是在賦值(setText)。

說(shuō)到這里我又想起了上一篇文章中提到的 Preference 代理,其實(shí)也有一定關(guān)聯(lián),那就是當(dāng)一個(gè)屬性在等號(hào)左邊和右邊的時(shí)候,不同于 JAVA 中一定是賦值操作,在 Kotlin 中則有可能會(huì)觸發(fā)一些別的。

未完待續(xù)...

補(bǔ)充:

翻看之前的項(xiàng)目,發(fā)現(xiàn)有如下代碼可做對(duì)比:

構(gòu)建并顯示 BottomSheet 

  1. Builder 版 
  2.  
  3. BottomSheet.Builder(this@ShareActivity, R.style.ShareSheetStyle) 
  4.         .sheet(999, R.drawable.share_circle,  R.string.wXSceneTimeline) 
  5.         .sheet(998, R.drawable.share_freind,  R.string.wXSceneSession) 
  6.         .listener { _, id -> 
  7.             shareTo(bitmap, target = when(id) { 
  8.                 999 -> SendMessageToWX.Req.WXSceneTimeline 
  9.                 998 -> SendMessageToWX.Req.WXSceneSession 
  10.                 else -> throw Exception("it can not happen"
  11.             }) 
  12.         } 
  13.         .build() 
  14.         .show() 
  15.  
  16. DSL 版 
  17.  
  18. showBottomSheet { 
  19.     style = R.style.ShareSheetStyle 
  20.  
  21.     sheet { 
  22.         icon = R.drawable.share_circle 
  23.         text = R.string.wXSceneTimeline 
  24.  
  25.         selected { 
  26.             shareTo(bitmap, SendMessageToWX.Req.WXSceneTimeline) 
  27.         } 
  28.     } 
  29.  
  30.     sheet { 
  31.         icon = R.drawable.share_freind 
  32.         text = R.string.wXSceneSession 
  33.  
  34.         selected { 
  35.             shareTo(bitmap, SendMessageToWX.Req.WXSceneTimeline) 
  36.         } 
  37.     } 

apply 構(gòu)建數(shù)據(jù)實(shí)例(微信分享)

  1. 普通版 
  2.  
  3. val obj = WXImageObject(bitmap) 
  4. val thumb = ...... 
  5. bitmap.recycle() 
  6.  
  7. val msg = WXMediaMessage() 
  8. msg.mediaObject = obj 
  9. msg.thumbData = thumb 
  10.  
  11. val req = SendMessageToWX.Req() 
  12. req.transaction = "share" 
  13. req.scene = target 
  14. req.message = msg 
  15.  
  16. WxObject.api.sendReq(req) 
  17.  
  18. DSL 版 
  19.  
  20. WxObject.api.sendReq( 
  21.         SendMessageToWX.Req().apply { 
  22.             transaction = "share" 
  23.             scene = target 
  24.             message = WXMediaMessage().apply { 
  25.                 mediaObject = WXImageObject(bitmap) 
  26.                 thumbData = ...... 
  27.                 bitmap.recycle() 
  28.             } 
  29.         } 

要是有人能只看普通版的,3秒之內(nèi)看清結(jié)構(gòu)關(guān)系,那一定是天才。。

責(zé)任編輯:未麗燕 來(lái)源: 安卓巴士
相關(guān)推薦

2018-05-30 15:22:03

KotlinAndroid開(kāi)發(fā)

2019-07-08 17:34:29

共享辦公ideaPod文印

2019-04-03 14:51:18

CPU性能工藝

2015-11-03 08:51:21

程序員怪物

2020-11-06 17:49:38

程序員技術(shù)開(kāi)發(fā)

2015-09-09 09:41:28

十年代碼

2017-03-10 09:09:41

C語(yǔ)言體驗(yàn)

2015-12-03 09:23:25

程序員產(chǎn)品經(jīng)理

2015-04-08 10:40:09

2021-01-14 21:46:02

Vue.jsReact框架

2010-08-02 13:30:34

移動(dòng)開(kāi)發(fā)移動(dòng)開(kāi)發(fā)平臺(tái)

2017-04-06 15:00:38

編程語(yǔ)言

2020-04-07 08:05:51

程序員互聯(lián)網(wǎng)職業(yè)

2017-08-17 13:14:01

2019-01-11 10:39:24

軟件架構(gòu)虛擬空間機(jī)器人

2015-02-04 10:55:14

2025-01-06 08:00:54

2016-08-30 21:09:33

2014-02-25 09:55:07

敏捷開(kāi)發(fā)

2015-01-21 15:35:58

開(kāi)源
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)