一、前言
使用kotlin開發(fā)項(xiàng)目已經(jīng)有一段時(shí)間,在使用kotlin的過程中,發(fā)現(xiàn)了許多很方便的語法糖,可以有效簡(jiǎn)潔代碼。在這里做個(gè)總結(jié)記錄,方便后續(xù)查閱。
二、kotlin基礎(chǔ)語法常識(shí)
1、 新建對(duì)象不需要new關(guān)鍵字。語句也不需要;結(jié)尾,但加上;也不會(huì)報(bào)錯(cuò)。
//java
StringBuffer buffer = new StringBuffer();
//kotlin
var buffer = StringBuffer()
2、var是kotlin保留字,用于聲明變量。與之相對(duì)的是val,表明引用不可變,但可以改變對(duì)象的值,使用var或val聲明變量后,kotlin會(huì)根據(jù)上下文進(jìn)行類型推斷。
var name = ""
var age = 18
3、kotlin 用 : 取代了implements和extends保留字。
//java
public class CheckableActivity extends Activity {
final public void setStatus(){}
}
public class MyListener implements View.OnClickListener{
@Override
public void onClick(View v) {
}
}
//kotlin
class CirclePartyListActivity : Activity() {
fun setStatus(){}
}
class MyListener : View.OnClickListener{
override fun onClick(v: View?) {
}
}
4、kotlin的lambda也更加簡(jiǎn)約:
//正常情況
view.setOnClickListener({ v -> v.setVisibility(View.INVISIBLE) })
//當(dāng)lambda是函數(shù)的最后一個(gè)參數(shù)時(shí),可以將其移到括號(hào)外面
view.setOnClickListener() { v -> v.setVisibility(View.INVISIBLE) }
//當(dāng)函數(shù)只有一個(gè)lambda類型的參數(shù),可以去省去括號(hào)
view.setOnClickListener { v -> v.setVisibility(View.INVISIBLE) }
//當(dāng)lambda只有一個(gè)參數(shù),可省去參數(shù)列表,在表達(dá)式部分用it引用參數(shù)
view.setOnClickListener { it.setVisibility(View.INVISIBLE) }
5、所有定義了setter和getter方法的字段,在kotlin中都可以通過賦值語法來直接操作
view.setOnClickListener { it.visibility = View.INVISIBLE }
6、空安全:kotlin中使用?.作為安全調(diào)用運(yùn)算符,將判空檢查和方法調(diào)用合并為一個(gè)操作。只有當(dāng)調(diào)用變量本身不為null時(shí),方法調(diào)用才成立,否則整個(gè)表達(dá)式返回null。
//java
public class Address {
private String country;
public String getCountry() {
return country;
}
}
public class Company {
private Address address;
public Address getAddress() {
return address;
}
}
public class Person {
private Company company;
public String getCountry() {
String country = null;
//需要多次判空
if (company != null) {
if (company.getAddress() != null) {
country = company.getAddress().getCountry();
}
}
return country;
}
}
//kotlin
fun getCountry(): String? {
return person.company?.address?.country
}
//另外也可以通過在可空的后面通過?:指定為null時(shí)返回的默認(rèn)值
fun getCountry(): String {
return person.company?.address?.country ?:""
}
7、擴(kuò)展函數(shù):擴(kuò)展函數(shù)是一個(gè)類的成員函數(shù),但它定義在類體外面。這樣定義的好處是,可以在任何時(shí)候任何地方給類添加功能。
在擴(kuò)展函數(shù)中,可以像類的其他成員函數(shù)一樣訪問類的屬性和方法(除了被private和protected修飾的成員)。還可以通過this引用類的實(shí)例,也可以省略它,把上段代碼進(jìn)一步簡(jiǎn)化:
fun Person.getCountry(): String? {
return this.company?.address?.country
}
8、kotlin中常用的擴(kuò)展函數(shù)
| 函數(shù) | 返回值 | 調(diào)用者角色 | 如何引用調(diào)用者 |
|---|---|---|---|
| also | 調(diào)用者本身 | 作為lambda參數(shù) | it |
| apply | 調(diào)用者本身 | 作為lambda接收者 | this |
| let | lambda返回值 | 作為lambda參數(shù) | it |
| with | lambda返回值 | 作為lambda接收者 | this |
apply舉例:
//java
Intent intent = new Intent(this, Activity1.class);
intent.setAction("actionA");
Bundle bundle = new Bundle();
bundle.putString("content","hello");
bundle.putString("sender","taylor");
intent.putExtras(bundle);
startActivity(intent);
//kotlin
Intent(this,Activity1::class.java).apply {
action = "actionA"
putExtras(Bundle().apply {
putString("content","hello")
putString("sender","taylor")
})
startActivity(this)
}
also舉例:
//java
String var2 = "testLet";
int var4 = var2.length();
System.out.println(var4);
System.out.println(var2);
//kotlin
val result = "testLet".also {
println(it.length)
}
println(result)
with舉例:
//java
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
ArticleSnippet item = getItem(position);
if (item == null) {
return;
}
holder.tvNewsTitle.setText(StringUtils.trimToEmpty(item.titleEn));
holder.tvNewsSummary.setText(StringUtils.trimToEmpty(item.summary));
String gradeInfo = "難度:" + item.gradeInfo;
String wordCount = "單詞數(shù):" + item.length;
String reviewNum = "讀后感:" + item.numReviews;
String extraInfo = gradeInfo + " | " + wordCount + " | " + reviewNum;
holder.tvExtraInfo.setText(extraInfo);
...
}
//kotlin
override fun onBindViewHolder(holder: ViewHolder, position: Int){
val item = getItem(position)?: return
with(item){
holder.tvNewsTitle.text = StringUtils.trimToEmpty(titleEn)
holder.tvNewsSummary.text = StringUtils.trimToEmpty(summary)
holder.tvExtraInf.text = "難度:$gradeInfo | 單詞數(shù):$length | 讀后感: $numReviews"
...
}
}
let舉例:
//java
if(mContext!=null){
textView.setText(mContext.getString(R.string.app_name))
...
}
//kotlin
mContext?.let{
textView.setText(it.getString(R.string.app_name))
}
9、集合數(shù)據(jù)操作
kotlin中集合的操作符很多,此處只列舉使用頻率較高的幾個(gè),感興趣的可以看下這篇文章
- forEach() 和 forEachIndexed() 循環(huán)遍歷
val list = mutableListOf<String>().apply{
add("A")
add("B")
add("C")
add("D")
add("E")
}
//遍歷
list.forEach{print(it)}
//遍歷帶角標(biāo)
list.forEachIndexed{content:String,index:Int -> print("$content $index")}
- map() 在集合的每一個(gè)元素上應(yīng)用一個(gè)自定義的變化
list.map {
it.apply {
name = name.replace(name.first(), name.first().toUpperCase())
}
}
- filter() 只保留滿足條件的集合元素
val reslutList = list.filter {it.length > 3}
- toSet() 將集合元素去重
三、ktx擴(kuò)展包
Android KTX 是包含在 Android Jetpack 及其他 Android 庫中的一組 Kotlin 擴(kuò)展程序。KTX 擴(kuò)展程序可以為 Jetpack、Android 平臺(tái)及其他 API 提供簡(jiǎn)潔而慣用的 Kotlin代碼。下面舉例進(jìn)行說明:
1、Uri對(duì)象創(chuàng)建
//Kotlin創(chuàng)建一個(gè)Uri對(duì)象
var s = "https://www.google.com"
var uri = Uri.parse(s)
//使用Android KTX + Kotlin之后
var s = "https://www.google.com".toUri()
2、SharedPreferences
//kotlin
sharedPreferences.edit().putBoolean(key, value).apply()
//Kotlin + Android KTX
sharedPreferences.edit {
putBoolean(key, value)
}
3、Canvas操作
//kotlin
val pathDiffer = Path(mPath1).apply {
op(mPath2, Path.Op.DIFFERENCE)
}
val mPaint = Paint()
canvas.apply {
val checkpoint = save()
translate(0F, 100F)
drawPath(pathDiffer, mPaint)
restoreToCount(checkpoint)
}
//Kotlin + Android KTX
val pathDiffer = mPath1 - mPath2
canvas.withTranslation(y = 100F) {
drawPath(pathDiffer, mPaint)
}
4、OnPreDraw 回調(diào)
//kotlin
view.viewTreeObserver.addOnPreDrawListener(
object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
viewTreeObserver.removeOnPreDrawListener(this)
actionToBeTriggered()
return true
}
})
//Kotlin + Android KTX
view.doOnPreDraw { actionToBeTriggered() }
5、View的顯示與隱藏
//kotlin
view1.visibility = View.VISIBLE
view2.visibility = View.GONE
//Kotlin + Android KTX
view1.isVisible = true
view2.isVisible = false
6、Fragment事務(wù)
//kotlin
supportFragmentManager
.beginTransaction()
.add(R.id.content,Fragment1())
.commit()
//Kotlin + Android KTX
supportFragmentManager.commit {
add<Fragment1>(R.id.content)
}
7、ViewModel聲明
//kotlin
//共享范圍activity
val mViewMode1l = ViewModelProvider(requireActivity()).get(UpdateAppViewModel::class.java)
//共享范圍fragment 內(nèi)部
val mViewMode1l = ViewModelProvider(this).get(UpdateAppViewModel::class.java)
//Kotlin + Android KTX
//共享范圍activity
private val mViewModel by activityViewModels<MyViewModel>()
//共享范圍fragment 內(nèi)部
private val mViewModel by viewModel<MyViewModel>()
更多的KTX的代碼簡(jiǎn)化使用請(qǐng)直接查看官方文檔
四、合理利用擴(kuò)展屬性和擴(kuò)展方法
合理的利用kotlin的擴(kuò)展屬性和擴(kuò)展方法 ,可以減少一些重復(fù)代碼的書寫。下面通過兩個(gè)例子,來進(jìn)行說明:
1、將px值轉(zhuǎn)換成dp值
一般在項(xiàng)目中我們都定義了工具類,來進(jìn)行px轉(zhuǎn)dp的操作,不過在kotlin里我們可以使用擴(kuò)展屬性達(dá)到更簡(jiǎn)潔的實(shí)現(xiàn):
val Int.dp: Int
get() {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
this.toFloat(),
Resources.getSystem().displayMetrics
).toInt()
}
然后使用時(shí),就可以這樣做:
viewGroup.addView( textView, LayoutParam( 40.dp, 50.dp ) )
2、為集合添加打印方法
在開發(fā)過程中,經(jīng)常需要打印列表的內(nèi)容,通常我們會(huì)將集合進(jìn)行遍歷,然后打印數(shù)據(jù):
for (String str:list) {
Log.v("test", "str="+str);
}
不同業(yè)務(wù)界面的數(shù)據(jù)類型不同,為了調(diào)試,這樣的 for 循環(huán)就會(huì)散落在各處,而且列表內(nèi)容會(huì)分若干條 log 輸出,中間極有可能被別的log打斷?,F(xiàn)在通過Kotlin的擴(kuò)展函數(shù)我們可以這樣做:
fun <T> Collection<T>.print(map: (T) -> String) =
StringBuilder("\n[").also { sb ->
//遍歷集合元素,通過 map 表達(dá)式將元素轉(zhuǎn)換成感興趣的字串,并獨(dú)占一行
this.forEach { e -> sb.append("\n\t${map(e)},") }
sb.append("\n]")
}.toString()
為集合的基類Collection新增一個(gè)擴(kuò)展函數(shù),用于進(jìn)行打印,該方法中將集合內(nèi)容進(jìn)行遍歷,并使用StringBuilder拼接每個(gè)元素的內(nèi)容。
按照同樣思路,我們可以新增一個(gè)map的擴(kuò)展函數(shù):
fun <K, V> Map<K, V?>.print(map: (V?) -> String): String =
StringBuilder("\n{").also { sb ->
this.iterator().forEach { entry ->
sb.append("\n\t[${entry.key}] = ${map(entry.value)}")
}
sb.append("\n}")
}.toString()
在上面的兩個(gè)擴(kuò)展函數(shù)中,有用到kotlin的高階函數(shù),它是一種特殊的函數(shù),它的參數(shù)或者返回值是另一個(gè)函數(shù)。
反應(yīng)到上面的例子當(dāng)中就是,print()方法的入?yún)ap是一個(gè)函數(shù)。
五、自定義DSL
什么是DSL
DSL = domain specific language,即“特定領(lǐng)域語言”,與它對(duì)應(yīng)的一個(gè)概念叫“通用編程語言”,通用編程語言有一系列完善的能力來解決幾乎所有能被計(jì)算機(jī)解決的問題,像 Java 就屬于這種類型。而特定領(lǐng)域語言只專注于特定的任務(wù),比如 SQL 只專注于操縱數(shù)據(jù)庫,HTML 只專注于表述超文本。
既然通用編程語言能夠解決所有的問題,那為啥還需要特定領(lǐng)域語言?因?yàn)樗梢允褂帽韧ㄓ镁幊陶Z言中等價(jià)代碼更緊湊的語法來表達(dá)特定領(lǐng)域的操作。比如當(dāng)執(zhí)行一條 SQL 語句時(shí),不需要從聲明一個(gè)類及其方法開始。
簡(jiǎn)單來說,就是DSL更簡(jiǎn)潔。
舉個(gè)例子
當(dāng)我們需要組合兩個(gè)動(dòng)畫一起執(zhí)行的,并且在動(dòng)畫結(jié)束時(shí)展現(xiàn)視圖A,通常我們會(huì)這么實(shí)現(xiàn):
val span = 5000
AnimatorSet().apply {
playTogether(
ObjectAnimator.ofPropertyValuesHolder(
tvTitle,
PropertyValuesHolder.ofFloat("alpha", 0f, 1.0f),
PropertyValuesHolder.ofFloat("translationY", 0f, 100f)).apply {
interpolator = AccelerateInterpolator()
duration = span
},
ObjectAnimator.ofPropertyValuesHolder(
ivAvatar,
PropertyValuesHolder.ofFloat("alpha", 1.0f, 0f),
PropertyValuesHolder.ofFloat("translationY", 0f,100f)).apply {
interpolator = AccelerateInterpolator()
duration = span
}
)
addPauseListener(object :Animator.AnimatorPauseListener{
override fun onAnimationPause(animation: Animator?) {
Toast.makeText(context,"pause",Toast.LENGTH_SHORT).show()
}
override fun onAnimationResume(animation: Animator?) {
}
})
addListener(object : Animator.AnimatorListener{
override fun onAnimationRepeat(animation: Animator?) {
}
override fun onAnimationEnd(animation: Animator?) {
showA()
}
override fun onAnimationCancel(animation: Animator?) {
}
override fun onAnimationStart(animation: Animator?) {
}
})
start()
}
這一段apply()有點(diǎn)過長(zhǎng)了,嚴(yán)重降低了它的可讀性。罪魁禍?zhǔn)资?java 接口。雖然只用到接口中的一個(gè)方法,但卻必須將其余的方法保留空實(shí)現(xiàn)。這里可以利用自定義DSL來優(yōu)化。下面看怎么實(shí)現(xiàn)
1、新建類用于存放接口中各個(gè)方法的實(shí)現(xiàn)
class AnimatorListenerImpl {
var onRepeat: ((Animator) -> Unit)? = null
var onEnd: ((Animator) -> Unit)? = null
var onCancel: ((Animator) -> Unit)? = null
var onStart: ((Animator) -> Unit)? = null
}
它包含四個(gè)成員,每個(gè)成員的類型都是函數(shù)類型??匆幌?code>Animator.AnimatorListener的定義就能理解AnimatorListenerImpl的用意:
public static interface AnimatorListener {
void onAnimationStart(Animator animation);
void onAnimationEnd(Animator animation);
void onAnimationCancel(Animator animation);
void onAnimationRepeat(Animator animation);
}
該接口中的每個(gè)方法都接收一個(gè)Animator參數(shù)并返回空值,用 lambda可以表達(dá)成(Animator) -> Unit。所以AnimatorListenerImpl將接口中的四個(gè)方法的實(shí)現(xiàn)都保存在函數(shù)變量中,并且實(shí)現(xiàn)是可空的。
2、為 Animator 定義一個(gè)高階擴(kuò)展函數(shù)
fun AnimatorSet.addListener(action: AnimatorListenerImpl.() -> Unit) {
AnimatorListenerImpl().apply { action }.let { builder ->
//將回調(diào)實(shí)現(xiàn)委托給AnimatorListenerImpl的函數(shù)類型變量
addListener(object : Animator.AnimatorListener {
override fun onAnimationRepeat(animation: Animator?) {
animation?.let { builder.onRepeat?.invoke(animation) }
}
override fun onAnimationEnd(animation: Animator?) {
animation?.let { builder.onEnd?.invoke(animation) }
}
override fun onAnimationCancel(animation: Animator?) {
animation?.let { builder.onCancel?.invoke(animation) }
}
override fun onAnimationStart(animation: Animator?) {
animation?.let { builder.onStart?.invoke(animation) }
}
})
}
}
為Animator定義了擴(kuò)展函數(shù)addListener(),該函數(shù)接收一個(gè)lambdaaction。
3、使用自定義的 DSL 將本文開頭的代碼改寫:
val span = 5000
AnimatorSet().apply {
playTogether(
ObjectAnimator.ofPropertyValuesHolder(
tvTitle,
PropertyValuesHolder.ofFloat("alpha", 0f, 1.0f),
PropertyValuesHolder.ofFloat("translationY", 0f, 100f)).apply {
interpolator = AccelerateInterpolator()
duration = span
},
ObjectAnimator.ofPropertyValuesHolder(
ivAvatar,
PropertyValuesHolder.ofFloat("alpha", 1.0f, 0f),
PropertyValuesHolder.ofFloat("translationY", 0f,100f)).apply {
interpolator = AccelerateInterpolator()
duration = span
}
)
addPauseListener{
onPause = { Toast.makeText(context,"pause",Toast.LENGTH_SHORT).show() }
}
addListener {
onEnd = { showA() }
}
start()
}
上面代碼中省略了擴(kuò)展函數(shù)addPauseListener()的定義,它和addListener()是類似的。
六、總結(jié)
熟練掌握Kotlin語法糖,可以幫助我們簡(jiǎn)化代碼,節(jié)省開發(fā)時(shí)間,提高效率。一般配合Google提供的KTX庫即可完成大部分的項(xiàng)目開發(fā),熟練掌握擴(kuò)展函數(shù)和高階函數(shù)的使用更是能為代碼簡(jiǎn)化插上翅膀。