Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Android

วิธี Highlight รายการที่เลือกใน RecyclerView ด้วย Kotlin อย่างถูกต้อง

ตัวอย่างนี้สาธิตวิธี 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):
  1. Android ตรวจสอบ state ของ View (selected, pressed, focused, enabled)
  2. Match กับ <item> ใน Selector ตามลำดับ (บนลงล่าง)
  3. ใช้ drawable แรกที่ match
  4. 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

การทดสอบ

รันแอปพลิเคชัน กดที่รายการ จะเปลี่ยนพื้นหลังเป็นสีเขียว กดอีกครั้งจะคืนค่าเดิม

วิธี Highlight รายการที่เลือกใน RecyclerView ด้วย Kotlin อย่างถูกต้อง วิธี Highlight รายการที่เลือกใน RecyclerView ด้วย Kotlin อย่างถูกต้อง วิธี Highlight รายการที่เลือกใน RecyclerView ด้วย Kotlin อย่างถูกต้อง