一个会上瘾的语言-Kotlin

2017-05-17  本文已影响543人  KeepDreaming

前言

因为一些原因,需要写一个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里面进行RecyclerViewItem进行操作(在这里,我在这个方法里面调用了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地址
代码可能有点乱,见谅。

资料

上一篇下一篇

猜你喜欢

热点阅读