接上篇:Kotlin的变量、属性、类、构造函数、继承、方法
上一篇简单介绍了Kotlin的一些基础构成,当然还有像对象声明、操作符等等都未涉及到,这些会在以后用到的过程中进行详细说明。
项目中ListView列表出现的频率是很高的,我们就以实现一个简单ListView为目标,介绍一下在子线程中获取数据等问题。
首先在layout中新增个listview:
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
在activity中绑定View,之前说了,可以通过 " import kotlinx.android.synthetic.main.activity_main.*" 的方式获取对应layout中的view,可以省略findViewById的代码。那这里我们使用findviewById的方式绑定view:
val listview:ListView=findViewById(R.id.listview) as ListView
可以看到Java中通过“()”完成类型转换,在这里使用了“as”关键字。
创建adapter的布局list_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#ffffff"
android:id="@+id/ll_item">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_weight="1"
android:layout_gravity="center"
android:gravity="center"
android:text="TextView" />
</LinearLayout>
新建adapter类ListViewAdapter.kt
class ListViewAdapter ( val datas: List<String>,val context: Context): BaseAdapter(){
override fun getCount(): Int {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getItem(p0: Int): Any {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getItemId(p0: Int): Long {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun getView(p0: Int, p1: View?, p2: ViewGroup?): View {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
可以看到ListViewAdapter类定义了需要传入datas和context两个函数。一个数据和一个上下文。然后我们队这个adapter进行的一定的修改:
class ListViewAdapter ( val datas: List<String>,val context: Context): BaseAdapter(){
override fun getCount(): Int=datas.size ?: datas.size ?: 0 //注释一
override fun getItem(p0: Int): Any = datas.get(p0)
override fun getItemId(p0: Int): Long = 0
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
var viewHodler:ViewHodler?=null //注释二
var view:View
if(convertView==null){
view= View.inflate(context,R.layout.list_item,null)
viewHodler=ViewHodler(view)
view.tag=viewHodler
}else{
view=convertView
viewHodler=view.tag as ViewHodler
}
if(getItem(position) is String){
//给textview赋值
viewHodler.textView.text=getItem(position).toString()
//隔行变色,闲着啥事
if((position+1)%2!=0)viewHodler.ll_item.setBackgroundColor(context.resources.getColor(R.color.f2f2f2))
}
return view
}
class ViewHodler(var view: View){
var textView: TextView=view.findViewById(R.id.textView) as TextView
var ll_item: LinearLayout=view.findViewById(R.id.ll_item) as LinearLayout
}
}
注释一:getCount()方法大家都知道,是adapter获取item数量的,通过这个数量决定adapter绘制的view个数。这个写法的话,在Kotlin里如果一个方法的返回值可以直接通过计算获得,可以使用等号,不需要括号,这个在前边的文章里有讲过。然后是"datas.size ?: datas.size ?: 0",这句就相当于Java里的"datas.size ? datas.size : 0",这样应该就明白了,?:前边的是判断条件,为true在取datas.size,为false则取0。
注释二:这里的?又是干啥的呢,“?”这是kotlin的一个基本概念,之前有提到,kotlin是空安全的,那么在kotlin中,类型系统将可空类型和非空类型进行了区分,例如:String为不可空类型,String?为可空类型,如果将不可空类型赋值null将会编译不通过。
var str1:String=null //错误
var str2:String="123131" //正确
var str3:String?=null //正确
如果此时操作str3,比如获取其长度str3.length也是编译不通过的,操作可空类型时,需要对其进行判断,否则编译不通过:
var length1:Int=str3.length //错误
var length2:Int=str3!!.length //正确
这时候是不是觉得好烦啊,我要知道这个东西是空的,肯定都改了,哈哈,自然不是这样的,我们可以通过下边的方式解决这种可空类型的引用:
var length3:Int=str3?.length
通过?.调用length,如果str3为空则返回null,否则就返回str3的长度。这样是不是就方便了,也避免了空指针的问题。所以对于可空类型的操作在调用前,需要先检查,因为可能为空,使用?.的形式调用,如果为空在返回null,而使用!!.的形式调用,如果为空则会抛出空指针异常。这就是kotlin的空安全的一种体现。
好咯,回到我们的Listview,上边的adapter除了刚才这两个地方,其他的代码相信大家只要是写过adapter的都能看得懂。那么既然是list列表,肯定是要有数据的吧,这里我们就建一个Request的类来模拟服务器端的数据,Request里有个run的方法返回了一个List<String>:
public class Request{
public fun run():List<String> {
val items= listOf<String>(
"瓦洛兰",
"德玛西亚",
"班德尔城",
"诺克萨斯",
"祖安",
"皮尔特沃夫",
"艾欧尼亚",
"李青",
"阿利斯塔",
"希维尔",
"潘森",
"伊泽瑞尔",
"雷克顿",
"古拉加斯",
"奥利安娜",
"崔斯塔娜",
"泰达米尔",
"马尔扎哈",
"卡西奥佩娅",
"艾尼维亚"
)
return items
}
}
然后我们回到Activity里,给listview绑定adapter:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val listview:ListView=findViewById(R.id.listview) as ListView //绑定listview
val request:Request= Request() //声明添加数据的对象,
var list:List<String>?=request.run()
listview.adapter=ListViewAdapter(list!!,this) //绑定adapter
}
listview.adapter就不说了,相当于Java里的:setAdapter,前边的文章里有介绍。那么运行APP,效果图:
正常开发的情况下,数据的来源肯定不是这样的,而我们从服务器端获取数据网络请求都是在子线程中进行的,关于Android线程的问题,这里就不做解释了(这是基础常识
)。因为kotlin强大的互操作性,之前适用的Java的第三方网络框架都是可以使用的。这里数据和例子都比较简单,就不使用第三方了,只来介绍下kotlin里的线程简单使用。大家比较熟悉的是Java里的AsyncTasks,在kotlin中,Anko库(Anko是JetBrains开发的一个强大的库,包含了很多非常有帮助的函数和属性来避免让你写很多的模板代码)提供了简单的DSL来处理异步任务:
import android.app.Activity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import org.jetbrains.anko.async
import org.jetbrains.anko.uiThread
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initData()
}
fun initData(){
async() {
//可以在此进行网络请求获取数据
//我们还使用Request模拟
val request:Request= Request()
var list:List<String>?=request.run()
uiThread {
listview.adapter=ListViewAdapter(list!!,this@MainActivity)
}
}
}
}
我们通过async函数,在其他线程里执行了网络请求的代码,然后通过uiThread的方式回到主线程给listview绑定了adapter。和Java比有个好处就是如果使用AsyncTasks时,当运行到postExecute的时候Activity被销毁了,那么就会崩溃了,而uiThread不会,如果activity.isFinishing()返回true,uiThread是不会执行的。async基本能够满足我们大部分的需求了。
快快尝试一下吧!
Kotlin学习记录(四)—— 常用集合的使用