[Android / Kotlin] SearchView와 Filter로 RecyclerView 검색하기

Subeen·2024년 1월 16일
1

Android

목록 보기
43/73

결과 화면

SearchView

iconifiedByDefault 속성을 사용해 밑줄쳐진 검색필드를 검색할 때만 활성화 시킬 수 있다. 기본값은 true이며, SeachView를 아이콘화 시키고 싶다면 해당 속성을 따로 입력하지 않아도 된다.

<?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.appcompat.widget.Toolbar
        android:id="@+id/toolBar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        app:contentInsetStart="0dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.appcompat.widget.SearchView
            android:id="@+id/searchView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:background="@drawable/background_round_radius"
            app:iconifiedByDefault="false"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:queryHint="@string/main_search_hint" />

    </androidx.appcompat.widget.Toolbar>

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@id/tabLayout"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/toolBar" />

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tabLayout"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="@color/widget"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:tabIconTint="@color/selector_tab_icon_color"
        app:tabIndicatorColor="#00ffffff" />

</androidx.constraintlayout.widget.ConstraintLayout>

SearchView의 OnQueryTextListener

OnQueryTextListenerSearchView에 문자를 입력하거나 검색 버튼을 눌렀을 때의 리스너다. 텍스트를 입력할 때마다 검색 기능을 사용할 목적이므로 onQueryTextChange만 사용한다.

    private fun setOnQueryTextListener() {
        binding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener,
            androidx.appcompat.widget.SearchView.OnQueryTextListener {
            // 검색 버튼 입력시 호출, 검색 버튼이 없으므로 사용하지 않는다. 
            override fun onQueryTextSubmit(query: String?): Boolean {
                return false
            }
			// 텍스트 입력, 수정시 호출 
            override fun onQueryTextChange(newText: String?): Boolean {
            	/*
                 * SearchView는 Activity 내에 구현했으며
                 * 입력 된 텍스트를 Fragment로 전달 하기 위한 함수 
                 */
                updateItemCurrentFragment(newText)
                return false
            }
        })
    }

RecyclerView Adapter

원본 리스트를 유지하고 필터링한 결과 리스트를 추가로 만들었다. 공백 또는 검색 기능을 사용하지 않을 때는 전체 리스트를 보여주기에 복제한 리스트의 초기값을 원본 리스트와 동일하게 선언한다.

class ContactAdapter(private var mItem: List<ContactViewType>) :
    RecyclerView.Adapter<RecyclerView.ViewHolder>(), Filterable {

    private var filteredList: List<ContactViewType> = mItem

	...
    
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
    	...
    
    }
    
	override fun getItemCount(): Int {
        return filteredList.size
    }
    
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (val item = filteredList[position]) {
        ...
    }

	override fun getFilter(): Filter {
        return object : Filter() {
            // 입력 받은 문자열에 대한 처리
            override fun performFiltering(constraint: CharSequence?): FilterResults {
                val charString = constraint.toString().trim()
                // 공백, 아무런 값이 입력 되지 않았을 때는 원본 리스트
                filteredList = if (charString.isBlank()) {
                    mItem
                } else {
                    val filterCondition: (ContactViewType) -> Boolean = { it ->
                        when (it) {
                        	// 뷰 타입이 리스트일 때 
                            is ContactViewType.ContactUser -> { // 이름, 전화 번호, 이메일 검색
                                it.user.name.contains(charString, true) ||
                                        it.user.phone.contains(charString, true) ||
                                        it.user.email.contains(charString, true)
                            }

							// 뷰 타입이 그리드일 때 
                            is GridUser -> { // 이름, 전화 번호, 이메일 검색
                                it.user.name.contains(charString, true) ||
                                        it.user.phone.contains(charString, true) ||
                                        it.user.email.contains(charString, true)
                            }
                        }
                    }

                    mItem.filter { filterCondition(it) }
                }

                // 검색 된 값으로 필터링 된 리스트
                val filterResults = FilterResults().apply {
                    values = filteredList
                }
                return filterResults
            }
            
            // 처리에 대한 결과물
            override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
                // FilterResults 값을 복사하고 값이 변경 되었으므로 adapter에 알린다.
                filteredList = results?.values as List<ContactViewType>
                notifyDataSetChanged()
            }
        }
    }

    // 검색 텍스트가 변경 되면 Fragment에서 호출 된다.
    fun performSearch(query: String) {
        filter.filter(query)
    }
   
profile
개발 공부 기록 🌱

0개의 댓글