跳到主要内容

Kotlin 如何用于 Android 开发

从这篇文章开始我们将进入 Kotlin 用于一些平台的开发,比如 Android、iOS、Web 以及服务端应用程序的开发。我们都知道 Kotlin 这门语言是借助 Android 进入了我们视野的,自从 2019 年 Google IO 大会上宣布 Kotlin 成为了 Android 开发的一级语言后,Google 官方力推 Kotlin,包括很多官方库和 Google APP 都采用 Kotlin 来开发。此外 Android 熟知的第三方库比如 OkHttp 都全部使用 Kotlin 重写了,可知 Kotlin 在 Android 中地位已经到了语言一等公民了。那么这篇文章,将从 0 到 1 带领大家使用 Kotlin 开发 Android 应用程序。

1. Kotlin 应用 Android 开发必须知道几点技巧

1.1 技巧一:告别你的 findviewById

使用 Kotlin 开发 Android 应用程序,给你带来最大方便之一的是可以省去 findviewById , 可以看到在 Java 中会写很多类似的模板代码,并且毫无技术含量。实现下面简单的 UI 效果:

图片描述

在 Java 中的 findviewById 实现:

package com.imooc.imooctest;

import android.os.Bundle;
import android.widget.ImageView;
import android.widget.RatingBar;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;

public class Main2Activity extends AppCompatActivity {

private TextView mTvTitle;
private TextView mTvTime;
private TextView mTvScore;
private TextView mTvLaunage;
private TextView mTvDirector;
private TextView mTvActors;
private TextView mTvHot;

private RatingBar mRatingBarScore;
private ImageView mIvPoster;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);

mTvTitle = findViewById(R.id.movie_tv_title);
mTvTime = findViewById(R.id.movie_tv_time);
mTvScore = findViewById(R.id.movie_tv_rating_score);
mTvLaunage = findViewById(R.id.movie_tv_language);
mTvDirector = findViewById(R.id.movie_tv_director);
mTvActors = findViewById(R.id.movie_tv_actors);
mTvHot = findViewById(R.id.movie_tv_hot);
mRatingBarScore = findViewById(R.id.movie_rating_bar);
mIvPoster = findViewById(R.id.movie_iv_poster);


mTvTitle.setText("Forrest Gump");
mTvTime.setText("1994");
mTvScore.setText("8.8");
mTvLaunage.setText("Language: English(USA)");
mTvDirector.setText("Director: Robert Zemeckis");
mTvActors.setText("Starring: Tom Hanks, Rebecca Willams, Sally Field, Michael...");
mTvHot.setText("1786891");
mRatingBarScore.setRating(4.4f);
mIvPoster.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.biz_app_icon_cover));
}
}


在 Kotlin 中的实现:

对于上述实现的 Java 的代码实际有效逻辑仅仅也就 20 多行,然而其中模板代码占了 50% 左右,那么 Kotlin 就可以省去这些模板。对于上面相同代码效果,Kotlin 仅需一半的代码实现,非常简单高效。

package com.imooc.imooctest

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import kotlinx.android.synthetic.main.activity_main.\*

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
movie_tv_title.text = "Forrest Gump"
movie_tv_time.text = "1994"
movie_tv_rating_score.text = "8.8"
movie_tv_language.text = "Language: English(USA)"
movie_tv_director.text = "Director: Robert Zemeckis"
movie_tv_actors.text = "Starring: Tom Hanks, Rebecca Willams, Sally Field, Michael..."
movie_tv_hot.text = "1786891"
movie_rating_bar.rating = 4.4f
movie_iv_poster.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.biz_app_icon_cover))
}
}


技巧点:

在 Kotlin 开发 Android 应用程序中只需要在 build.gradle 中添加如下 plugin 即可,当然这个已经是 Kotlin 开发 Android 的自动生成的标配了。

apply plugin: 'kotlin-android-extensions'

1.2 技巧二:扩展函数让你的开发效率倍增

扩展函数可以说是 Kotlin 中最吸引开发者之一的语法特性,有了它代码开发效率不仅仅一点点,此外它还能让你的代码变得更具有可维护性。关于扩展函数的定义就不展开了,之前文章中有。下面我会列出几个场景,Kotlin 扩展如何让你代码变得更少更简洁。

场景一: ImageView 的扩展函数 loadUrl

这里以 ImageView 加载网络图片场景举例假如有 3 个 Activity (可以扩展若干个 Activity) 都需要加载网络图片,也是 Android 开发中最为频繁的场景。相信这样加载图片代码写法一定让你值得收藏。(这里就以 Glide 图片库举例)

在 Java 中实现 ImageView 加载网络图片:

//MainActivity加载图片
public class MainActivity extends AppCompatActivity {
private ImageView mIvPoster;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mIvPoster = findViewById(R.id.movie_iv_poster);
Glide.with(context).load("http://goo.gl/gEgYUd").into(mIvPoster);
}
}

//Main2Activity加载图片
public class MainActivity extends AppCompatActivity {
private ImageView mIvPoster;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
mIvPoster = findViewById(R.id.movie_iv_poster);
Glide.with(context).load("http://goo.gl/gEgYUd").into(mIvPoster);
}
}

//Main3Activity加载图片
public class MainActivity extends AppCompatActivity {
private ImageView mIvPoster;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);
mIvPoster = findViewById(R.id.movie_iv_poster);
Glide.with(context).load("http://goo.gl/gEgYUd").into(mIvPoster);//虽然Glide可以做到很简单也是一行代码,但是每次都需要写 Glide.with(context)这些代码。
}
}

想象下上述代码如果很多 Activity 每次都需要编写重复的代码。此外还有一个问题就是比如后面图片从 Glide 迁移到其他图片库,并且写法上也和 Glide 一样,这时候的重构只能每处使用 Glide 加载图片地方都得修改,如果整个项目是大型项目各个业务到处都是 Glide 图片写法。就会造成重构成本加大。如果 Kotlin 的扩展函数不仅让写法更简单,反而也能解决后续维护和迁移成本,那么是不是绝妙呢。

使用 Kotlin 扩展函数实现 ImageView 加载网络图片:

//先定义一个图片加载的扩展函数

fun ImageView.loadUrl(url: String) {
Glide.with(context).load(url).into(this)//也是这么简单的一行,但是仅仅需要写这么一次就可以,后续无需再答复。
}

//MainActivity加载图片
class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
movie_iv_poster.loadUrl(""http://goo.gl/gEgYUd"")//仅仅需要这么一行,无论在哪个Activity都需要这么一行就能实现图片加载就可以了。
//此外这样代码更具有可读性,loadUrl就像是ImageView组件中自带的函数。
}
}

//Main2Activity加载图片
class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
movie_iv_poster.loadUrl(""http://goo.gl/gEgYUd"")
}
}

//Main3Activity加载图片
class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main3)
movie_iv_poster.loadUrl(""http://goo.gl/gEgYUd"")
}
}

可以看到 Kotlin 的 ImageView 扩展函数 loadUrl 更具可读性,就像是 ImageView 组件中自带加载网络图片的函数。此外及时后续 Glide 图片加载库迁移了,上层业务代码使用 loadUrl 地方都可以不用修改,保证了上层业务代码接口稳定性,只要修改 loadUrl 扩展函数内部的实现即可,非常简单。

场景二: TextView 的扩展函数 SpannableStringBuilder

我们都知道 Android 开发中,经常会利用 TextView 实现同一个文本中不同文字样式标注,比如部分加粗、变颜色、变字体样式、加下划线等。遇到这样的需求时我们可以很自然地想到使用 TextView 中的 SpannableStringBuilder 来实现,但是相信大家一定对于 SpannableStringBuilder 实现代码比较麻烦,每次都需要指定 star,end 然后就会去算字第几个啥的,是不是很麻烦,看看 Kotlin 扩展函数如何助你一臂之力。

图片描述

在 Java 中实现以上场景 UI 效果:

public class Main2Activity extends AppCompatActivity {

private TextView mTvSpan;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
mTvSpan = findViewById(R.id.span_tv);

SpannableStringBuilder sb = new SpannableStringBuilder();

String text1 = "我们发布了";
String text2 = "Jetpack Compose for Desktop";
String text3 = "第一个里程碑";
String text4 = "版本的正式版";

sb.append(text1);
sb.append(text2);
sb.append(text3);
sb.append(text4);

//以下代码可以说是编写Span代码基本模板,写起来也是比较麻烦的。
ForegroundColorSpan colorSpan1 = new ForegroundColorSpan(Color.parseColor("#333333"));
StyleSpan styleSpan1 = new StyleSpan(Typeface.ITALIC);
sb.setSpan(colorSpan1, 0, text1.length(), SpannableStringBuilder.SPAN_EXCLUSIVE_INCLUSIVE);
sb.setSpan(styleSpan1, 0, text1.length(), SpannableStringBuilder.SPAN_EXCLUSIVE_INCLUSIVE);

ForegroundColorSpan colorSpan2 = new ForegroundColorSpan(Color.parseColor("#ff9900"));
StyleSpan styleSpan2 = new StyleSpan(Typeface.BOLD);
sb.setSpan(colorSpan2, text1.length(), text1.length() + text2.length(), SpannableStringBuilder.SPAN_EXCLUSIVE_INCLUSIVE);
sb.setSpan(styleSpan2, text1.length(), text1.length() + text2.length(), SpannableStringBuilder.SPAN_EXCLUSIVE_INCLUSIVE);


UnderlineSpan underlineSpan = new UnderlineSpan();
sb.setSpan(underlineSpan, text1.length() + text2.length(), text1.length() + text2.length() + text3.length(), SpannableStringBuilder.SPAN_EXCLUSIVE_INCLUSIVE);

ForegroundColorSpan colorSpan3 = new ForegroundColorSpan(Color.parseColor("#ff0000"));
sb.setSpan(colorSpan3, text1.length() + text2.length() + text3.length(), text1.length() + text2.length() + text3.length() + text4.length(), SpannableStringBuilder.SPAN_EXCLUSIVE_INCLUSIVE);


mTvSpan.setText(sb);
}
}


上述 Java 实现上面效果大概需要近 45 行代码,而且每次写起来也比较麻烦,那么使用 Kotlin 扩展函数是不是更方便呢?一起来看下:

Kotlin 扩展函数实现上述效果:

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//可以看到Kotlin屏蔽了具体start和end指定以及一些模板,仅仅需要核心代码十几行即可搞定,而且后续可持续复用
span_tv.text = buildSpannedString {
inSpans(ForegroundColorSpan(Color.parseColor("#333333")), StyleSpan(Typeface.ITALIC)) {
append("我们发布了")
}
inSpans(ForegroundColorSpan(Color.parseColor("#ff9900")), StyleSpan(Typeface.BOLD)) {
append("Jetpack Compose for Desktop")
}
underline {
append("第一个里程碑")
}
color(Color.parseColor("#ff0000")) {
append("版本的正式版")
}
}
}
}

下面给出 SpannbleStringBuilder 的扩展函数:

inline fun buildSpannedString(builderAction: SpannableStringBuilder.() -> Unit): SpannedString {
val builder = SpannableStringBuilder()
builder.builderAction()
return SpannedString(builder)
}

inline fun SpannableStringBuilder.inSpans(
vararg spans: Any,
builderAction: SpannableStringBuilder.() -> Unit
): SpannableStringBuilder {
val start = length
builderAction()
for (span in spans) setSpan(span, start, length, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
return this
}

inline fun SpannableStringBuilder.italic(builderAction: SpannableStringBuilder.() -> Unit) =
inSpans(StyleSpan(Typeface.ITALIC), builderAction = builderAction)

inline fun SpannableStringBuilder.underline(builderAction: SpannableStringBuilder.() -> Unit) =
inSpans(UnderlineSpan(), builderAction = builderAction)

inline fun SpannableStringBuilder.color(
@ColorInt color: Int,
builderAction: SpannableStringBuilder.() -> Unit
) = inSpans(ForegroundColorSpan(color), builderAction = builderAction)

其实关于扩展函数的场景还有很多很多,所以官方特地整理一个库: androidx-core-ktx, 这里面收录了 Android 中常用的 Kotlin 扩展函数。这里还给出一些常用的扩展函数:

//TextView加粗
fun TextView.isBold() {
this.paint.isFakeBoldText = true
}

//TextView设置左、上、右、下 图标+文字的样式,包括支持定义图标的颜色
fun TextView.setCompoundDrawablesKt(
leftDrawable: Drawable? = null,
topDrawable: Drawable? = null,
rightDrawable: Drawable? = null,
bottomDrawable: Drawable? = null,
tintColor: Int = -1
) {
this.setCompoundDrawables(leftDrawable?.getTintDrawable(tintColor)?.apply {
setBounds(0, 0, intrinsicWidth, intrinsicHeight)
}, topDrawable?.getTintDrawable(tintColor)?.apply {
setBounds(0, 0, intrinsicWidth, intrinsicHeight)
}, rightDrawable?.getTintDrawable(tintColor)?.apply {
setBounds(0, 0, intrinsicWidth, intrinsicHeight)
}, bottomDrawable?.getTintDrawable(tintColor)?.apply {
setBounds(0, 0, intrinsicWidth, intrinsicHeight)
})
}

//设置View的Margin
fun View.setMargins(leftMargin: Int = 0, topMargin: Int = 0, rightMargin: Int = 0, bottomMargin: Int = 0) {
val layoutParams = (this.layoutParams ?: return) as? ViewGroup.MarginLayoutParams ?: return

if(leftMargin > 0) {
layoutParams.leftMargin = leftMargin
}
if(topMargin > 0) {
layoutParams.topMargin = topMargin
}
if(rightMargin > 0) {
layoutParams.rightMargin = rightMargin
}
if(bottomMargin > 0) {
layoutParams.bottomMargin = bottomMargin
}
this.layoutParams = layoutParams
}

//常用的Context扩展,比如Toast以及设置颜色和Drawable
fun Context.getColorCompat(@ColorRes color: Int): Int = ContextCompat.getColor(this, color)

fun Context.getDrawableCompat(@DrawableRes drawable: Int): Drawable {
return ContextCompat.getDrawable(this, drawable) ?: ColorDrawable()
}

fun Context.showToast(text: String, isShort: Boolean = true) {
Toast.makeText(this, text, isShort.yes { Toast.LENGTH_SHORT }.otherwise { Toast.LENGTH_LONG })
.show()
}

1.3 技巧三: data class 告别冗长模板代码

相信大家不管是 Android 开发还是 Java 服务端开发都使用 JavaBean 或者 Model 类,我们经常会借助 IDEA 工具来自动生成构造器函数和 setter,getter 函数。但是可曾想过如果没有 IDEA 工具来生成代码,需要很多 model 类模板代码可是会花蛮久时间的。那么一起来看下 Kotlin 的 data class 如何替代 Java 中 Model 类的。

在 Java 实现一个 Movie 类:

package com.imooc.imooctest.model;

public class Movie {
private String title;
private String year;
private String type;
private String poster;
private String rated;
private String released;
private String runtime;
private String genre;
private String director;
private String writer;
private String actors;
private String language;

public Movie(String title, String year, String type, String poster, String rated, String released, String runtime, String genre, String director, String writer, String actors, String language) {
this.title = title;
this.year = year;
this.type = type;
this.poster = poster;
this.rated = rated;
this.released = released;
this.runtime = runtime;
this.genre = genre;
this.director = director;
this.writer = writer;
this.actors = actors;
this.language = language;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public String getYear() {
return year;
}

public void setYear(String year) {
this.year = year;
}

public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}

public String getPoster() {
return poster;
}

public void setPoster(String poster) {
this.poster = poster;
}

public String getRated() {
return rated;
}

public void setRated(String rated) {
this.rated = rated;
}

public String getReleased() {
return released;
}

public void setReleased(String released) {
this.released = released;
}

public String getRuntime() {
return runtime;
}

public void setRuntime(String runtime) {
this.runtime = runtime;
}

public String getGenre() {
return genre;
}

public void setGenre(String genre) {
this.genre = genre;
}

public String getDirector() {
return director;
}

public void setDirector(String director) {
this.director = director;
}

public String getWriter() {
return writer;
}

public void setWriter(String writer) {
this.writer = writer;
}

public String getActors() {
return actors;
}

public void setActors(String actors) {
this.actors = actors;
}

public String getLanguage() {
return language;
}

public void setLanguage(String language) {
this.language = language;
}
}


使用 Kotlin 的 data class 实现一个 Movie 类:

data class MovieEntity(
val title: String,
val year: String,
val type: String,
val poster: String,
val rated: String,
val released: String,
val runtime: String,
val genre: String,
val director: String,
val writer: String,
val actors: String
)

可以看到比较大差距的对比,仅仅需要十几行代码就能替代百来行的 Java 模板代码,可以说使用 Kotlin 实现和 Java 同样功能,代码量可以下降很多的。

2. Kotlin 开发 Android 应用基本流程

2.1 创建 Kotlin 的 Android 项目工程

打开 AndroidStudio 创建一个 Android Project,并且勾选语言为 Kotlin (默认勾选):

图片描述

图片描述

创建完毕后,会自动加载 Kotlin Gradle Plugin 和 Kotlin 标准库的依赖,可以看到默认 Kotlin 创建 MainActivity 比 Java 看起来都简洁多了。

图片描述

此外 Kotlin Android Project 会默认加载一些 Kotlin 依赖,可以省去自定义配置,帮助更高效开发 Android 应用,下面是默认依赖配置:

图片描述

2.2 编写 Kotlin 业务代码,运行 App

实际上只需上面简单创建 Kotlin 相关依赖就自动配置完成了,现在只需简单运行就可以把这个 Android 应用程序跑起来:

图片描述

图片描述

2.3 修改布局,然后直接设置中间文案

由于我们引用了 kotlin-android-extensions plugin,所以就可以省去 findViewById 的操作,直接可以通过 id 获取组件。

图片描述

图片描述

拿到组件 id 以后直接这个 id 当作组件引用就可以直接操作这个 TextView 组件,此外由于 Kotlin 中属性默认是 setter 和 getter 方法的,所以 setText 方法就直接变成了赋值操作了,具体代码如下:

package com.imooc.imooctest

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.\*

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
center_tv.text = "Hello Kotlin!"//直接.text赋值即可,相当于Java中setText("Hello Kotlin!")
}
}


3. 小结

到这里有关 Kotlin 用于 Android 开发就结束,实际上这篇仅仅做了一些 Kotlin 应用于 Android 开篇作用,其实还有很多有趣点和提效值得你去发掘。最后你会慢慢发现 Kotlin 开发 Android 应用是真的高效简洁,而且还不易出错。Kotlin 在大家印象里仅仅是用于 JVM 和 Android 开发,其实 Kotlin 已然是面向全平台的开发语言了,那么下一篇将带领大家看下 Kotlin 应用 iOS 开发。