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 开发。