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

SearchView ใน Android: การค้นหาข้อมูลใน ListView


SearchView คืออะไร?

SearchView เป็น Widget สำหรับรับคำค้นหาจากผู้ใช้ มักวางที่ Toolbar/ActionBar หรือใน Layout โดยตรง รองรับการกรองข้อมูลแบบ Real-time (Filter) และการส่งคำค้นหา (Submit)

โครงสร้างพื้นฐาน

  • SearchView - UI รับคำค้นหา
  • ListView/RecyclerView - แสดงรายการข้อมูล
  • Adapter + Filter - จัดการข้อมูลและกรอง

ตัวอย่างครบถ้วน: SearchView กับ ListView

1. Layout (activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.appcompat.widget.SearchView
        android:id="@+id/searchView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:queryHint="ค้นหาภาษาโปรแกรม..."
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:iconifiedByDefault="false" />

    <ListView
        android:id="@+id/listView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintTop_toBottomOf="@id/searchView"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

2. MainActivity.java

import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.SearchView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    private ListView listView;
    private SearchView searchView;
    private ArrayAdapter adapter;
    private List originalData = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        listView = findViewById(R.id.listView);
        searchView = findViewById(R.id.searchView);

        // ข้อมูลตัวอย่าง
        originalData.add("Java");
        originalData.add("Kotlin");
        originalData.add("Android");
        originalData.add("Swift");
        originalData.add("Dart");
        originalData.add("JavaScript");
        originalData.add("TypeScript");
        originalData.add("Python");
        originalData.add("C++");
        originalData.add("C#");
        originalData.add("Go");
        originalData.add("Rust");

        // Adapter ใช้ simple_list_item_1 (built-in layout)
        adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new ArrayList<>(originalData));
        listView.setAdapter(adapter);

        // ตั้งค่า SearchView
        setupSearchView();
        
        // คลิกที่รายการ
        listView.setOnItemClickListener((parent, view, position, id) -> {
            String item = adapter.getItem(position);
            Toast.makeText(this, "เลือก: " + item, Toast.LENGTH_SHORT).show();
        });
    }

    private void setupSearchView() {
        searchView.setIconifiedByDefault(false); // ขยายค้นหาเต็ม
        searchView.setQueryHint("พิมพ์คำค้นหา...");
        searchView.clearFocus(); // ไม่ให้ Focus อัตโนมัติ

        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                // เมื่อกด Enter/ค้นหา
                searchView.clearFocus();
                return true; // consume event
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                // กรองข้อมูล Real-time
                filterList(newText);
                return true;
            }
        });
    }

    private void filterList(String query) {
        List filtered = new ArrayList<>();
        if (query.isEmpty()) {
            filtered.addAll(originalData);
        } else {
            String lowerQuery = query.toLowerCase();
            for (String item : originalData) {
                if (item.toLowerCase().contains(lowerQuery)) {
                    filtered.add(item);
                }
            }
        }
        adapter.clear();
        adapter.addAll(filtered);
        adapter.notifyDataSetChanged();
    }
}

SearchView ใน Toolbar/ActionBar (แนะนำ)

วาง SearchView ใน Menu เพื่อให้อยู่ใน ActionBar

menu/search_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/action_search"
        android:title="ค้นหา"
        android:icon="@android:drawable/ic_menu_search"
        app:showAsAction="always|collapseActionView"
        app:actionViewClass="androidx.appcompat.widget.SearchView" />

</menu>

ใน Activity

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.search_menu, menu);
    
    MenuItem searchItem = menu.findItem(R.id.action_search);
    SearchView searchView = (SearchView) searchItem.getActionView();
    
    // ตั้งค่า listener เหมือนด้านบน
    searchView.setOnQueryTextListener(...);
    
    return true;
}

RecyclerView + Filterable (แนะนำสำหรับ Production)

ListView ล้าสมัยแล้ว ควรใช้ RecyclerView พร้อม implements Filterable ใน Adapter

public class MyAdapter extends RecyclerView.Adapter implements Filterable {
    private List originalList;
    private List filteredList;
    private Filter filter;

    // ... constructor, onCreateViewHolder, onBindViewHolder

    @Override
    public Filter getFilter() {
        if (filter == null) {
            filter = new Filter() {
                @Override
                protected FilterResults performFiltering(CharSequence constraint) {
                    List result = new ArrayList<>();
                    if (constraint == null || constraint.length() == 0) {
                        result.addAll(originalList);
                    } else {
                        String query = constraint.toString().toLowerCase();
                        for (String item : originalList) {
                            if (item.toLowerCase().contains(query)) result.add(item);
                        }
                    }
                    FilterResults results = new FilterResults();
                    results.values = result;
                    results.count = result.size();
                    return results;
                }

                @Override
                protected void publishResults(CharSequence constraint, FilterResults results) {
                    filteredList = (List) results.values;
                    notifyDataSetChanged();
                }
            };
        }
        return filter;
    }
}

เคล็ดลับและ Best Practices

  • Debounce: ใช้ RxJava/Kotlin Flow `debounce(300ms)` เพื่อลดการกรองซ้ำ
  • Large Dataset: ใช้ `Filterable` ใน Background Thread (performFiltering รันบน Worker Thread)
  • Clear Focus: เรียก `searchView.clearFocus()` หลังค้นหาเพื่อซ่อนคีย์บอร์ด
  • Voice Search: เพิ่ม `android:voiceSearchMode` ใน XML เพื่อรองรับค้นหาด้วยเสียง
  • Recent Queries: ใช้ `setOnSuggestionListener` และ `SearchRecentSuggestionsProvider`