上一篇简单介绍了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基本能够满足我们大部分的需求了。
快快尝试一下吧!