개발 공부/Android / / 2022. 8. 19. 18:52

[Android Kotlin] Toolbar SearchView 구현하기(검색 기능 만들기)

RecyclerView내에서 빠르게 검색하는 기능을 만들고 싶어서 시작. 

 

1. menu 만들기

안드로이드 공식 사이트에서의 코드를 참고 

<?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:id="@+id/search"
              android:title="@string/search_title"
              android:icon="@drawable/ic_search"
              android:showAsAction="collapseActionView|ifRoom"
              android:actionViewClass="android.widget.SearchView" />
    </menu>

Toolbar에 들어가는 게 SearchView 하나면 상관없는데, 여러 개를 사용할 경우는 위처럼 했을 때 다른 메뉴에 같이 나오게 됨. 내 경우는 옵션 메뉴가 하나 더 있었는데 옵션 메뉴 리스트에 SearchView도 같이 잡힘. 그래서 검색해서 알아본 결과 ifRoom말고 always를 적용

 

실제 적용 코드 

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

    <item
        android:id="@+id/menu_search"
        android:icon="@drawable/ic_search"
        android:title="Search"
        android:enabled="true"
        android:visible="true"
        app:showAsAction="always|collapseActionView"
        app:actionViewClass="androidx.appcompat.widget.SearchView"/>

    <group
        android:id="@+id/menu_group">
        <item
            android:id="@+id/menu_account"
            android:title="account"
            />
        <item
            android:id="@+id/menu_logout"
            android:title="logout"
            />
    </group>
</menu>

2. onCreateOptionMenu 구현

override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.options_menu, menu)

        return true
    }

3. 검색 가능한 구성 만들기 

res>xml(폴더 생성)>searchable.xml 생성

<?xml version="1.0" encoding="utf-8"?>

    <searchable xmlns:android="http://schemas.android.com/apk/res/android"
            android:label="@string/app_name"
            android:hint="@string/search_hint" />

4. 위에 만든 xml을 Manifest에 meta-data로 추가 

<activity ... >
        ...
        <meta-data android:name="android.app.searchable"
                android:resource="@xml/searchable" />

    </activity>

5. 앞에어 만든 onCreateOptionsMenu() 메서드에서 setSearchableInfo(SearchableInfo) 를 호출하여 검색 가능한 구성을 SearchView와 연결

override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.options_menu, menu)

        // Associate searchable configuration with the SearchView
        val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
        (menu.findItem(R.id.search).actionView as SearchView).apply {
            setSearchableInfo(searchManager.getSearchableInfo(componentName))
        }

        return true
    }

6. RecyclerView를 구현할 때 만든 Adapter로 가서 Filterable 상속 및 메소드 구현

class AllAccountListAdapter(
    val context: Context, accounts: List<AccountData>,
    onClick: OnClickInterface,
    onLongClick: OnLongClickInterface,
    val viewModel: PageViewModel
) :
    RecyclerView.Adapter<AllAccountListAdapter.ItemViewHolder>(), Filterable {

//검색
    val mDataListAll = ArrayList<AccountData>(accounts)
    var mAccounts:MutableList<AccountData> = accounts as MutableList<AccountData>

...

mDataListAll 은 검색용도로 만든 모든 데이터를 가진 ArrayList

mAccounts는 검색된 결과를 담는 List 

override fun getFilter(): Filter {
        return exampleFilter
    }

    private val exampleFilter: Filter = object : Filter() {
        //Automatic on background thread
        override fun performFiltering(constraint: CharSequence): FilterResults {
            val filteredList: MutableList<AccountData> = java.util.ArrayList<AccountData>()
            if (constraint.isEmpty()) {
                filteredList.addAll(mDataListAll)
            } else {
                val filterPattern = constraint.toString().lowercase(Locale.getDefault()).trim { it <= ' ' }
                for (item in mDataListAll) {
                    //filter 대상 setting
                    if (item.name.lowercase(Locale.getDefault()).contains(filterPattern)) {
                        filteredList.add(item)
                    }
                }
            }
            val results = FilterResults()
            results.values = filteredList
            return results
        }

        //Automatic on UI thread
        @SuppressLint("NotifyDataSetChanged")
        override fun publishResults(constraint: CharSequence, results: FilterResults) {
            mAccounts.clear()
            mAccounts.addAll(results.values as Collection<AccountData>)
            notifyDataSetChanged()
        }
    }

 

 

7. onCreateOptionMenu메소드로 다시 가서 필터링이 될 수 있도록 setOnQueryTextListener 추가 

나는 Activity에 RecyclerView가 있는 게 아니라 Activity안에 Tablayout을 구현한 상황.

검색이 Activity에서 일어나야하는데 데이터를 보여주는 RecyclerView는 각 Fragment안에 있는 상태라 어떻게 할까 고민하다가 Activity쪽에 FrameLayout을 추가해서 그 안에 RecyclerView를 구현.

검색창에 값이 입력 될 때 마다 FrameLayout을 GONE, VISIBLE처리로 검색 결과 화면을 따로 만들었다. 

val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
        (menu?.findItem(R.id.menu_search)?.actionView as SearchView).apply {
            setSearchableInfo(searchManager.getSearchableInfo(componentName))

            this.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
				//검색(찾기) 버튼을 눌렀을 때 실행
                override fun onQueryTextSubmit(query: String?): Boolean {
                    return false
                }
				//검색창에 값이 입력될 때 마다 실행
                override fun onQueryTextChange(newText: String?): Boolean {
                    if (newText != null) {
                        if (newText.isNotEmpty()) {
                            binding.flMainSearchResult.visibility = View.VISIBLE
                            searchAdapter.filter.filter(newText)
                        } else {
                            binding.flMainSearchResult.visibility = View.GONE
                        }
                    }
                    return false
                }
            })
        }

[이슈]

공식 사이트에서 제시한 예제로 했을 경우 옵션 메뉴에 SearchView 타이틀인 search가 뜨고 검색 중에 옵션 메뉴를 누르면 검색 아이콘이 사라지는 에러 발생. 해결책으로 위에서 말한 것 처럼 always로 변경, 그리고 아이콘이 사라지는 부분은 setOnActionExpandListener를 추가 invalidateOptionMenu()를 넣어서 해결하였다. 

 

최종 onCreateOptionsMenu()

override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.home_menu, menu) // main_menu 메뉴를 toolbar 메뉴 버튼으로 설정

        //검색을 누른 후 옵션 아이콘을 누른 후 검색이 끝나면 검색 아이콘이 사라져서 넣음
        menu?.findItem(R.id.menu_search)
            ?.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
                override fun onMenuItemActionExpand(p0: MenuItem?): Boolean {
                   return true
                }

                override fun onMenuItemActionCollapse(p0: MenuItem?): Boolean {
                    invalidateOptionsMenu()
                    return true
                }
            })

        // Associate searchable configuration with the SearchView
        val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
        (menu?.findItem(R.id.menu_search)?.actionView as SearchView).apply {
            setSearchableInfo(searchManager.getSearchableInfo(componentName))

            this.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
                override fun onQueryTextSubmit(query: String?): Boolean {
                    return false
                }

                override fun onQueryTextChange(newText: String?): Boolean {
                    if (newText != null) {
                        if (newText.isNotEmpty()) {
                            binding.flMainSearchResult.visibility = View.VISIBLE
                            searchAdapter.filter.filter(newText)
                        } else {
                            binding.flMainSearchResult.visibility = View.GONE
                        }
                    }
                    return false
                }
            })
        }



        return true
    }

 

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유