服务端相关 / Kotlin 如何用于Android开发

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