關於這個番外篇呢
其實跟這次鐵人賽的主題關聯性較低
沒興趣的可以先行跳過
寫這篇的原因是快富堅了 只好先塞其他內容墊檔一下
今天要舉的例子是這個
(圖取自google play)
現在很多app的首頁都有類似這種嵌套式UI 你分明就是想拿上班的工作項目來混水摸魚
那這個需求其實可以用內建的RecyclerView來實現
今天會建立新的專案來實做類似的頁面
解決方案放在這
https://github.com/mars1120/NestedRecyclerViewDemo
先設定Dependencies
build.gradle
Dependencies{
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'androidx.cardview:cardview:1.0.0'
}
首先先建立垂直的recyclerview
先從首頁的xml開始
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_base"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</androidx.constraintlayout.widget.ConstraintLayout>
接著建立可重複添加的recyclerview
水平的遷套recyclerview之後也會添加在這xml之內
view_item_parent_recycler.xml
<androidx.cardview.widget.CardView
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="2dp"
card_view:cardBackgroundColor="#fff"
card_view:cardCornerRadius="5dp"
card_view:cardElevation="4dp"
card_view:cardUseCompatPadding="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_title"
style="@style/Base.TextAppearance.AppCompat.Subhead"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignStart="@+id/rv_child"
android:layout_alignParentTop="true"
android:padding="20dp"
android:text="I'm title"
android:textColor="@color/colorAccent"
android:textStyle="bold" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_child"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="30dp"
android:orientation="horizontal"
android:padding="20dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:layout_editor_absoluteX="74dp" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
另一個類型的item
view_banner.xml
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="2dp"
card_view:cardBackgroundColor="#fff"
card_view:cardCornerRadius="5dp"
card_view:cardElevation="4dp"
card_view:cardUseCompatPadding="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:background="@color/cardview_dark_background"
android:id="@+id/tv_banner"
style="@style/Base.TextAppearance.AppCompat.Subhead"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignStart="@+id/rv_child"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:gravity="center"
android:padding="40dp"
android:text="I'm Banner"
android:textColor="@color/colorPrimary"
android:textStyle="bold" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
ItemParentModel
用來存資料的數據類
ItemParentModel.kt
data class ItemParentModel (
val title : String = ""
)
ItemParentAdapter
用來綁定與設定recyclerView用的
ItemParentAdapter.kt
class ItemParentAdapter(private val parents: List<ItemParentModel>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup,
viewType: Int): RecyclerView.ViewHolder {
when (viewType) {
0 -> return BannerViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.view_banner, parent, false))
else -> return ItemViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.view_item_parent_recycler, parent, false))
}
}
override fun getItemCount(): Int {
return parents.size+1
}
override fun getItemViewType(position: Int): Int {
return if (0 == position) {
TYPE_BANNER
} else {
TYPE_ITEM
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder,
position: Int) {
when(holder)
{
is BannerViewHolder -> {
holder.textView.text = "hello"
}
is ItemViewHolder -> {
holder.textView.text = parents[position-1].title
}
}
}
private inner class BannerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textView: TextView = itemView.tv_banner
}
private inner class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textView: TextView = itemView.tv_title
}
companion object {
private val TYPE_BANNER = 0
private val TYPE_ITEM = 1
}
}
最後是首頁
Mainactivity.kt
class MainActivity : AppCompatActivity() {
lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerView = rv_base
recyclerView.apply {
adapter = ItemParentAdapter(getParents())
}
}
private fun getParents(): List<ItemParentModel> {
val listOfFacility = mutableListOf<ItemParentModel>()
var facilityModel = ItemParentModel("Sample 0")
listOfFacility.add(facilityModel)
facilityModel = ItemParentModel("Sample 1")
listOfFacility.add(facilityModel)
facilityModel = ItemParentModel("Sample 2")
listOfFacility.add(facilityModel)
facilityModel = ItemParentModel("Sample 3")
listOfFacility.add(facilityModel)
facilityModel = ItemParentModel("Sample 4")
listOfFacility.add(facilityModel)
facilityModel = ItemParentModel("Sample 5")
listOfFacility.add(facilityModel)
facilityModel = ItemParentModel("Sample 6")
listOfFacility.add(facilityModel)
return listOfFacility
}
}
目前階段的畫面
接下來會調整ItemParentAdapter讓他再嵌入一個recyclerview
首先先來新增child xml
view_item_child_recycler.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/child_textView"
android:layout_width="128dp"
android:layout_height="37dp"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="1dp"
android:background="@android:color/darker_gray"
android:padding="10dp"
android:text="TextView"
android:textColor="@android:color/white"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@id/child_imageView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
<ImageView
android:id="@+id/child_imageView"
android:layout_width="126dp"
android:layout_height="189dp"
android:layout_marginBottom="38dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="42dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0"
app:srcCompat="@drawable/ic_attach_money_128dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
其中@drawable/ic_attach_money_128dp可新增矢量圖或用其他圖源代替
新增child資料源
ChildModel.kt
data class ChildModel(
val image: Int = -1,
val title: String = ""
)
ChildAdapter.kt
class ChildAdapter(private val children: List<ChildModel>) :
RecyclerView.Adapter<ChildAdapter.ViewHolder>() {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): ViewHolder {
val v = LayoutInflater.from(parent.context)
.inflate(R.layout.view_item_child_recycler, parent, false)
return ViewHolder(v)
}
override fun getItemCount(): Int {
return children.size
}
override fun onBindViewHolder(
holder: ViewHolder,
position: Int
) {
val child = children[position]
holder.imageView.setImageResource(child.image)
holder.textView.text = child.title
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textView: TextView = itemView.child_textView
val imageView: ImageView = itemView.child_imageView
}
}
接著要回頭修改ItemParentAdapter 串接child RecyclerView
ItemParentAdapter.kt
class ItemParentAdapter( ...
...
private val viewPool = RecyclerView.RecycledViewPool()
override fun onBindViewHolder() {
when(holder)
{
is ItemViewHolder -> {
holder.textView.text = parents[position-1].title
holder.recyclerView.apply {
adapter = ChildAdapter(parents[position-1].children)
setRecycledViewPool(viewPool)
}
}
}
}
private inner class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textView: TextView = itemView.tv_title
val recyclerView : RecyclerView = itemView.rv_child
}
父物件的資料源也要修改
data class ItemParentModel (
val title : String = "",
val children : List
)
最後是調整後的首頁
MainActivity.kt
class MainActivity : AppCompatActivity() {
lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerView = rv_base
recyclerView.apply {
adapter = HomeItemParentAdapter(getParents())
}
}
private fun getParents(): List<ItemParentModel> {
val listOfFacility = mutableListOf<ItemParentModel>()
var facilityModel = ItemParentModel("Sample 0", ChildDataFactory.getChildren(5))
listOfFacility.add(facilityModel)
facilityModel = ItemParentModel("Sample 1", ChildDataFactory.getChildren(5))
listOfFacility.add(facilityModel)
facilityModel = ItemParentModel("Sample 2", ChildDataFactory.getChildren(5))
listOfFacility.add(facilityModel)
facilityModel = ItemParentModel("Sample 3", ChildDataFactory.getChildren(5))
listOfFacility.add(facilityModel)
facilityModel = ItemParentModel("Sample 4", ChildDataFactory.getChildren(5))
listOfFacility.add(facilityModel)
facilityModel = ItemParentModel("Sample 5", ChildDataFactory.getChildren(5))
listOfFacility.add(facilityModel)
facilityModel = ItemParentModel("Sample 6", ChildDataFactory.getChildren(5))
listOfFacility.add(facilityModel)
return listOfFacility
}
object ChildDataFactory {
private val random = Random()
private val titles = arrayListOf("title0", "title1", "title2", "title3", "title4")
private fun randomTitle(): String {
val index = random.nextInt(titles.size)
return titles[index]
}
private fun randomImage(): Int {
return R.drawable.ic_attach_money_128dp
}
fun getChildren(count: Int): List<ChildModel> {
val children = mutableListOf<ChildModel>()
repeat(count) {
val child = ChildModel(randomImage(), randomTitle())
children.add(child)
}
return children
}
}
}
搞定
成品畫面
最後再貼一次solution連結
https://github.com/mars1120/NestedRecyclerViewDemo
之後可能會拿本專案當基底做一些其他demo