使Forecast list可點(diǎn)擊
作為一個(gè)真正的app,當(dāng)前列表的每一個(gè)item布局應(yīng)該做一些工作。第一件事就是創(chuàng)建一個(gè)合適的XML,能符合我們的需要就行。我們希望顯示一個(gè)圖標(biāo),日期,描述以及最高和最低溫度。所以讓我們創(chuàng)建一個(gè)名為item_forecast.xml的layout:
<?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="match_parent"
android:padding="@dimen/spacing_xlarge"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal">
<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_weight="1"
android:layout_marginLeft="@dimen/spacing_xlarge"
android:layout_marginRight="@dimen/spacing_xlarge"
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/maxTemperature"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
tools:text="30"/>
<TextView
android:id="@+id/minTemperature"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
tools:text="15"/>
</LinearLayout>
</LinearLayout>
Domain model和數(shù)據(jù)映射時(shí)必須生成完整的圖標(biāo)url,所以我們可以這樣去加載它:
data class Forecast(val date: String, val description: String,
val high: Int, val low: Int, val iconUrl: String)
在ForecastDataMapper中:
private fun convertForecastItemToDomain(forecast: Forecast): ModelForecast {
return ModelForecast(convertDate(forecast.dt),
forecast.weather[0].description, forecast.temp.max.toInt(),
forecast.temp.min.toInt(), generateIconUrl(forecast.weather[0].icon))
}
private fun generateIconUrl(iconCode: String): String
= "http://openweathermap.org/img/w/$iconCode.png"
我們從第一個(gè)請(qǐng)求中得到圖標(biāo)的code,用來(lái)組成完成的圖標(biāo)url。加載圖片最簡(jiǎn)單的方式是使用圖片加載庫(kù)。Picasso是一個(gè)不錯(cuò)的選擇。它需要加到build.gradle的依賴(lài)中:
compile "com.squareup.picasso:picasso:<version>"
如此,Adapter也需要一個(gè)大的改動(dòng)了。還需要一個(gè)click listener,我們來(lái)定義它:
public interface OnItemClickListener {
operator fun invoke(forecast: Forecast)
}
如果你還記得上一課程,當(dāng)被調(diào)用時(shí)invoke方法可以被省略。所以我們來(lái)使用它來(lái)簡(jiǎn)化。listener可以被以下兩種方式調(diào)用:
itemClick.invoke(forecast)
itemClick(forecast)
ViewHolder將負(fù)責(zé)去綁定數(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 maxTemperatureView: TextView
private val minTemperatureView: TextView
init {
iconView = view.find(R.id.icon)
dateView = view.find(R.id.date)
descriptionView = view.find(R.id.description)
maxTemperatureView = view.find(R.id.maxTemperature)
minTemperatureView = view.find(R.id.minTemperature)
}
fun bindForecast(forecast: Forecast) {
with(forecast) {
Picasso.with(itemView.ctx).load(iconUrl).into(iconView)
dateView.text = date
descriptionView.text = description
maxTemperatureView.text = "${high.toString()}"
minTemperatureView.text = "${low.toString()}"
itemView.setOnClickListener { itemClick(forecast) }
}
}
}
現(xiàn)在Adapter的構(gòu)造方法接收一個(gè)itemClick。創(chuàng)建和綁定數(shù)據(jù)也是更簡(jiǎn)單:
public class ForecastListAdapter(val weekForecast: ForecastList,
val itemClick: ForecastListAdapter.OnItemClickListener) :
RecyclerView.Adapter<ForecastListAdapter.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(weekForecast[position])
}
...
}
如果你使用了上面這些代碼,parent.ctx不會(huì)被編譯成功。Anko提供了大量的擴(kuò)展函數(shù)來(lái)讓Android編程更簡(jiǎn)單。舉個(gè)例子,activitys、fragments以及其它包含了ctx這個(gè)屬性,通過(guò)ctx這個(gè)屬性來(lái)返回context,但是在View中缺少這個(gè)屬性。所以我們要?jiǎng)?chuàng)建一個(gè)新的名叫ViewExtensions.kt文件來(lái)代替ui.utils,然后增加這個(gè)擴(kuò)展屬性:
val View.ctx: Context
get() = context
從現(xiàn)在開(kāi)始,任何View都可以使用這個(gè)屬性了。這個(gè)不是必須的,因?yàn)槟憧梢允褂脭U(kuò)展的context屬性,但是我覺(jué)得如果我們使用ctx的話(huà)在其它類(lèi)中也會(huì)更有連貫性。而且,這是一個(gè)很好的怎么去使用擴(kuò)展屬性的例子。
最后,MainActivity調(diào)用setAdapter,最后結(jié)果是這樣的:
forecastList.adapter = ForecastListAdapter(result,
object : ForecastListAdapter.OnItemClickListener{
override fun invoke(forecast: Forecast) {
toast(forecast.date)
}
})
如你所見(jiàn),創(chuàng)建一個(gè)匿名內(nèi)部類(lèi),我們?nèi)?chuàng)建了一個(gè)實(shí)現(xiàn)了剛剛創(chuàng)建的接口的對(duì)象??雌饋?lái)不是很好,對(duì)吧?這是因?yàn)槲覀冞€沒(méi)開(kāi)始試使用另一個(gè)強(qiáng)大的函數(shù)式編程的特性,但是你將會(huì)在下一章中學(xué)習(xí)到怎么去把這些代碼轉(zhuǎn)換得更簡(jiǎn)單。
去代碼庫(kù)中更新新的代碼。UI開(kāi)始看起來(lái)更好了。