一个会上瘾的语言-Kotlin
前言
因为一些原因,需要写一个Android的小程序。刚好因为最近一段时间接触到了Kotlin,便想着机会难得,终于可以体会一下Kotlin的强大了。为什么我之前不去写Kotlin呢?身为懒癌晚期的我不做解释。(PS:由于这也是我个人的第一篇文章,文采什么鬼的也都是没有的,各位大佬也就凑活着看吧。)
Kotlin的安装
Kotlin的安装这里就不做过多的介绍了,度娘一堆堆的,随便找一篇装一下应该还是很简单的。本篇主要介绍我在这个程序开发中Kotlin的使用。
正题
安装好了之后,在Android Studio中创建一个项目,和平常一样。都会自动生成一个MainActivity类以及其他一堆堆的东西。不同的是这个MainActivity类是一个后缀为kt的文件,和我们平时使用的java文件是不一样的,打开后可以看到:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
可以看到,原本的extends关键字不见了,取而代之的是 : ,如果要实现一个接口,也不是使用implements,而是在AppCompatActivity()的后面加一个逗号,然后写上你要实现的接口名字就OK了。通过上面的代码可以发现,Kotlin的编程是不需要;
这种东西的,看上去和实际开发都是一件很棒的事。在Kotlin里面,冒号几乎是无处不见,具体作用我们后面慢慢来讲。
在平时开发中,总会去声明许多的变量和常量,平常声明一个变量:
private String a;
在Kotlin中:
var a:String? = null
可以看到,是两个完全不一样的方式,让我们拆开看一下。var
是用来声明一个变量,而常量则是用val
来进行声明。a
这是变量名就不需要多说了,紧跟一个冒号,然后是一个变量的类型String
后面加一个?
表示当前变量是可空的,但是做了非空判断的变量,在使用上不能直接使用,如:
val a1 = a.length
这时候,编译就会报红,在这里需要对a进行非空判断才能使用它,这样子就显得很麻烦,如果一个项目中如果有十几二十个变量,光这个判断就能写一堆了。显然,设计师也想到了这个问题,在使用这种变量的时候有两种方式,
一:val a1 = a?.length
这时候表示,如果a
这时候是一个空的,则返回null
,否则就返回a.length
二:val a1 = a!!.length
这时候表示,如果a
是一个空的,则出现空指针异常,否则就返回a.length
在Android里面,我们经常回去定义一些Fragment、控件等等的东西。让我们来看看在Kotlin中该如何使用的:
XML:
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
var mBtn:Button? = null
mBtn = findViewById(R.id.btn) as Button
mBtn?.text = "按钮"
上面的这两行代码相信作为一个Android开发,非常的熟悉了。虽然用了Kotlin的写法,但是理解起来也很快。可以看到,Kotlin中强转都是通过as
这个关键字。通过代码发现,我们熟悉的setText()
这个方法没了,取而代之的是text
。Kotlin中,如果一个属性同时拥有get set
,那么就会合并成一个属性,赋值就如同上面的方式,取值便是调用btn.text
便可。但是,在Kotlin中,有一个非常非常方便的方法来使用一个控件:
btn.text = "按钮"
就这一句,就可以和上面的三行代码呈现相同的效果。在Kotlin中,可以直接使用XML中给控件定义的ID值,来操作这个控件。要使用这种方式,需要导入一个包:
import kotlinx.android.synthetic.main.activity_test.*
当你直接只用ID的时候,Android Studio会帮你导入这个包。如此一来,省略的代码就很可观了。
在Kotlin中,没有new
这个关键字,所以在声明一个类就变得更加简洁:
var t:Test = Test("参数1");
接下来让我们看一下如何去写一个方法:
public void show(){
//具体操作
}
而在Kotlin中,长这个样子:
fun show(){
//具体操作
}
Kotlin中声明一个方法用fun
关键字,后面跟着方法命。当然如果要定义一个有返回值的方法怎么办?万能的冒号就出现了:
fun show() : String{
return "abc"
}
在Kotlin里面,有一个特性叫默认参数,具体就是在你的方法的括号里面定义参数,并且可以给这个参数赋予一个默认值,具体:
fun show(a: String,b:String = "c") : String{
return a + b
}
这个方法里面,给参数b
赋予了一个默认值是c
,如果我们调用这个方法的时候用show("123")
这时候这个方法会给我们返回123c
,正常使用show("123","abc")
的时候,便会返回123abc
的字符串。身为开发者的你想到什么了没有?没错,就是重载。通过默认参数,原本在java中需要写两个方法,在Kotlin中只要一个方法就够了。在一个项目中,这一特性就可以少写很多代码了。
在Kotlin中,如果想继承一个自己写的类,需要在class
关键字前面加上open
,表示当前类是可以继承的。而方法也一样,如果不加open
关键字,继承后无法找到该方法。如果子类中使用了父类的方法,也会在方法加上override
关键字,相对应的Java中则是@override
。在Kotlin中,如果不给类和方法使用修饰符,默认是public
。其中,Kotlin中的修饰符没有了default
,而是多了一个internal
,表示在当前模块(Model)可以使用。其余作用域都与Java中一致。
在Kotlin中,构造方法的使用:
open class Test constructor(a:String) {
var a: String? = null
init {
this.a = a
}
}
Kotlin中,在定义类名的后面加上constructor
关键字紧跟一对括号,括号内传入需要的参数。可以看到在代码中有一个init{}
的东西(算代码块吧。算了,不重要),这里面就是用来放置构造方法的代码体。每个类不管有参无参,都会有一个init{}
的东西用来给开发者初始化一些东西。
Kotlin中,并没有static
这个关键字,那么我们如果要定义一个静态的属性或者方法怎么办呢?这时候,伴生对象就上场了。在开发中,我是把伴生对象当作一个用来定义静态属性或者方法来使用(具体还有什么作用,就需要继续研究了)。下面看一下用法:
A:
companion object{
val name:String? = "H"
}
B:
Log.d("companion object",Test1.name)
在A类中,我们通过伴生对象定义了一个字符串,然后在B类中通过类名调用了它。这就是伴生对象一个简单的使用。
在Kotlin中,有一个特性叫扩展函数。它可以将我们自定义的一个方法加到任何一个地方。比如,给Activity加一个更加方便的Toast方法:
fun Activity.toast(msg:String,duration:Int = Toast.LENGTH_SHORT){
Toast.makeText(this,msg,duration)
}
上述代码,我们给Activity加了一个toast
方法,需要传入一个msg
参数作为消息主体,时间默认是Toast.LENGTH_SHORT
,也可以进行修改。这个方法在任何继承了Activity
的类中都可以进行调用。具体如何将这个特性发扬光大,就靠广大的开发者了。(扩展方法要定义在一个后缀.kt的文件下,我之前定义在类里面,并没有什么**用)
Kotlin对For循环进行了改变:
1、 in关键字,相当于foreach:
var m:MutableList<String> = mutableListOf()
for (i in m){
Log(i)
}
MutableList
相当于ArrayList
,在Kotlin中也有ArrayList
,不过它变成了只读的。而mutableListOf
则是用来初始化这个集合的。代码中,定义了一个i
来代表m
里面的单个元素,通过Log打印在控制台(Log是通过扩展函数实现的)。在Kotlin中,foreach还有另一种实现方法:
m.forEach {
Log(it)
}
这两种方式是等同的。
2、
for (i in m.indices){
Log("${i}")
}
通过indices
,可以循环拿到m
集合里面元素的下标。这里,我们的Log
打印通过${代码块}
的方式,Kotlin允许通过这种方式进行字符串的拼接,使代码更加直观、美观。
3、
for ((i,v) in m.withIndex()){
Log("$i and $v")
}
有时候如果我们既想获取下标也想拿到相对应的值,就可以通过withIndex
这个属性。
Kotlin用when
来取代了switch
。
when(edit.text.toString()){
"1" -> Log("a")
"2","4" -> Log("b")
"3" -> Log("c")
else -> {
Log("默认")
}
}
在这段代码中,我们根据在edit
这个输入框中的值进行匹配,如果输入1
则打印a
,以此类推。when
的表达方式,相比较switch
,直观了许多。
Android中,经常会用到Intent来进行一些跳转界面或者进行一些打开照相机之类的操作。那么,我们来看一下,在Kotlin中怎么操作:
mPicture.setOnClickListener {
var i = intentFor<PictureActivity>(
Pair("1",1)
)
startActivity(i)
}
这个就是在Kotlin中跳转Activity的语句。这里通过intentFor
,在泛型里面将要跳转的类名传入便可,紧跟的括号里面可以放入想要传给目标类的数据,第一次参数是String
,第二个数据传的类型是Any
(相当于Java中的Object
)。不过,intentFor
的写法是Anko
提供的。Anko
封装了很多实用的方法,但是Anko
最大的亮点是用来写布局,而且非常的简洁(当然,我并没有试过。为什么知道?百度呗。)。有关Anko
的一些东西,有生之年我会写一篇出来。在这个项目中有使用到跳转相机,看看如何实现的:
var uri: Uri? = null
path = Environment.getExternalStorageDirectory().absolutePath + "/${System.currentTimeMillis()}.png"
var i: Intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE,null)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ){
uri = FileProvider.getUriForFile(this,"com.testprojects.fileprovider",File(path))
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
i.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
}else{
uri = Uri.fromFile(File(path))
}
i.putExtra(MediaStore.EXTRA_OUTPUT, uri)
startActivityForResult(i, 1)
简单的解释一下这段代码,定义一个Uri
,用于隐式跳转作为参数。然后定义一个路径字符串,用于存放拍完后照片存储的位置(我这里是将照片放在手机内存的根目录,通过获取系统时间作为照片的名字)。然后定义一个Intent
,定义动作。接着是一个判断,用于判断手机版本是否是7.0。在7.0的时候访问文件需要用到一个FileProvider
的东西,用于临时访问,至于这东西怎么用,百度一下就好了,不会难。当时刚开始写项目的时候,我手机是5.1的。根本没考虑什么权限什么鬼的。结果,一夜之间,自动升级到了7.0。第二天一脸懵逼的我因为权限这些的折腾了一早上(题外话题外话)。
最后,讲一下RecyclerView的Adapter:
class PictureAdapter(context: Context, d:MutableList<PictureBean>, val onClick:(Int)->Unit,hide:Boolean) : RecyclerView.Adapter<PictureAdapter.ViewHolder>(){
var c:Context? = null
var data:MutableList<PictureBean>? = null
var h:Boolean? =null
init {
this.c=context
this.data=d
this.h = hide
}
override fun onBindViewHolder(holder: ViewHolder?, position: Int) {
holder!!.abc(position)
}
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder {
val v:View = LayoutInflater.from(c).inflate(R.layout.picture_view_item,null)
return ViewHolder(v)
}
override fun getItemCount(): Int {
return data!!.size
}
inner class ViewHolder(itemView:View) : RecyclerView.ViewHolder(itemView){
fun abc(i: Int){
if (h!!){
itemView.imgSelect.visibility = View.VISIBLE
}else{
itemView.imgSelect.visibility = View.GONE
}
if (!position.equals(itemView.item_img.getTag(R.id.item_img))){
itemView.item_img.setTag(R.id.item_img,position)
Glide.with(c)
.load(data!!.get(position).path)
.into(itemView.item_img)
}
itemView.setOnClickListener { onClick.invoke(i) }
}
}
}
同样的,建立一个类继承RecyclerVIew.Adapter
,在构造方法里面传入上下文,数据,onClick(下面讲),布尔值(偷懒复用这个adapter,控制控件显示用,不要在意)。在类里面声明变量,在init{}
里面赋值(惯用套路)。重写onBindViewHolder onCreateViewHolder getItemCount
这三个方法。在onBindViewHolder
里面进行RecyclerView
的Item
进行操作(在这里,我在这个方法里面调用了ViewHolder里的方法,不过不重要,效果都一样),在onCreateViewHolder
里面获取Item
的布局并返回,在getItemCount
里面返回显示Item
的个数。然后写一个内部类继承RecyclerView.ViewHolder
,这里注意,类前面要加上inner
才能访问到外面类的变量。在自定义的ViewHolder
里面,我在里面写了一个方法,供上面调用,方法里面也就设置控件之类的。但是,有一点特别的:
itemView.setOnClickListener { onClick.invoke(i) }
还记得我们构造方法里面的第三个参数吗?就是这里面的onClick
,传入onBindViewHolder
方法参数里面的position
,通过这样子的调用就实现了RecyclerView
的点击事件。
咳咳...(就放那个赌神出场的那个BGM,没错没错,就那个)。没看错,就是这么勉强算是两句话的代码就实现了之前还要写接口,还要传来传去才能用的点击事件。当然onLongClick
也是一样的道理,改一下itemView.setOnClickListener
就可以了。怎么用呢?
adapter = PictureAdapter(this, l!!, {
Log("$it")
},true)
在这里,传入上下文,数据,第三个就是我们点击事件要做的事情,it
是我们当前点击的Item
的脚标,你可以在这里面写,也可以写一个方法丢里面,看个人了。
最后说两句
因为从没写过这种东西,上学时候作文又写的不好(我也很绝望啊)。所以可能看的时候会怪怪的,也就凑合着看吧。Kotlin还有很多很多没发现的用法,很多的新特性没有挖掘出来,这篇文章也只是用来抛砖引玉。上述内容如果有哪里有问题的请各位读者及时指出,以免误导了别人,有什么看不懂的也可以问我,知无不言。
上述项目github地址
代码可能有点乱,见谅。