Giới thiệu
Ví dụ minh họa cách thêm chức năng tìm kiếm (search/filter) cho ListView sử dụng ArrayAdapter có sẵn phương thức getFilter() và TextWatcher trên EditText bằng Kotlin.
Các bước thực hiện
Bước 1: Tạo dự án mới
Android Studio > File > New Project.
Bước 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">
<EditText
android:id="@+id/etSearch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Tìm kiếm tại đây..." />
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/etSearch" />
</RelativeLayout>
Bước 3: MainActivity.kt
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.widget.ArrayAdapter
import android.widget.EditText
import android.widget.ListView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private lateinit var listView: ListView
private lateinit var etSearch: EditText
private val months = mutableListOf()
private var arrayAdapter: ArrayAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
title = "KotlinApp"
listView = findViewById(R.id.listView)
etSearch = findViewById(R.id.etSearch)
// Dữ liệu mẫu
months.addAll(listOf(
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
))
arrayAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, android.R.id.text1, months)
listView.adapter = arrayAdapter
// TextWatcher để lọc real-time
etSearch.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
arrayAdapter?.filter?.filter(s) // Lọc adapter
}
override fun afterTextChanged(s: Editable) {}
})
}
}
Bước 4: AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
package="com.example.listviewsearch">
<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>
Chạy ứng dụng
Chạy app, gõ vào ô tìm kiếm (ví dụ: "Jan"), ListView sẽ lọc chỉ còn "January". Xóa text -> hiện lại tất cả.
Mở rộng: Custom Adapter với Filter tùy chỉnh
ArrayAdapter filter mặc định chỉ tìm kiếm theo toString() của object. Với object phức tạp, cần override Filter:
// Model data class Contact(val name: String, val phone: String) // Custom Adapter implements Filterable class ContactAdapter(context: Context, private val originalList: List) : ArrayAdapter (context, 0, originalList), Filterable { private var filteredList = originalList override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { // Inflate layout, bind name, phone... } override fun getFilter(): Filter = object : Filter() { override fun performFiltering(constraint: CharSequence?): FilterResults { val query = constraint?.toString()?.lowercase() ?: "" val results = FilterResults() if (query.isEmpty()) { results.values = originalList results.count = originalList.size } else { val filtered = originalList.filter { it.name.lowercase().contains(query) } results.values = filtered results.count = filtered.size } return results } override fun publishResults(constraint: CharSequence?, results: FilterResults?) { filteredList = (results?.values as? List ) ?: emptyList() notifyDataSetChanged() } } override fun getCount() = filteredList.size override fun getItem(position: Int) = filteredList[position] }
Cách hiện đại: RecyclerView + SearchView (Toolbar)
Thay vì ListView + EditText, dùng SearchView trong Toolbar/ActionBar + RecyclerView:
// Menu: res/menu/search_menu.xml
<menu xmlns:android="..." xmlns:app="...">
<item android:id="@+id/action_search"
android:title="Tìm kiếm"
android:icon="@android:drawable/ic_menu_search"
app:showAsAction="collapseActionView|ifRoom"
app:actionViewClass="androidx.appcompat.widget.SearchView" />
</menu>
// Activity
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.search_menu, menu)
val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.actionView as SearchView
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?) = false
override fun onQueryTextChange(newText: String?): Boolean {
adapter.filter.filter(newText) // Adapter phải implements Filterable
return true
}
})
return true
}
Tóm tắt
- ListView + ArrayAdapter: Đơn giản, filter built-in, phù hợp list nhỏ, dữ liệu đơn giản (String).
- Custom Adapter + Filterable: Cần thiết cho object phức tạp, tùy chỉnh logic tìm kiếm (tìm theo name, phone, email...).
- RecyclerView + SearchView: Chuẩn hiện đại, hiệu năng tốt, UI đẹp, tách biệt logic.