SwipeRefreshLayoutとRecycleViewをListViewに導入してみた
ブログエントリで試しているアプリにSwipeRefreshLayoutとRecycleViewをListViewに導入してみたのでまとめていく。
SwipeRefreshLayout
SwipeRefreshLayoutはViewGroupの1つで引っ張って更新する機能を導入することができる。
まずはレイアウトからまとめていく。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
// 更新する要素
.support.v4.widget.SwipeRefreshLayout>
.support.constraint.ConstraintLayout>
|
SwipeRefreshLayoutの子要素は必ず1つになるようにレイアウトを組む必要がある。
次にコードをまとめていく。
1
|
swipeRefreshLayout.setOnRefreshListener { request() }
|
setOnRefreshListener
で更新処理をリスナーに登録する。
更新処理が完了したところでisRefreshing
をfalseにすることでインジケータを停止することができる。
1
|
swipeRefreshLayout.isRefreshing = false |
RecycleView
次にRecycleViewをまとめていく。RecycleViewは大量なリスト表示が必要や頻繁にデータが変わるような場面で限られたViewを効率的に維持してくれる。
RecyclerView.Adapterを実装する
1行のデータをViewとして生成するAdapterを実装する。FragmentからはRecyclerAdapterのreplaceAllを呼び出しRecyclerViewを更新する。
BaseRecyclerAdapterはRecyclerView.Adapter
を継承してonCreateViewHolder, onBindViewHolder, getItemCountを実装している。onCreateViewHolder, onBindViewHolderはそれぞれViewHolderが生成に合わせてviewTypeやpositionに応じたオブジェクトをリストから取得して返している。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
class RecyclerAdapter : BaseRecyclerAdapter<RecyclerView.ViewHolder>() {
fun <B : Binder<RecyclerView.ViewHolder>> replaceAll(objects: List<B>) {
clear()
objects.withIndex().forEach {
insert(it.index, it.value)
}
notifyDataSetChanged()
}
}
open class BaseRecyclerAdapter<VH : RecyclerView.ViewHolder> : RecyclerView.Adapter<VH>() {
private val mObjects: MutableList<Binder<VH>> = mutableListOf()
override fun onCreateViewHolder(parent: ViewGroup,
viewType: Int): VH = getItemByViewType(viewType).onCreateViewHolder(parent)
override fun onBindViewHolder(holder: VH, position: Int) = getItem(position).onBindViewHolder(holder, position)
override fun getItemCount(): Int = mObjects.size
fun getItem(position: Int): Binder<VH> =
mObjects.withIndex().filter { it.index == position }
.takeIf { it.isNotEmpty() }?.let { it[0].value }
?: throw IllegalArgumentException("invalid position=${position}")
private fun getItemByViewType(viewType: Int): Binder<VH> =
mObjects.filter { it.getViewType() == viewType }
.takeIf { it.isNotEmpty() }?.let { it[0] }
?: throw IllegalArgumentException("invalid viewType=${viewType}")
fun insert(index: Int, obj: Binder<VH>) {
mObjects.add(index, obj)
}
fun clear() {
mObjects.clear()
}
}
interface Binder<VH> {
fun onCreateViewHolder(parent: ViewGroup): VH
fun onBindViewHolder(viewHolder: VH, position: Int)
fun getViewType(): Int
}
|
RecyclerView.ViewHolderを保持するBinderクラスを実装する
RecyclerView.ViewHolderは1行分のView参照を保持するクラス。このRecyclerView.ViewHolderを自作のBinderクラスで包みAdapterに応じたメソッドを提供する。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
interface Binder<VH> {
fun onCreateViewHolder(parent: ViewGroup): VH
fun onBindViewHolder(viewHolder: VH, position: Int)
fun getViewType(): Int
}
interface ViewType {
fun viewType(): Int
}
abstract class RecycleBinder(private val context: Context, private val viewType: ViewType) : Binder<RecyclerView.ViewHolder> {
@LayoutRes
abstract fun layoutResId(): Int
abstract fun onCreateViewHolder(view: View): RecyclerView.ViewHolder
override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder {
return onCreateViewHolder(LayoutInflater.from(context).inflate(layoutResId(), parent, false))
}
override fun getViewType(): Int {
return viewType.viewType()
}
}
|
上述したRecycleBinderを継承したForecastViewBinderクラスでViewHolderを引数に取りViewにデータをセットしていく。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
class ForecastViewBinder(private val context: Context,
private val forecast: Forecast) : RecycleBinder(context, ForecastViewType.FORECAST) {
override fun layoutResId() = R.layout.forcast_binder
override fun onCreateViewHolder(view: View): RecyclerView.ViewHolder {
return ViewHolder(view)
}
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
viewHolder as ViewHolder
viewHolder.date.text = DateUtils.formatDateTime(context, forecast.dt * 1000L, FORMAT_NO_YEAR)
viewHolder.main.text = forecast.weather.get(0).main
viewHolder.max.text = forecast.temp.max.toString()
viewHolder.min.text = forecast.temp.min.toString()
forecast.weather.get(0).main.let {
viewHolder.main.text = it
when (it.toLowerCase()) {
"clear" -> R.drawable.ic_sun
"clouds" -> R.drawable.ic_cloud
"fog" -> R.drawable.ic_haze
"light_clouds" -> R.drawable.ic_cloudy
"light_rain" -> R.drawable.ic_rain
"rain" -> R.drawable.ic_rain
"snow" -> R.drawable.ic_snowing
"storm" -> R.drawable.ic_storm
else -> R.drawable.ic_sun // fix me
}.let {
viewHolder.image.setBackgroundResource(it)
}
}
}
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val date: TextView = view.findViewById(R.id.forecast_date)
val main: TextView = view.findViewById(R.id.forecast_weather)
val max: TextView = view.findViewById(R.id.forecast_temp_max)
val min: TextView = view.findViewById(R.id.forecast_temp_min)
val image: AppCompatImageView = view.findViewById(R.id.forecast_ic)
}
|
FragmentからadapterのreplaceAllを呼び出す
Fragmentでadapterを生成してリスト取得したデータをreplaceAllに渡しViewを更新する。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val context = context ?: return
forecastsStore.forecasts()
.observeOn(AndroidSchedulers.mainThread())
.`as`(autoDisposable(this))
.subscribe { forecasts ->
swipeRefreshLayout.isRefreshing = false
cityView.text = "%s/%s".format(forecasts.city.name, forecasts.city.country)
adapter.replaceAll(forecasts.list.map {
ForecastViewBinder(context, it)
})
}
savedInstanceState ?: request()
swipeRefreshLayout.setOnRefreshListener { request() }
} |
コード
SwipeRefreshLayoutとRecycleViewを追加したプルリクエストがあるので参考にしてほしい。
Feature/improve list view by soushin · Pull Request #5 · soushin/sunshine-app · GitHub