ตัวอย่างนี้สาธิตวิธี Highlight (เน้นสี) รายการที่เลือกใน RecyclerView อย่างถูกต้องด้วย Kotlin โดยใช้ SparseBooleanArray เก็บสถานะการเลือก และ StateListDrawable (Selector) สำหรับ Background
ขั้นตอนที่ 1: สร้างโปรเจกต์ใหม่ (Kotlin)
เปิด Android Studio → File → New Project เลือก Kotlin
ขั้นตอนที่ 2: Layout หลัก (activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="4dp"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="8dp" />
</RelativeLayout>
ขั้นตอนที่ 3: MainActivity.kt
package com.example.q11
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
title = "KotlinApp"
val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
recyclerView.addItemDecoration(SimpleItemDecoration(this))
val layoutManager = LinearLayoutManager(this)
recyclerView.layoutManager = layoutManager
val posts = returnListItems()
val adapter = RecyclerViewAdapter(this, posts)
recyclerView.adapter = adapter
}
private fun returnListItems(): List<ItemObject> {
return listOf(
ItemObject("Ballon d'or", "2007", "Ricardo Kaká"),
ItemObject("Ballon d'or", "2008", "Cristiano Ronaldo"),
ItemObject("Ballon d'or", "2009-2012, 2015", "Lionel Messi"),
ItemObject("Ballon d'or", "2013, 2014, 2016, 2017", "Cristiano Ronaldo"),
ItemObject("Ballon d'or", "2018", "Luka Modrić"),
ItemObject("Ballon d'or", "2019", "Lionel Messi")
)
}
}
ขั้นตอนที่ 4: สร้าง Class ต่างๆ
ItemObject.kt (Data Class)
data class ItemObject(
val awardTitle: String,
val awardYear: String,
val player: String
)
RecyclerViewAdapter.kt
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.NonNull
import androidx.recyclerview.widget.RecyclerView
class RecyclerViewAdapter(
private val context: MainActivity,
private val itemList: List<ItemObject>
) : RecyclerView.Adapter<RecyclerViewHolders>() {
@NonNull
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerViewHolders {
val layoutView = LayoutInflater.from(parent.context).inflate(R.layout.list_layout, parent, false)
return RecyclerViewHolders(layoutView)
}
override fun onBindViewHolder(holder: RecyclerViewHolders, position: Int) {
val item = itemList[position]
holder.awardTitle.text = "Award Title: ${item.awardTitle}"
holder.awardYear.text = "Award Year: ${item.awardYear}"
holder.player.text = "Player Name: ${item.player}"
}
override fun getItemCount() = itemList.size
}
RecyclerViewHolders.kt - จัดการสถานะเลือก
import android.util.SparseBooleanArray
import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
class RecyclerViewHolders(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
var awardTitle: TextView = itemView.findViewById(R.id.awardTitle)
var awardYear: TextView = itemView.findViewById(R.id.awardYear)
var player: TextView = itemView.findViewById(R.id.playerName)
private val selectedItems = SparseBooleanArray()
init {
itemView.setOnClickListener(this)
}
override fun onClick(view: View) {
val position = adapterPosition
if (position == RecyclerView.NO_POSITION) return
if (selectedItems.get(position, false)) {
selectedItems.delete(position)
view.isSelected = false
} else {
selectedItems.put(position, true)
view.isSelected = true
}
}
}
SimpleItemDecoration.kt - เส้นคั่นระหว่างรายการ
import android.content.Context
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import androidx.annotation.NonNull
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
internal class SimpleItemDecoration(context: Context) : RecyclerView.ItemDecoration() {
private val drawable: Drawable = ContextCompat.getDrawable(context, R.drawable.line_divider)!!
override fun onDrawOver(
@NonNull canvas: Canvas,
parent: RecyclerView,
@NonNull state: RecyclerView.State
) {
val left = parent.paddingLeft
val right = parent.width - parent.paddingRight
val childCount = parent.childCount
for (i in 0 until childCount) {
val child = parent.getChildAt(i)
val params = child.layoutParams as RecyclerView.LayoutParams
val top = child.bottom + params.bottomMargin
val bottom = top + drawable.intrinsicHeight
drawable.setBounds(left, top, right, bottom)
drawable.draw(canvas)
}
}
}
ขั้นตอนที่ 5: Drawable Resources
background_selector.xml (ใน res/drawable/) - สำคัญที่สุดสำหรับ Highlight
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="https://schemas.android.com/apk/res/android">
<!-- สถานะ Selected: สีเขียว -->
<item android:drawable="@android:color/holo_green_light"
android:state_selected="true"
android:state_pressed="false" />
<!-- สถานะ Pressed: สีม่วง -->
<item android:drawable="@android:color/holo_purple"
android:state_pressed="true" />
<!-- สถานะปกติ: ใส (transparent) -->
<item android:drawable="@android:color/transparent" />
</selector>
line_divider.xml (ใน res/drawable/)
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="https://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size
android:width="2dp"
android:height="2dp" />
<solid android:color="@color/colorPrimaryDark" />
</shape>
ขั้นตอนที่ 6: Layout รายการ (list_layout.xml)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/background_selector"
android:padding="16dp">
<TextView
android:id="@+id/awardTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="New Text"
android:textColor="@android:color/black"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/awardYear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/awardTitle"
android:layout_alignStart="@id/awardTitle"
android:layout_marginTop="20dp"
android:text="New Text"
android:textColor="@android:color/black" />
<TextView
android:id="@+id/playerName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_marginBottom="10dp"
android:text="New Text"
android:textColor="@android:color/black" />
</RelativeLayout>
ขั้นตอนที่ 7: AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
package="com.example.q11">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
หลักการทำงานของ Selector (StateListDrawable):
- Android ตรวจสอบ state ของ View (selected, pressed, focused, enabled)
- Match กับ
<item>ใน Selector ตามลำดับ (บนลงล่าง) - ใช้ drawable แรกที่ match
android:state_selected="true"ถูกตั้งค่าโดยview.isSelected = true
⚠️ ข้อผิดพลาดทั่วไปที่ต้องหลีกเลี่ยง:
- ❌ ไม่ใช้ Selector - เปลี่ยนสีพื้นหลังใน onBindViewHolder (ทำให้ recycled view ผิด)
- ❌ ไม่ reset สถานะใน onBindViewHolder - ต้องทำ
holder.itemView.isSelected = selectedItems.get(position) - ❌ ใช้ position แทน adapterPosition ใน ViewHolder
- ✅ ใช้
adapterPositionใน ViewHolder - ✅ ใช้ Selector XML สำหรับ background
- ✅ จัดการ state ใน ViewHolder เอง
การปรับปรุง: Sync State ใน onBindViewHolder (จำเป็น)
// ใน RecyclerViewAdapter.kt
override fun onBindViewHolder(holder: RecyclerViewHolders, position: Int) {
// ... set text ...
// ซิงค์สถานะ selected กับ ViewHolder
holder.itemView.isSelected = holder.selectedItems.get(position, false)
}
หมายเหตุ: ต้องเปลี่ยน selectedItems ใน ViewHolder ให้เป็น companion object หรือส่งผ่าน Adapter
การทดสอบ
รันแอปพลิเคชัน กดที่รายการ จะเปลี่ยนพื้นหลังเป็นสีเขียว กดอีกครั้งจะคืนค่าเดิม
