Kotlin實現《第一行代碼》案例「酷歐天氣」

 

看過《第一行代碼》的朋友應該知道「酷歐天氣」,做者郭神用整整一章的內容來說述其從無到有的過程。java

最近正好看完該書的第二版(也有人稱「第二行代碼」),嘗試着將項目中的Java代碼用Kotlin實現。git

 

原項目獲取點這裏github

Kotlin實現點這裏json

 

除了將Java轉爲Kotlin外,界面與資源的定義也略微作了調整,可是功能上沒有變化(這部分後續進行完善,書中有提到能夠改進的地方)。api

 

1. 界面調整數組

1.1 將「預告」和「空氣質量」兩個模塊的位置互換,「空氣質量」和「溫度/天氣狀態」放在一塊兒感受會比較直觀。緩存

1.2 將模塊佈局的背景色調整爲主題色,與標題欄背景色保持一致。ide

效果圖以下:工具

   

    

 

2. 資源定義佈局

2.1 將dimen、color及string由直接使用字面值改成先將資源值定義在相應的文件中,而後在佈局文件中用@type/value的形式引用。

2.2 將表示左/右方向的xxxLeft、xxxRight改成xxxStart、xxxEnd,適配Android高版本設備左/右方向問題。

具體細節能夠下載代碼進行查看,這樣作的目的是增強應用的適配性與可維護性。

 

3. Java->Kotlin

Kotlin的基礎知識能夠閱讀官方文檔或者我以前的文章進行了解,下面只描述和原項目代碼結構上的調整,或者Java轉爲Kotlin值得注意的地方。

3.1 Android Studio 3.0開始已經集成Kotlin(2.0須要手動安裝插件),不過有兩種狀況:

a 在非Kotlin項目的基礎上進行Kotlin編碼,得先在Project和Module的build.gradle文件中引入依賴;

b 新建支持Kotlin的項目,那麼依賴是默認引入的,直接編碼便可;

3.2 將「散落」在多個文件中的常量提取到HttpUtil類中:

1 object HttpUtil {
2     val Url = "http://guolin.tech/api/weather"
3     val Key = "key=bc0418b57b2d4918819d3974ac1285d9"
4     val Bing = "http://guolin.tech/api/bing_pic"
5     val China = "http://guolin.tech/api/china"
6     ...
7 }

Kotlin中推薦用object修飾工具類,裏面的成員默認是static的。常量用val,變量用var。

可是在使用HttpUtil.Url時並不像Java那樣是引用類所屬的靜態變量,而是經過單例對象(lazy-init機制,第一次使用時進行建立)來引用成員變量,瞭解更多點這裏

3.3 for (int i = start; i < end; ++i) {}改成for (i in start..end - 1) {}:

1 for (i in start..end - 1) {
2     ...
3 }

in和..的組合遍歷的區間是[start, end-1],即最後一個元素是包含在內的,不注意容易引發下標越界問題。變量i是自動推斷類型,不須要顯式聲明。

3.4 將只有數據的類由class改成data class,如城市類的定義:

1 data class City(var id: Int = 0,
2                 var cityName: String? = null,
3                 var cityCode: Int = 0,
4                 var provinceId: Int = 0) : DataSupport() {}

這種類通常稱爲Bean類(只擁有數據而沒有操做),編譯器會根據聲明的屬性自動生成對應的equal()、hashCode()方法及pair,瞭解更多點這裏

3.5 Kotlin中==和equals效果是相同的(比較的是對象的值),比較引用的是===。那麼咱們在比較字串的值時能夠簡化代碼了:

1 if ("ok" == it.status) {
2     ...
3 }

將常量字面值寫在前面也是一種好習慣,由於str.equals("ok")的形式,當str爲空時會報空指針異常。還有,基本類型比較時須要顯式轉換,不然也會報錯,如1 == 1L須要寫成1.toLong() == 1L。瞭解更多點這裏

3.6 打開應用時會進行天氣緩存信息的讀取,若是以前使用過應用,那麼就直接顯示上次的天氣信息;若是沒有則進行城市的選擇,並進行對應天氣的獲取。這裏經過?.let語法來代替下一步操做前的null判斷:

1 val prefs = PreferenceManager.getDefaultSharedPreferences(this@MainActivity)
2 val weather = prefs.getString("weather", null)
3 weather?.let {
4   val intent = Intent(this, WeatherActivity::class.java)
5   startActivity(intent)
6   finish()
7 }

若是weather變量值爲null,那麼就不會往下執行,相似的用法還有獲取字串長度str?.length,當str爲null時不日後取length值就能避免空指針異常。

3.7 by lazy延遲加載

1 val drawerLayout by lazy {
2     findViewById(R.id.drawer_layout) as DrawerLayout
3 }
4 
5 val swipeRefresh by lazy {
6     findViewById(R.id.swipe_refresh) as SwipeRefreshLayout
7 }

以前的作法通常是先定義變量,而後在onCreate等重載方法中利用findViewById將變量和id進行綁定。如今Kotlin中提供了延遲加載機制,只有在佈局環境初始化完成後,纔會創建變量和id的聯繫。不論是綁定的時機,仍是這個過程的代碼實現,都變得一目瞭然。

3.8 when、_和is

 1 list_view.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
 2     when (currentLevel) {
 3         LEVEL_PROVINCE -> {
 4             selectedProvince = provinceList!![position]
 5             queryCities()
 6         }
 7         LEVEL_CITY -> {
 8             selectedCity = cityList!![position]
 9             queryCounties()
10         }
11         LEVEL_COUNTY -> {
12             val weatherId = countyList!![position].weatherId
13             weatherId?.let {
14                 when (activity) {
15                     is MainActivity -> {
16                         val intent = Intent(activity, WeatherActivity::class.java)
17                         intent.putExtra("weather_id", weatherId)
18                         startActivity(intent)
19                         activity.finish()
20                     }
21                     is WeatherActivity -> {
22                         val activity = activity as WeatherActivity
23                         activity.drawerLayout.closeDrawers()
24                         activity.swipeRefresh.isRefreshing = true
25                         activity.requestWeather(weatherId)
26                     }
27                     else -> {}
28                 }
29             }
30         }
31         else -> {}
32     }
33 }

這段代碼的功能是給列表項添加點擊響應,也比以往的寫法簡化不少,提一下代碼中用到的三個點。

a when用來替換switch case,省去了case:、break及default,畢竟每個分支結尾處都要記得寫上break仍是不爽的;

b 方法參數_,當後面不會使用的狀況下能夠寫成下劃線,優勢是過多不用的參數能夠簡寫,缺點是可讀性很差,後續若是參數被使用仍是須要加上名稱;

c is和as,前者是判斷類型是否匹配,後者是類型轉換。

3.9 map映射和it

 1 provinceList?.let {
 2     if (it.isNotEmpty()) {
 3         dataList.clear()
 4         it.map {
 5             dataList.add(it.provinceName!!)
 6         }
 7         adapter.notifyDataSetChanged()
 8         list_view.setSelection(0)
 9         currentLevel = LEVEL_PROVINCE
10         return
11     }
12 }

以前提到過let的用法,其實和之搭配的還有it,表明let前面的變量,好比it.isNotEmpty()就是判斷列表變量provinceList是否爲空。而it.map的做用是遍歷列表/數組,元素又能夠用it表示,有點像it的嵌套,Kotlin會自動區分it表明的對象。

3.10 @SerializedName註解(Java中概念),用於將json內容中的屬性名和對應類的成員變量名進行一一對應,畢竟json中字段名的可讀性是不能保證的。用法以下:

1 class Now {
2     @SerializedName("tmp")
3     var temperature: String? = null
4     ...    
5 }

3.11 $name和${name}獲取變量值

val address = "${HttpUtil.China}/$provinceCode"

這種用法在字串拼接時特別適用,不用再經過「+」號寫一長串的代碼來進行各字串的拼接,直接將值的獲取放到字串中。

$name當name是普通變量時使用,${name}當name是類屬性或者數組/列表元素時使用,還能夠這樣使用:
1 val a = 10
2 println("a > 10 is ${if (a > 10) "true" else "false"}")

輸出結果爲false,將變量值的判斷和結果都放在${}中。

 

4. 總結

上面僅僅介紹了關於Kotlin語言的部分用法,項目中還用到了Glide、Litepal、OkHttp及Gson等流行庫,以及在Service中用AlarmManager來後臺按期更新天氣數據。感興趣的話建議查看相關源碼,會有收穫的。

相關文章
相關標籤/搜索