作者:谭东
Kotlin相信现在大家都不陌生了,它是谷歌在5月18日,它的安卓团队在Google I/O 2017 大会上正式宣布 Kotlin 成为官方头等支持语言。最近一段时间我学习和研究了下Kotlin的特点和基本用法。大概用了一天时间,把Android的一些主要的APP功能,用Kotlin语言和结构重新写了一遍,体会就是:上手和学习很快、语法简洁、代码少写了很多、不用很麻烦的写控件绑定了(自动导包)、兼容性不错、空指针处理都帮你想好了、Java和Android的原来的框架和库都可以正常使用、支持Java和Kotlin混合编写等等。一些语法糖很好用。
Kotlin 是一个基于 JVM 的新的编程语言,由 JetBrains 开发。Kotlin可以编译成Java字节码,也可以编译成JavaScript,方便在没有JVM的设备上运行。
先看下官网的一个最形象简单的例子。
好吧,看到这里你会说没什么简洁的,感觉都差不多。
官方使用文档地址:http://kotlinlang.org/docs/reference/android-overview.html
那我先总结下Kotlin中必要有特点的几个地方:
1、方法名fun开头、语句和声明结尾无需加分号了(当然加了也不报错)、方法参数和变量声明是反过来的:前面是名称,后面是类型,例如
var name:String?=null
2、对象的创建没有new了,直接对象名+括号。例如创建Utils这个类,然后调用它的foo方法:
var utils = Utils();
utils.foo(this);
3、控件直接不用findViewById了,直接对应布局里的控件id名字使用,会自动导包。
import kotlinx.android.synthetic.main.activity_main.*
4、重写父类方法由原来的@override注解改成了前缀。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initView();
}
5、继承变成了冒号:分隔代替了extend,实现接口直接逗号,分隔代替了immplement。
class MainActivity : BaseActivity(), View.OnClickListener {
}
6、没有了switch case语句,取而代之是when ->语句,例如:
override fun onClick(v: View?) {
when (v!!.id) {
R.id.tv_getdata -> {
getData()
}
R.id.tv_intent -> {
var intent = Intent(this@MainActivity, SencondActivity::class.java);
intent.putExtra("name", "名字")
intent.putExtra("id", 12)
startActivity(intent)
}
}
}
7、非空控制严格,更安全方便。!!、?。例如在使用!!后,使用的对象不可为空,空的时候直接抛出空指针异常;使用?后,使用的对象可以为空,空的时候返回Null。
private var users: Call<List<User>>? = null
private var userList: List<User>? = null
8、Intent意图的接收,直接intent接收,不用getIntent了。已经封装好,例如
name = intent.getStringExtra("name")
id = intent.getIntExtra("id", 1)
9、for循环的不同。例如i从0到9循环,添加到ArrayList
for (i in 0..9) {
list!!.add("" + i)
}
也有这种,for(i in 对象集合.indices),例如
for (i in userList!!.indices) {
Log.i("info", "用户:" + userList!!.get(i).full_name);
}
10、构造方法的不同,构造方法可以卸载类名后,如空的构造方法,后面会直接执行init方法,例如
class ApiClient constructor() {
private var apiservice: ApiService? = null;
private var retrofit: Retrofit? = null;
init {
retrofit = Retrofit.Builder().baseUrl(Conf.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
apiservice = retrofit!!.create(ApiService::class.java)
}
当然有参的多个构造方法,也可以写在类里,例如
constructor(resultListener: ResultListener<T>) {
this.resultListener = resultListener
}
constructor(context: Context, progress: Boolean, resultListener: ResultListener<T>) {
this.progress = progress
this.resultListener = resultListener
if (progress) {
loadingView = LoadingView(context)
loadingView!!.setOnCancelListener(this)
}
}
11、接口的定义。
interface ResultListener<T> {
fun complete(t: T)
fun onError(e: Throwable)
}
12、对象实体的构建,不用setter和getter了,直接定义或者data class+对象名,例如
class User {
var id: Int = 0
var name: String? = null
var full_name: String? = null
var owner: OwnerBean? = null
@SerializedName("private")
var isPrivateX: Boolean = false
var html_url: String? = null
var description: String? = null
var isFork: Boolean = false
var url: String? = null
var forks_url: String? = null
var keys_url: String? = null
var collaborators_url: String? = null
var teams_url: String? = null
var hooks_url: String? = null
var issue_events_url: String? = null
var events_url: String? = null
var assignees_url: String? = null
var branches_url: String? = null
var tags_url: String? = null
var blobs_url: String? = null
var git_tags_url: String? = null
var git_refs_url: String? = null
var trees_url: String? = null
var statuses_url: String? = null
var languages_url: String? = null
var stargazers_url: String? = null
var contributors_url: String? = null
var subscribers_url: String? = null
var subscription_url: String? = null
var commits_url: String? = null
var git_commits_url: String? = null
var comments_url: String? = null
var issue_comment_url: String? = null
var contents_url: String? = null
var compare_url: String? = null
var merges_url: String? = null
var archive_url: String? = null
var downloads_url: String? = null
var issues_url: String? = null
var pulls_url: String? = null
var milestones_url: String? = null
var notifications_url: String? = null
var labels_url: String? = null
var releases_url: String? = null
var deployments_url: String? = null
var created_at: String? = null
var updated_at: String? = null
var pushed_at: String? = null
var git_url: String? = null
var ssh_url: String? = null
var clone_url: String? = null
var svn_url: String? = null
var homepage: String? = null
var size: Int = 0
var stargazers_count: Int = 0
var watchers_count: Int = 0
var language: String? = null
var isHas_issues: Boolean = false
var isHas_projects: Boolean = false
var isHas_downloads: Boolean = false
var isHas_wiki: Boolean = false
var isHas_pages: Boolean = false
var forks_count: Int = 0
var mirror_url: Any? = null
var isArchived: Boolean = false
var open_issues_count: Int = 0
var license: Any? = null
var forks: Int = 0
var open_issues: Int = 0
var watchers: Int = 0
var default_branch: String? = null
class OwnerBean {
var login: String? = null
var id: Int = 0
var avatar_url: String? = null
var gravatar_id: String? = null
var url: String? = null
var html_url: String? = null
var followers_url: String? = null
var following_url: String? = null
var gists_url: String? = null
var starred_url: String? = null
var subscriptions_url: String? = null
var organizations_url: String? = null
var repos_url: String? = null
var events_url: String? = null
var received_events_url: String? = null
var type: String? = null
var isSite_admin: Boolean = false
}
}
13、BaseActivity的写法大家也应该可以大概猜到什么样式的。
open class BaseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
fun showToast(context: Context, text: String) {
Toast.makeText(context, text, Toast.LENGTH_SHORT).show();
}
/*
* show toast in activity
* */
fun Activity.toast(msg: String){
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
}
override fun onResume() {
super.onResume()
}
}
14、常量的定义,val表示常量,类似java里的final,常量定义后不能修改,var定义的可以修改。例如
object Conf {
val BASE_URL: String? = "https://api.github.com/"
}
15、多了Any这个任意数据类型,字符串支持三个引号""",也支持换行。例如
/*
* kotlin对字符串的加强,三个引号"""中可以包含换行、反斜杠等等特殊字符
* */
fun testString() {
val str1 = "abc"
val str2 = """line1\n
line2
line3
"""
val js = """
function myFunction()
{
document.getElementById("demo").innerHTML="My First JavaScript Function";
}
""".trimIndent()
println(str1)
println(str2)
println(js)
}
16、单例模式。可以这样写。
package com.tandong.kotlin.utils
import android.util.Log
/**
* Created by Tandong on 2018/1/19.
*/
class TestInstance private constructor() {
init {
Log.i("info", "构造方法")
}
private object SingletonHolder {
val instance = TestInstance()
}
fun method() {
Log.i("info", "构造方法SingletonInner")
}
companion object {
val instance: TestInstance
@Synchronized get() = SingletonHolder.instance
}
}
调用时候这样声明调用。
private var testInstance: TestInstance? = null
...
testInstance = TestInstance.instance;
testInstance!!.method();
...
好了,我总结的关键的大概这么多。都是实际实践操作中总结的比较突出的。
下面我粘贴部分我的写的例子的代码。完整的去Github上看。
Android Studio或者IntelliJ IDEA创建是选择Kotlin的,当然如果Android也可以,只不过需要自己手动在build.gradle等文件里添加一些Kotlin配置库。
先看BaseApplication.kt,很简单,里面其他逻辑没写。
package com.tandong.kotlin.base
import android.app.Application
/**
* Created by Tandong on 2018/1/17.
*/
class BaseApplication : Application() {
override fun onCreate() {
super.onCreate()
}
}
BaseActivity.kt
package com.tandong.kotlin.base
import android.app.Activity
import android.content.Context
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.widget.Toast
/**
* Created by Tandong on 2018/1/15.
*/
open class BaseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
fun showToast(context: Context, text: String) {
Toast.makeText(context, text, Toast.LENGTH_SHORT).show();
}
/*
* show toast in activity
* */
fun Activity.toast(msg: String){
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
}
override fun onResume() {
super.onResume()
}
}
MainActivity.kt,里面接口网络请求数据使用了Retrofit和Rxjava
package com.tandong.kotlin.ui
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.View
import com.bumptech.glide.Glide
import com.tandong.kotlin.R
import com.tandong.kotlin.base.BaseActivity
import com.tandong.kotlin.entity.User
import com.tandong.kotlin.net.ApiClient
import com.tandong.kotlin.net.ResultListener
import com.tandong.kotlin.net.ResultObserver
import com.tandong.kotlin.utils.Utils
import kotlinx.android.synthetic.main.activity_main.*
import retrofit2.Call
class MainActivity : BaseActivity(), View.OnClickListener {
private var users: Call<List<User>>? = null
private var userList: List<User>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initView();
}
private fun initView() {
var utils = Utils();
utils.foo(this);
tv_tips.setText("测试控件");
utils.testString3();
utils.testApply(this);
utils.getPoint('B');
Glide.with(this).load("https://www.baidu.com/img/bd_logo1.png").into(iv_img);
Log.i("info", "测试输出Log");
runOnUiThread(Runnable {
kotlin.run {
toast("测试弹出提示")
}
})
tv_getdata.setOnClickListener(this);
tv_intent.setOnClickListener(this)
Utils().foo(this);
ApiClient().getListRepo("1", ResultObserver(object : ResultListener<List<User>> {
override fun complete(t: List<User>) {
showToast(this@MainActivity, t.size.toString() + " " + t.get(0).full_name)
}
override fun onError(e: Throwable) {
}
}));
}
override fun onClick(v: View?) {
when (v!!.id) {
R.id.tv_getdata -> {
getData()
}
R.id.tv_intent -> {
var intent = Intent(this@MainActivity, SencondActivity::class.java);
intent.putExtra("name", "名字")
intent.putExtra("id", 12)
startActivity(intent)
}
}
}
fun getData() {
Thread(Runnable {
users = ApiClient().getListRepo("1");
userList = users!!.execute().body();
for (i in userList!!.indices) {
Log.i("info", "用户:" + userList!!.get(i).full_name);
}
}).start();
}
}
SecondActivity.kt
package com.tandong.kotlin.ui
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.OrientationHelper
import com.tandong.kotlin.R
import com.tandong.kotlin.adapter.ListAdapter
import kotlinx.android.synthetic.main.activity_second.*
class SencondActivity : AppCompatActivity() {
private var name: String? = null
private var id: Int? = null
private var adapter: ListAdapter? = null
private var layoutManager: LinearLayoutManager? = null
private var list: ArrayList<String>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
initView()
}
private fun initView() {
name = intent.getStringExtra("name")
id = intent.getIntExtra("id", 1)
tv_intent.setText(name + " " + id)
layoutManager = LinearLayoutManager(this)
rv.setLayoutManager(layoutManager)
layoutManager!!.orientation = OrientationHelper.VERTICAL
list = ArrayList<String>()
for (i in 0..9) {
list!!.add("" + i)
}
adapter = ListAdapter(this@SencondActivity, list!!);
rv.setAdapter(adapter)
}
}
ListAdapter.kt
package com.tandong.kotlin.adapter
import android.content.Context
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.RecyclerView.ViewHolder
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import com.tandong.kotlin.R
/**
* Created by Tandong on 2018/1/17.
*/
class ListAdapter(private val mContext: Context, private val mDatas: List<String>) : RecyclerView.Adapter<ListAdapter.MyViewHolder>() {
private var inflater: LayoutInflater? = null
override fun getItemCount(): Int {
return mDatas!!.size
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.tv.text = mDatas!![position]
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
inflater = LayoutInflater.from(mContext)
val view = inflater!!.inflate(R.layout.item_list, parent, false)
return MyViewHolder(view)
}
class MyViewHolder(view: View) : ViewHolder(view) {
var tv: TextView
init {
tv = view.findViewById(R.id.tv_text)
}
}
}
Conf.kt
package com.tandong.kotlin.base
/**
* Created by Tandong on 2018/1/17.
*/
object Conf {
val BASE_URL: String? = "https://api.github.com/"
}
LoadingView.kt
package com.tandong.kotlin.views
import android.app.Dialog
import android.content.Context
import android.content.DialogInterface
import android.view.Gravity
import android.view.WindowManager
import com.tandong.kotlin.R
/**
* Created by Tandong on 2017/7/19.
*/
class LoadingView : Dialog {
constructor(context: Context) : super(context) {
init(context)
}
constructor(context: Context, themeResId: Int) : super(context, R.style.Loading) {
init(context)
}
protected constructor(context: Context, cancelable: Boolean, cancelListener: DialogInterface.OnCancelListener) : super(context, cancelable, cancelListener) {
init(context)
}
private fun init(context: Context) {
setContentView(R.layout.layout_loading)
setCanceledOnTouchOutside(false)
val lp = window!!.attributes
lp.width = WindowManager.LayoutParams.WRAP_CONTENT
lp.height = WindowManager.LayoutParams.WRAP_CONTENT
lp.gravity = Gravity.CENTER
lp.dimAmount = 0f
window!!.attributes = lp
}
}
ApiClient.kt,retrofit和rxjava写法
package com.tandong.kotlin.net
import com.tandong.kotlin.base.Conf
import com.tandong.kotlin.entity.User
import io.reactivex.Observable
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
/**
* Created by Tandong on 2018/1/15.
*/
class ApiClient constructor() {
private var apiservice: ApiService? = null;
private var retrofit: Retrofit? = null;
init {
retrofit = Retrofit.Builder().baseUrl(Conf.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
apiservice = retrofit!!.create(ApiService::class.java)
}
fun getListRepo(id: String): Call<List<User>> {
return apiservice!!.listRepos(id);
}
private fun <T> subscribeOnobserveOn(observable: Observable<T>, observer: Observer<T>) {
observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(observer)
}
fun getListRepo(id: String, resultObserver: ResultObserver<List<User>>) {
subscribeOnobserveOn(apiservice!!.getUserList(id), resultObserver)
}
}
ApiService.kt写法
package com.tandong.kotlin.net
import com.tandong.kotlin.entity.User
import io.reactivex.Observable
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Path
/**
* Created by Tandong on 2018/1/17.
*/
interface ApiService {
@GET("users/{user}/repos")
fun listRepos(@Path("user") user: String): Call<List<User>>
@GET("users/{user}/repos")
fun getUserList(@Path("user") user: String): Observable<List<User>>
}
Utils.kt,这里面的测试例子借用的别人的
package com.tandong.kotlin.utils
import android.app.Activity
import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
/**
* Created by Tandong on 2018/1/16.
*/
class Utils {
fun foo(context: Context) {
Toast.makeText(context, "文本", Toast.LENGTH_LONG).show();
print("成员函数Foo")
} // 成员函数
fun demo(x: Any) {
if (x is String) {
print(x.length) // x 自动转换为字符串
}
}
/*
* kotlin对字符串的加强,三个引号"""中可以包含换行、反斜杠等等特殊字符
* */
fun testString() {
val str1 = "abc"
val str2 = """line1\n
line2
line3
"""
val js = """
function myFunction()
{
document.getElementById("demo").innerHTML="My First JavaScript Function";
}
""".trimIndent()
println(str1)
println(str2)
println(js)
}
/*
* kotlin字符串模版,可以用$符号拼接变量和表达式
* */
fun testString2() {
val strings = arrayListOf("abc", "efd", "gfg")
println("First content is $strings")
println("First content is ${strings[0]}")
println("First content is ${if (strings.size > 0) strings[0] else "null"}")
}
/*
*Kotlin中,美元符号$是特殊字符,在字符串中不能直接显示,必须经过转义,方法1是用反斜杠,方法二是${'$'}
* */
fun testString3() {
println("First content is \$strings")
println("First content is ${'$'}strings")
}
/*
* 用apply语句简化类的初始化,在类实例化的时候,就可以通过apply把需要初始化的步骤全部实现,非常的简洁
* */
fun testApply(context: Context) {
var imgView = ImageView(context).apply {
setBackgroundColor(0)
setImageBitmap(null)
}
var textView = TextView(context).apply {
text = "content"
textSize = 20.0f
setPadding(10, 0, 0, 0)
}
}
fun test01() {
val list = listOf(2, 5, 10)
/*
* 传人函数来过滤
* */
println(list.filter { it > 4 })
}
/*
* kotlin中,when是表达式,可以取代Java 中的switch,when的每个分支的最后一行为当前分支的值
* */
fun getPoint(grade: Char) = when (grade) {
'A' -> "GOOD"
'B', 'C' -> {
println("test when")
"OK"
}
'D' -> "BAD"
else -> "UN_KNOW"
}
/*
* show toast in activity
* */
fun Activity.toast(msg: String) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
}
/**
* 获取版本号VersionCode
*/
fun getVersionCode(context: Context): Int {
val packageManager = context.packageManager
val packageInfo: PackageInfo
var versionCode = ""
try {
packageInfo = packageManager.getPackageInfo(context.packageName, 0)
versionCode = packageInfo.versionCode.toString() + ""
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
}
return Integer.parseInt(versionCode)
}
}
针对混淆的话,加入
-dontwarn kotlin.**
主要的就这些,完整KotlinDemo可以在Github上体验。
https://github.com/jaychou2012/KotlinDemo
后续继续完善。
参考文献:
[1]Kotlin for Android - Kotlin Programming Language[OL].http://kotlinlang.org/docs/reference/android-overview.html,2018-1-17