
如果你對RecyclerView還沒有完全了解,可參看我的這一篇:
3.2.1 一篇文章完全掌握 RecycleView 的六大用法
想要知道 Kotlin 是如何簡化我們的人生的個很有趣的方式就是去創(chuàng)造一個 RecyclerView 適配器,在上一篇中,我們學會了 RecyclerView 的使用,這一篇我們用 Kotlin 實現(xiàn) RecyclerView 中用到的 Adapter,你會發(fā)現(xiàn)整個工程代碼會用如此簡單并且易懂的方式組織在一起。
一、Kotlin 中的 RecyclerView Adapter
我們會創(chuàng)造一個包含一個圖片和一個標題的條目適配器,之所以做的如此簡單,是因為我們不必花過多的精力在修改單個的條目上面,所以我們現(xiàn)在需要創(chuàng)建一個簡單的數(shù)據(jù)類條目,然后創(chuàng)建一個適配器,最后把它放到 RecyclerView。
- 數(shù)據(jù)類條目
data class Item(val id: Long, val title: String, val url: String)
在我們這樣定義過這個數(shù)據(jù)類之后,他就自己創(chuàng)建了它的構造器,并且他此時此刻已經(jīng)有了自己的一些不可變的屬性以及一些有用的函數(shù)實現(xiàn),比如:equals 或 hashCode。
-
適配器 Adapter
適配器的結構如下,它會自己創(chuàng)建一些必須的方法:
class MyAdapter : RecyclerView.Adapter() {
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder {
}
override fun onBindViewHolder(holder: ViewHolder?, position: Int) {
}
override fun getItemCount(): Int {
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
}
這樣我們就創(chuàng)建了一個由原始 ViewHolder 擴展而來的 ViewHolder 類,因為適配器需要原始抽象類的實現(xiàn)。另外,有些元素被標注為 nullable。這是因為如果庫沒有適當?shù)?@NonNull 標注的話 Kotlin 就沒有方法知道 null 類型是否是允許的,所以這就要讓我們來決定了。如果我們通過默認方式創(chuàng)建方法了,它就會認為其值是 nullable,但是進一步研究支持庫,我們就知道哪些值是為 null,所以我們在這里就能夠刪除它,于是代碼可以簡化成這樣:
class MyAdapter : RecyclerView.Adapter() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
}
override fun getItemCount(): Int {
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
}
-
構造器 constructor
適配器需要接收項目參數(shù)和適配器,這就像這樣:
class MyAdapter(val items: List, val listener: (Item) -> Unit)
我們可以借助擴展函數(shù)的方式來進行實現(xiàn),那么接下來的代碼就變得非常簡單:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(parent.inflate(R.layout.view_item))
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(items[position], listener)
override fun getItemCount() = items.size
這里面的三個方法都可以實現(xiàn)如此簡約的形式,并獲得以前的結果,僅僅用量三行我們就實現(xiàn)了完整的適配器。
關于擴展函數(shù),我們在之前的文章當已經(jīng)詳細講解過了,沒有看過的同學請移步這里。。。,當然,在這里我們可以簡單的理解為我們?yōu)?ViewGroup 和 ViewHolder 添加了額外擴展出來的 inflate 、bind 這樣的函數(shù),以便于我們可以直接像上面這樣使用。
當然了,如果你覺得還是有一點難以理解的話,那么我們暫且為止可以把他先還原成下面的代碼:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(TextView(parent.context))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.textView.text = items.[position]
}
override fun getItemCount(): Int = items.size
你會發(fā)現(xiàn)又是如此,我們可以像訪問屬性一樣訪問 context 和 text,當然我們也可以保持以往那樣操作(使用 getters 和 setters),但是我們會得到一個編譯器的警告。如果你還是傾向于Java中的使用方式,這個檢查也是可以被關閉的。但是一旦你使用上了這種屬性調用的方式你就會發(fā)現(xiàn)他幫我們節(jié)省了額外的字符總量。
-
ViewHolder
ViewHolder 里面的值就是由剛才的數(shù)據(jù)類分配來的:
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(item: Item, listener: (Item) -> Unit) = with(itemView) {
itemTitle.text = item.title
itemImage.loadUrl(item.url)
setOnClickListener { listener(item) }
}
}
-
綁定適配器
最后剩下的事就是綁定適配器,我們回到 MainActivity,現(xiàn)在簡單地創(chuàng)建一系列的String放入List中,然后使用創(chuàng)建分配Adapter實例:
private val items = listOf(
"Kotlin ",
"RecyclerView",
"WilFlow")
override fun onCreate(savedInstanceState: Bundle?) {
...
val testList = findViewById(R.id.list) as RecyclerView
testList .layoutManager = LinearLayoutManager(this)
testList .adapter = TestListAdapter(items)
}
List的創(chuàng)建
盡管我們以后會說到 Collection ,但是我們現(xiàn)在僅僅簡單地通過使用一個函數(shù) listOf 創(chuàng)建一個常量的 List(很快我們就會學習 mmutable)。它接收一個任何類型的 vararg(可變長的參數(shù)),它會自動推斷出結果的類型。
當然也還有很多其它的函數(shù)可以選擇,比如:setOf、arrayListOf 或者 hashSetOf。
我們在上面很簡短的代碼中看到了很多之前說過的東西,比如:基本類型、變量、屬性等比較重要的概念,如果你沒能看懂的話,那么推薦先看看這個(入門文章):。。。
如果你想要代碼更加精簡,你可以采用 Kotlin 擴展函數(shù)對 ViewHolder 進行簡化詳情參看這篇:Kotlin 擴展函數(shù)詳解。
二、為 RecyclerView 添加點擊事件
我們知道在開發(fā)App的過程中,前列表的每一個item布局都應該做一些工作的,比如我們接下來要說的點擊事件。那么我們要做的第一件事就是去創(chuàng)建一個合適的XML布局文件,當然了這個布局文件能夠符合我們的需要就行,所以讓我們創(chuàng)建一個名為item.xml的layout:
- 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="wrap_content"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="@dimen/spacing_xlarge">
<ImageView
android:id="@+id/icon"
android:layout_width="48dp"
android:layout_height="48dp"
tools:src="@mipmap/ic_launcher" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/spacing_xlarge"
android:layout_marginRight="@dimen/spacing_xlarge"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
tools:text="May 14, 2015" />
<TextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
tools:text="Light Rain" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/maxTest"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
tools:text="30o" />
<TextView
android:id="@+id/minTest"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
tools:text="15o" />
</LinearLayout>
</LinearLayout>
對于我們需要用到的數(shù)據(jù)類,我們可以這樣去加載它:
data class Forecast(val date: String, val description: String, val high: Int, val low: Int, val iconUrl: String)
熟悉上節(jié)我們說過的 RecyclerView 監(jiān)聽器創(chuàng)建和綁定過程的同學應該知道,我們此時此刻應該創(chuàng)建一個 click listener,如果你還不熟悉的話,請到這里來看一下:。。。
接下來我們來這樣定義它:
public interface OnItemClickListener {
operator fun invoke(forecast: Forecast)
}
然后我們可以這樣使用:
itemClick.invoke(forecast)
或者省略掉 invoke:
itemClick(forecast)
然后我們創(chuàng)建一個 ViewHolder
它將負責去綁定數(shù)據(jù)到新的View:
class ViewHolder(view: View, val itemClick: OnItemClickListener) :
RecyclerView.ViewHolder(view) {
private val iconView: ImageView
private val dateView: TextView
private val descriptionView: TextView
private val maxTestView: TextView
private val minTestView: TextView
init {
iconView = view.find(R.id.icon)
dateView = view.find(R.id.date)
descriptionView = view.find(R.id.description)
maxTestView = view.find(R.id.maxTest)
minTestView = view.find(R.id.minTest)
}
fun bindTest(test: Test) {
with(forecast) {
Picasso.with(itemView.ctx).load(iconUrl).into(iconView)
dateView.text = date
descriptionView.text = description
maxTestView.text = "${high.toString()}"
minTestView.text = "${low.toString()}"
itemView.setOnClickListener { itemClick(test) }
}
}
}
改變 Adapter:
現(xiàn)在 Adapte r的構造方法可以接收一個 itemClick了,那么創(chuàng)建和綁定數(shù)據(jù)也就變得更加簡單,我們這樣來改寫它:
public class TestListAdapter(val weekTest: TestList,
val itemClick: TestListAdapter.OnItemClickListener) :
RecyclerView.Adapter<TestListAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
ViewHolder {
val view = LayoutInflater.from(parent.ctx)
.inflate(R.layout.item_forecast, parent, false)
return ViewHolder(view, itemClick)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindForecast(weekTest[position])
}
......
}
設置適配器
最后我們就可以在 MainActivity 中調用 setAdapter 來為我們的 RecyclerView 設置適配器并實現(xiàn)監(jiān)聽功能了,最后結果是這樣的:
testList.adapter = TestListAdapter(result,
object : TestListAdapter.OnItemClickListener{
override fun invoke(test: Test) {
toast(forecast.date)
}
})
三、對 setOnClickListener() 的簡化與使用
(1)用 Lambda 簡化setOnClickListener()
正如我們開篇提到的那樣,Kotlin 是允許 Java 庫的一些優(yōu)化的,所以我們的 Interface 中包含的單個函數(shù)可以被替代為一個函數(shù)。所以如果我們這么去定義上面的點擊監(jiān)聽器的話,它依然會正常執(zhí)行:
fun setOnClickListener(listener: (View) -> Unit)
一個 lambda 表達式通過參數(shù)的形式被定義在箭頭的左邊(被圓括號包圍),然后在箭頭的右邊返回結果值。在這個例子中,我們接收一個View,然后返回一個Unit(沒有東西)。所以根據(jù)這種思想,我們可以把前面的代碼簡化成這樣:
view.setOnClickListener({ view -> toast("Click")})
這是非常棒的簡化了:當我們定義了一個方法,我們必須使用大括號包圍,然后在箭頭的左邊指定參數(shù),在箭頭的右邊返回函數(shù)執(zhí)行的結果。而如果左邊的參數(shù)沒有使用到的話,我們甚至可以更加簡化這段代碼,那就是省略左邊的參數(shù):
view.setOnClickListener({ toast("Click") })
更為湊巧的是,我們這個函數(shù)的最后一個參數(shù)是一個函數(shù),所以我們可以把這個函數(shù)移動到圓括號外面:
view.setOnClickListener() { toast("Click") }
并且,因為我們這個函數(shù)只有一個參數(shù),所以最后我們可以省略這個圓括號:
view.setOnClickListener { toast("Click") }
到此為止,我們對 setOnClickListener() 的簡化工作就結束了,你會發(fā)現(xiàn)這比原始的Java代碼簡短了約5倍多,并且更加容易理解它所做的事情,是不是瞬間愛上 Kotlin 了呢。接下來我們在 Adapter 中使用。
(2)在 Adapter的使用簡化的 OnClickListener
在上面的簡化過程中,我如此艱苦地寫了click listener 的目的就是更好的在這里進行使用。我們首先從TestListAdapter 中刪除 listener 接口,然后使用 lambda 代替:
public class TestListAdapter(val weekTest: TestList,
val itemClick: (Test) -> Unit)
這個itemClick函數(shù)接收一個test參數(shù)然后不返回任何東西,ViewHolder中也可以這么修改:
class ViewHolder(view: View, val itemClick: (Test) -> Unit)
然后其它的代碼保持不變,僅僅改變MainActivity:
val adapter = TestListAdapter(result) { test -> toast(test.date) }
按照上面簡化的規(guī)則,我們最后做出如下的簡化:
val adapter = TestListAdapter(result) { toast(it.date) }
感謝優(yōu)秀的你跋山涉水看到了這里,不如關注下讓我們永遠在一起!