起這個文內標題的原因很簡單,就是對Kotlin抱有希望——能使Android的開發更簡潔、高效及安全。知道Kotlin是從簡書的一篇短文,越來越覺得將自己學習、實踐的過程和想法總結成文字分享出來,不管文筆好壞,內容多少,若能拋磚引玉就足以。所以,感謝寫了那么多精彩文章的大神,而我才剛從山腳啟程。
項目源碼放在Github上,感興趣的朋友可以下載,歡迎送星和討論。Demo運行的動態效果圖如下:
1. Kotlin在Android Studio中的環境配置
按照下面兩篇文章的介紹操作,就能完成Kotlin在Android Studio中的環境配置(Eclipse就不推薦了),並能學習到基礎語法和使用案例。如果有問題可以百度、谷歌或參考分享的項目源碼中的Project及App的build.gradle設置,也可以留言大家一起討論。
http://kotlinlang.org/docs/tutorials/kotlin-android.html
http://kotlinlang.org/docs/tutorials/android-plugin.html
其實和引入普通插件類似,說簡單點就是做兩件事情:
a 安裝Kotlin插件;
b Porject和App的build.gradle文件中添加引用;
如果配置沒問題了,在Studio工具欄的Code欄最下方會出現可將Java代碼轉為Kotlin代碼的選擇項:
如果本身就是kt格式,那該選項就是灰色不可點擊的。
2. Kotlin學習與編碼總結
開發環境OK之后,作為剛接觸Kotlin的初學者,有兩種選擇:
a 直接新建Android項目與Kotlin文件,在學習的同時從無到有得敲Kotlin代碼;
b 打開之前的Android項目,通過上述的轉化工具先將有一個和若干Java文件轉為Kotlin代碼,通過閱讀轉化后的代碼可以快速熟悉Android項目的Kotlin的代碼;
推薦從第二種方法開始,比較簡單,而且一般在將Java代碼轉過來之后會有一些小錯誤或者警告,在修改的過程中進步也是蠻大的。注明一下:Kotlin不只是能在Android項目中替代Java,在其官網有詳細的介紹。
下面着重講述一下因為Kotlin使得代碼變得簡潔、安全以及巧妙的幾個點,這門新語言日漸成熟,不可能將其特點通過一兩篇文章就能覆蓋到。隨着學習的深入,之后會再進行補充。
2.1 text & setText()
1 text1.text = (Editable e)
1 text1.setText(CharSequence text)
先來個簡單的,text1是某布局中TextView組件的id。就這樣一句代碼,就可以完成文本的設置了,沒有TextView類對象聲明,不用調用findViewById()查找,是不是簡潔好多。雖說這中間有些步驟還是需要Kotlin去默默處理,但是作為開發者,效率明顯提升了。一般常用的是后面一種,因為CharSequence或者String的使用頻率較高。但是Kotlin的簡潔形式都趨向於obj.field,而不是setField()這樣的方法,只不過這里將CharSequence轉為Editable稍微麻煩了一點:
1 text1.text = Editable.Factory().newEditable(message)
2.2 @ & when () {}
1 login_image_sel.setOnClickListener(this@LoginFragment) 2 login_in.setOnClickListener(this@LoginFragment) 3 login_reg.setOnClickListener(this@LoginFragment) 4 login_out.setOnClickListener(this@LoginFragment)
給四個id代表的組件設置點擊監聽,方法參數為View.OnClickListener,如果類已經繼承了它並實現了其onClick()方法,那么直接寫成代表自己的this即可。而@Name部分則是強調類名稱(不寫也不影響編譯、運行),但是寫上之后可讀性更強了,可以說此@是寫成開發者(自己或未來看代碼的人)看的。
1 override fun onClick(view: View) { 2 val id = view.id 3 when (id) { 4 R.id.login_image_sel -> selectImageBtn() 5 R.id.login_in -> loginInBtn() 6 R.id.login_reg -> loginRegBtn() 7 R.id.login_out -> loginOutBtn() 8 else -> { } 9 } 10 }
第一行需要先解釋,Java代碼是這樣的:
1 @Override 2 public void onClick(View view) {}
有幾個地方不同:
a @Override->override;
b default->public,Kotlin默認的訪問限制是public,所以可以省略;
c void->: Unit,Kotlin無返回值可以省略,也可以寫成: Unit,當然看個人習慣了;
1 override fun onClick(view: View): Unit {
d View view->view: View,形參聲明形式,具體的后面會再提到;
Kotlin用when解放了switch,仔細琢磨一下可以發現,不管是代碼形式和行數都簡潔了不少。告別了原先Java的case、“:”、break及default,我認為最直接的好處是進入when之后,只會執行匹配項對應的"->"后面那一個分支,不用每次都要小心翼翼地想在哪加break。
2.3 var & val
1 val id = view.id
接着上面的話題,來看一下變量的定義。看到上面這句代碼,熟悉腳本語言的朋友會有親切感,所以有時候覺得Kotlin是在慢慢地將各門語言的優點結合起來。
val和var對應,前者定義不可能變量,后者定義可改變量。這時候將Java的final拿出來最合適了,Kotlin中限定不可變的重擔是由val來完成,它們限定的變量必須在聲明時就賦值(類直接屬性除外,具體的后面會再提到)。
那么上面說到方法形參時(view: View),view后面有類型View,其實在聲明變量時也是如此,來看幾個例子:
1 var int1: Int = 1 2 var int2 = 2 3 var str3: String? = null 4 val str4: String? = null
聲明了四個變量:兩個可變Int型,一個可變String型,一個不可變String型。既然不可變,那么在后面再次賦值時就會報錯了:
1 int1 = 2 2 int2 = 3 3 str3 = "Hello" 4 str4 = "Kotlin" //會標紅線,鼠標移入顯示"Val cannot be reassigned"
實踐過就會發現,Kotlin中不能再像Java那樣聲明變量了,比如:
1 var int3 2 var int4: Int
這兩種都是不行的,會提示"Property must be initialized or be abstract"。
再來看是類型后的那個問號(String? = null),它表示聲明的變量是否允許為null。這要和另一種情況區分開:聲明變量的時候還不確定其值是什么,解決方式可以是先賦一個不影響程序的值。比如:
1 var str3: String = "will be reassigned later"
": String"類型限定部分可以不加,編譯器會自動推斷,這樣處理就沒有“?”。
那么有人疑問加“?”和不加的根本區別在哪?就在於程序運行過程中對變量的賦值,如果給沒有加“?”的變量賦值了null,程序就會異常。一般用在函數的形參中,比如定義的方法形參如下:
1 fun testNotNull(str: String) {}
調用時傳給形參str的值是不能為null的,這一特性非常有用。因為在大部分應用場景下都可以確定需要的參數是否可以null,比如讀取圖像的路徑不能為空,通過索引訪問元素時列表不能為空等等。這不是說可以完美地解決因為null引起的異常,而是可以將異常的點提前,或者說變量容易發現與消除。至於在不同場景怎么用,還得深入研究,並不是全部限定為NonNull就是最合適的。當然,非空限制在Java代碼中也可以通過注解@NonNull來實現。
“?”還有一個很有用的地方是方法返回值:
1 fun getStringLength(obj: Any): Int? { 2 if (obj is String) 3 return obj.length // no cast to String is needed 4 return null 5 }
在Java中聲明為int返回值類型后,是不允許返回null值的,可能會在沒有想要的結果時返回一個標記數值。而在Kotlin中只要檢查返回值是否為null,如果不是則返回值就是希望得到的結果。上面的代碼還可以簡寫為:
1 fun getStringLength(obj: Any): Int? = if (obj is String) obj.length else null
這里有一個新的知識,is的作用是判斷obj是否是String類型實例應用,如果不是則直接返回null,Java是instanceof。
2.4 Any
Any有點像Java中的Object,對象的祖先。直接上例子:
1 fun showLog(message: Any?) { 2 Log.i(LOG_TAG, message?.toString()) 3 }
這是在Utils文件中自定義的一個實現log的方法,形參message的類型時Any?,正好鞏固一下上一條的概念。對於傳入的實參,可以是null,也可以是任意類型的變量值;重點在於message后面的那個“?”,它會判斷message是否為null,如果是則直接返回字串“null”,如果不是才去調用toString()方法。注意這里假設傳入的實參對象繼承或重寫了toString(),否則可能會出錯。
2.5 Custom View
這里說的並不是熟悉的自定義一個圓形View,然后在xml或者Java中進行使用,而是直接在代碼中生成布局與組件,這又是Kotlin的一個優點。來看定制一個Dialog的代碼:
1 val dialog = Dialog(mContext, R.style.DialogNoTitle) 2 dialog.setContentView(mContext.linearLayout { 3 imageView { 4 Utils.setImageToView(mContext, null, imageUri, this) 5 onClick { 6 dialog.dismiss() 7 } 8 } 9 }) 10 11 dialog.show()
代碼中的Style和Utils部分可以在項目源碼中查看,這里主要針對Kotlin定義布局部分。動態添加線性布局和一個圖像組件只需要聲明一個名稱即可,分別為linearLayout和imageView。可以理解為包含的元素以{}為界,imageView屬於linearLayout,而onClick {}和this則是屬於imageView。測試發現,顯示的Dialog默認就是居中的,想達到其他效果進行相應的調整即可。
2.6 Map
1 `object`.map { 2 var bulletinT = ReceiveBulletin(it.teacherName, 3 it.updatedAt, 4 it.bulletinContent, 5 it.bulletinImage?.fileUrl) 6 //do something 7 }
這里的`object`可以是列表數據,也可以是數組等其他集合類型。map的作用就是遍歷集合中的每一個元素,對其在{}中進行處理,而每個元素的臨時名稱為“it”。這樣,是不是又可以不用看到for或者Iterator了。
2.7 Class
2.3中提到過val聲明的變量也可以先不賦值,這種情況會在Class的聲明時出現:
1 class BulletinAdapter(private val mContext: Context, 2 private val mBulletins: ArrayList<ReceiveBulletin>) 3 : RecyclerView.Adapter<BulletinAdapter.ViewHolder>() {}
自定義了一個和RecyclerView結合使用的Adapter類,類的繼承由Java的extends變成了冒號“:”,仿佛進入了C++的世界。
類名后面竟然跟了一個括號“()”,而且還多了那么多奇怪的參數,Kotlin的解釋是這樣寫相當於快捷的構造函數。其實可以理解為primary constructor方法省略了名稱,如果方法前有注解等特殊修改,那么名稱“constructor”是不能省略的。之所以說類后面跟着的方法為primary,是因為在類中還可以實現secondary constructors,以后用到時再深挖吧。而init是在類對象構造時就會調用一次,僅此一次,所以可以作為類實例時的標記,比如打印log:
1 init { 2 Utils.showLog("Create a BulletinAdapter object") 3 }
val就不解釋了,傳入的實參賦給形參變量后,在本類中是不允許改變的。但是集合有點特殊,比如重新給mContext賦值不行,但是給mBulletins通過add()方法添加元素是可以的,這里涉及到對象指向的地址和包含的元素問題,先不展開。
添加了private訪問限定符的目的和Java中類似,在類外不可見,即不允許在類外面對變量進行訪問與更改。
2.8 if () {} else () {}
之前2.2中when分支->后面代碼都只有一句,如果有兩句呢?先看看if else的情形:
1 if (position == itemCount - 1) 2 itemView.bulletin_divider.visibility = View.GONE 3 else 4 itemView.bulletin_divider.visibility = View.VISIBLE
當分支下有多個語句時,必須將屬於分支的所有代碼都包含在{}中,否則會出現下面的else匹配不到if的錯誤,這點和Java中的也是相同的。最后提一下Kotlin中不推薦在語句后寫分號“;”,寫上也沒錯,只是給變量名下面畫一條灰色的提醒而已。
3. 總結
3.1 項目介紹
開頭給出了項目源碼下載地址和運行動態效果圖,現在來進行簡單的介紹。自己學習過程中練手的不能叫做App,確切地應該叫Demo。
Demo一共三個頁面:消息接收、消息發送及用戶信息。
a 消息接收:打開程序時,會從雲數據庫中讀取消息,如果有則加載,沒有則顯示提示信息(如稍后下拉刷新等);一條信息包括發送者頭像、名稱、發送時間、信息內容(文字或圖片至少有其一);如果有圖片內容,則點擊后放大;消息的接收沒有用戶登錄要求;
b 發送消息:只有注冊並登錄的用戶才能進行消息的發送;發送的內容至少要有文字或圖片內容其中之一;
c 用戶信息:先進行注冊,不能設置數據庫中已存在的用戶名,一定要選擇頭像,成功后一般會自動登錄;登陸后才能進行注銷操作;注銷后才能進行再次登錄操作;
這里說的雲數據庫指的是項目中用到的免費的Bmob雲平台,比較適合個人練習用,可以自己建表及定義表中的信息。
3.2 Kotlin未來學習計划
文中提到的和項目中用到的知識點,都只是Kotlin語言的冰山一角。還有更有趣、奇妙的的地方值得去發現,相信以后可以在項目中將以前習以為常的、繁瑣的代碼進行更簡潔、高效的實現。