一、前言
使用kotlin開發(fā)項目已經(jīng)有一段時間餐济,在使用kotlin的過程中耘擂,發(fā)現(xiàn)了許多很方便的語法糖,可以有效簡潔代碼絮姆。在這里做個總結(jié)記錄醉冤,方便后續(xù)查閱。
二篙悯、kotlin基礎(chǔ)語法常識
1蚁阳、 新建對象不需要new
關(guān)鍵字。語句也不需要;
結(jié)尾鸽照,但加上;
也不會報錯螺捐。
//java
StringBuffer buffer = new StringBuffer();
//kotlin
var buffer = StringBuffer()
2、var
是kotlin保留字矮燎,用于聲明變量定血。與之相對的是val
,表明引用不可變,但可以改變對象的值诞外,使用var
或val
聲明變量后澜沟,kotlin會根據(jù)上下文進行類型推斷。
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
也更加簡約:
//正常情況
view.setOnClickListener({ v -> v.setVisibility(View.INVISIBLE) })
//當(dāng)lambda是函數(shù)的最后一個參數(shù)時既们,可以將其移到括號外面
view.setOnClickListener() { v -> v.setVisibility(View.INVISIBLE) }
//當(dāng)函數(shù)只有一個lambda類型的參數(shù)濒析,可以去省去括號
view.setOnClickListener { v -> v.setVisibility(View.INVISIBLE) }
//當(dāng)lambda只有一個參數(shù),可省去參數(shù)列表贤壁,在表達式部分用it引用參數(shù)
view.setOnClickListener { it.setVisibility(View.INVISIBLE) }
5悼枢、所有定義了setter
和getter
方法的字段,在kotlin中都可以通過賦值語法來直接操作
view.setOnClickListener { it.visibility = View.INVISIBLE }
6脾拆、空安全:kotlin中使用?.
作為安全調(diào)用運算符馒索,將判空檢查和方法調(diào)用合并為一個操作。只有當(dāng)調(diào)用變量本身不為null時名船,方法調(diào)用才成立绰上,否則整個表達式返回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時返回的默認(rèn)值
fun getCountry(): String {
return person.company?.address?.country ?:""
}
7渠驼、擴展函數(shù):擴展函數(shù)是一個類的成員函數(shù)蜈块,但它定義在類體外面。這樣定義的好處是,可以在任何時候任何地方給類添加功能百揭。
在擴展函數(shù)中爽哎,可以像類的其他成員函數(shù)一樣訪問類的屬性和方法(除了被private和protected修飾的成員)。還可以通過this引用類的實例器一,也可以省略它课锌,把上段代碼進一步簡化:
fun Person.getCountry(): String? {
return this.company?.address?.country
}
8、kotlin中常用的擴展函數(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中集合的操作符很多渺贤,此處只列舉使用頻率較高的幾個,感興趣的可以看下這篇文章
- 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() 在集合的每一個元素上應(yīng)用一個自定義的變化
list.map {
it.apply {
name = name.replace(name.first(), name.first().toUpperCase())
}
}
- filter() 只保留滿足條件的集合元素
val reslutList = list.filter {it.length > 3}
- toSet() 將集合元素去重
三请毛、ktx擴展包
Android KTX
是包含在 Android Jetpack
及其他 Android
庫中的一組 Kotlin
擴展程序志鞍。KTX 擴展程序可以為 Jetpack
、Android
平臺及其他 API 提供簡潔而慣用的 Kotlin
代碼方仿。下面舉例進行說明:
1固棚、Uri對象創(chuàng)建
//Kotlin創(chuàng)建一個Uri對象
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的代碼簡化使用請直接查看官方文檔
四面徽、合理利用擴展屬性和擴展方法
合理的利用kotlin的擴展屬性和擴展方法 艳丛,可以減少一些重復(fù)代碼的書寫。下面通過兩個例子趟紊,來進行說明:
1氮双、將px值轉(zhuǎn)換成dp值
一般在項目中我們都定義了工具類,來進行px轉(zhuǎn)dp的操作霎匈,不過在kotlin里我們可以使用擴展屬性達到更簡潔的實現(xiàn):
val Int.dp: Int
get() {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
this.toFloat(),
Resources.getSystem().displayMetrics
).toInt()
}
然后使用時戴差,就可以這樣做:
viewGroup.addView( textView, LayoutParam( 40.dp, 50.dp ) )
2、為集合添加打印方法
在開發(fā)過程中铛嘱,經(jīng)常需要打印列表的內(nèi)容暖释,通常我們會將集合進行遍歷,然后打印數(shù)據(jù):
for (String str:list) {
Log.v("test", "str="+str);
}
不同業(yè)務(wù)界面的數(shù)據(jù)類型不同墨吓,為了調(diào)試球匕,這樣的 for 循環(huán)就會散落在各處,而且列表內(nèi)容會分若干條 log 輸出帖烘,中間極有可能被別的log打斷×敛埽現(xiàn)在通過Kotlin的擴展函數(shù)
我們可以這樣做:
fun <T> Collection<T>.print(map: (T) -> String) =
StringBuilder("\n[").also { sb ->
//遍歷集合元素,通過 map 表達式將元素轉(zhuǎn)換成感興趣的字串,并獨占一行
this.forEach { e -> sb.append("\n\t${map(e)},") }
sb.append("\n]")
}.toString()
為集合的基類Collection
新增一個擴展函數(shù),用于進行打印照卦,該方法中將集合內(nèi)容進行遍歷式矫,并使用StringBuilder
拼接每個元素的內(nèi)容。
按照同樣思路役耕,我們可以新增一個map的擴展函數(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()
在上面的兩個擴展函數(shù)中衷佃,有用到kotlin的高階函數(shù),它是一種特殊的函數(shù)蹄葱,它的參數(shù)或者返回值是另一個函數(shù)氏义。
反應(yīng)到上面的例子當(dāng)中就是,print()方法的入?yún)ap是一個函數(shù)图云。
五惯悠、自定義DSL
什么是DSL
DSL = domain specific language,即“特定領(lǐng)域語言
”竣况,與它對應(yīng)的一個概念叫“通用編程語言”克婶,通用編程語言有一系列完善的能力來解決幾乎所有能被計算機解決的問題,像 Java 就屬于這種類型丹泉。而特定領(lǐng)域語言只專注于特定的任務(wù)情萤,比如 SQL 只專注于操縱數(shù)據(jù)庫,HTML 只專注于表述超文本摹恨。
既然通用編程語言能夠解決所有的問題筋岛,那為啥還需要特定領(lǐng)域語言?因為它可以使用比通用編程語言中等價代碼更緊湊的語法來表達特定領(lǐng)域的操作晒哄。比如當(dāng)執(zhí)行一條 SQL 語句時睁宰,不需要從聲明一個類及其方法開始。
簡單來說寝凌,就是DSL更簡潔柒傻。
舉個例子
當(dāng)我們需要組合兩個動畫一起執(zhí)行的,并且在動畫結(jié)束時展現(xiàn)視圖A,通常我們會這么實現(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()有點過長了较木,嚴(yán)重降低了它的可讀性红符。罪魁禍?zhǔn)资?java 接口。雖然只用到接口中的一個方法伐债,但卻必須將其余的方法保留空實現(xiàn)预侯。這里可以利用自定義DSL來優(yōu)化。下面看怎么實現(xiàn)
1泳赋、新建類用于存放接口中各個方法的實現(xiàn)
class AnimatorListenerImpl {
var onRepeat: ((Animator) -> Unit)? = null
var onEnd: ((Animator) -> Unit)? = null
var onCancel: ((Animator) -> Unit)? = null
var onStart: ((Animator) -> Unit)? = null
}
它包含四個成員雌桑,每個成員的類型都是函數(shù)類型
∽娼瘢看一下Animator.AnimatorListener
的定義就能理解AnimatorListenerImpl
的用意:
public static interface AnimatorListener {
void onAnimationStart(Animator animation);
void onAnimationEnd(Animator animation);
void onAnimationCancel(Animator animation);
void onAnimationRepeat(Animator animation);
}
該接口中的每個方法都接收一個Animator
參數(shù)并返回空值校坑,用 lambda
可以表達成(Animator) -> Unit
拣技。所以AnimatorListenerImpl
將接口中的四個方法的實現(xiàn)都保存在函數(shù)變量中,并且實現(xiàn)是可空的耍目。
2膏斤、為 Animator 定義一個高階擴展函數(shù)
fun AnimatorSet.addListener(action: AnimatorListenerImpl.() -> Unit) {
AnimatorListenerImpl().apply { action }.let { builder ->
//將回調(diào)實現(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
定義了擴展函數(shù)addListener()
,該函數(shù)接收一個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()
}
上面代碼中省略了擴展函數(shù)addPauseListener()的定義,它和addListener()是類似的毅访。
六沮榜、總結(jié)
熟練掌握Kotlin語法糖,可以幫助我們簡化代碼喻粹,節(jié)省開發(fā)時間蟆融,提高效率。一般配合Google提供的KTX庫即可完成大部分的項目開發(fā)守呜,熟練掌握擴展函數(shù)和高階函數(shù)的使用更是能為代碼簡化插上翅膀型酥。