RecyclerView 시리즈의 두번째 포스팅입니다.
이번엔 SerachView, Filterable 위젯을 사용해 검색기능을 구현해 보겠습니다.
SerachView는 커스텀하기가 쉽지 않아 EditText로 직접 구현하는 경우도 굉장히 많습니다.
하지만 공식문서를 찾아보시면 생각보다 많은 속성이 있으니, 필요에 따라 쓰시면 되겠습니다.
정리가 잘 된 블로그가 있어서 링크 공유드립니다.
https://landroid.tistory.com/5
SearchView, 속성 사용법
안녕하세요! Landroid입니다~! 보통 안드로이드에서 검색 기능을 구현할 때 많이들 사용하시는데요. 의외로 자료가 많이 없어 구현하는데 애를 먹는 뷰이기도 합니다. 그래서 오늘은 SearchView 사용
landroid.tistory.com

이번 포스팅의 시퀀스 다이어그램을 한번 그려봤습니다..ㅎ
자세한 내용은 이제 서술하도록 하겠습니다!
재료
1. SerachView
2. SerachView의 OnQueryTextListener
3. Adapter에 Filter 구현
ㄱ. filterable 구현 및 원본/검색결과 배열을 구분
ㄴ. Filter를 상속받는 class 구현
재료 구현
1. SerachView

<SearchView
android:id="@+id/search_view_phone_book"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:iconifiedByDefault="false"/>
오른쪽 그림의 빨간 테두리로 표시한것이 SeachView입니다.
리스트가 있는 레이아웃에 추가해 줍니다.
iconifiedByDefault속성을 사용해 밑줄쳐진 검색필드를 검색할때만 활성화 시킬 수 있습니다.
기본값은 true이므로, SeachView를 아이콘화 시키고 싶다면 해당 속성을 따로 입력하지 않아도 됩니다.
2. SerachView의 OnQueryTextListener
var searchViewTextListener: SearchView.OnQueryTextListener =
object : SearchView.OnQueryTextListener {
//검색버튼 입력시 호출, 검색버튼이 없으므로 사용하지 않음
override fun onQueryTextSubmit(s: String): Boolean {
return false
}
//텍스트 입력/수정시에 호출
override fun onQueryTextChange(s: String): Boolean {
phoneBookListAdapter.getFilter().filter(s)
Log.d(TAG, "SearchVies Text is changed : $s")
return false
}
}
SerachView에 문자를 입력하거나, 검색버튼을 눌렀을 때의 이벤트 리스너입니다.
저는 입력할때마다 검색기능을 사용할 것이므로 onQueryTextChange만 사용하겠습니다.
onQueryTextChange에 나오는 getFilter는 아래에 ItemFilter를 구현하며 만들어지는 메소드입니다.
여기까지 해서 SerachView와 OnQueryTextListener의 작성이 끝났으므로,
SerachView에 OnQueryTextListener를 부착시켜 주시면 됩니다.
search_view_phone_book.setOnQueryTextListener(searchViewTextListener)
3. Adapter에 Filter 구현
ㄱ. filterable 구현 및 원본/검색결과 배열을 구분
Adapter를 상속받은 PhoneBookListAdapter에 innerClass로 Filter를 상속받는 ItemFilter를 구현하겠습니다.
주의점은, 원본 배열은 유지해야 하고, 사용자에게 보여줄 배열을 추가로 만들어야 합니다.
그리고 저는 검색기능을 사용하지 않는다면 전체 배열을 보여줄 것이므로,
복제한 배열의 초기값은 원본배열과 동일하게 구현하겠습니다.
class PhoneBookListAdapter(var persons: ArrayList<Person>, var con: Context) :
RecyclerView.Adapter<PhoneBookListAdapter.ViewHolder>(), Filterable {
var TAG = "PhoneBookListAdapter"
var filteredPersons = ArrayList<Person>()
var itemFilter = ItemFilter()
init {
filteredPersons.addAll(persons)
}
//...
}
원본 persons를 복제한 filteredPersons배열과, 곧 구현할 ItemFilter의 인스턴스를 선언해 주었습니다.
filteredPersons배열을 사용자에게 보여줘야 하므로,
이전에 어댑터가 persons를 참조한걸 싹 다 filteredPersons를 참조하도록 수정하셔야 합니다.
Filterable interface의 구현에 필요한 메서드 getFilter도 재정의 해주면 filter를 상속받는 class만 만들어주면 됩니다.
override fun getFilter(): Filter {
return itemFilter
}
ㄴ. Filter를 상속받는 class 구현
Filter를 상속받는 클래스 ItemFilter 클래스를 Inner class로 구현했습니다.
Filter는 performFiltering과 publishResults 두가지 메소드만 구현해주면 됩니다.
이름 그대로, performFiltering는 입력받은 문자열에 대한 처리를,
publishResults는 처리에 대한 결과물을 다루게 됩니다.
performFiltering부터 보겠습니다.
SeachView에서 입력받은 문자열 charSequence에 따른 처리를 하면 되겠습니다.
우리는 검색 결과를 FilterResult()의 value와 count에 담아 반환하면 publishResults가 받게 됩니다.
참고로, FilterResult()는 Fliter의 내부 클래스로, Objcet타입의 value, int타입의 count변수 두가지로만 구현되어 있습니다.
저는 검색어가 없다면 원본 배열을, 2글자 이하라면 이름으로만, 3글자 이상이라면 이름+전화번호로 검색하게 하겠습니다.
//-- filter
inner class ItemFilter : Filter() {
override fun performFiltering(charSequence: CharSequence): FilterResults {
val filterString = charSequence.toString()
val results = FilterResults()
Log.d(TAG, "charSequence : $charSequence")
//검색이 필요없을 경우를 위해 원본 배열을 복제
val filteredList: ArrayList<Person> = ArrayList<Person>()
//공백제외 아무런 값이 없을 경우 -> 원본 배열
if (filterString.trim { it <= ' ' }.isEmpty()) {
results.values = persons
results.count = persons.size
return results
//공백제외 2글자 이하인 경우 -> 이름으로만 검색
} else if (filterString.trim { it <= ' ' }.length <= 2) {
for (person in persons) {
if (person.name.contains(filterString)) {
filteredList.add(person)
}
}
//그 외의 경우(공백제외 2글자 초과) -> 이름/전화번호로 검색
} else {
for (person in persons) {
if (person.name.contains(filterString) || person.phoneNumber.contains(filterString)) {
filteredList.add(person)
}
}
}
results.values = filteredList
results.count = filteredList.size
return results
}
//...
}
마지막으로 publishResults입니다.
performFiltering이 빈환한 FilterResult()클래스를 매개변수로 받아 처리하면 됩니다.
검색의 결과를 보여줘야 하므로,
Adapter 클래스에 선언한 복제 배열을 초기화 한뒤,
FilterResult의 value의 값을 모두 복사하고,
값이 바뀌었음으로 어댑터에 알리면 구현은 끝이 납니다.
//-- filter
inner class ItemFilter : Filter() {
//...
@SuppressLint("NotifyDataSetChanged")
override fun publishResults(charSequence: CharSequence?, filterResults: FilterResults) {
filteredPersons.clear()
filteredPersons.addAll(filterResults.values as ArrayList<Person>)
notifyDataSetChanged()
}
}
결과

RecyclerView 시리즈 다른글 보러가기
2022.01.30 - [IDE & Framework/Android] - [Kotlin] RecyclerView (1) - 기본 예제
[Kotlin] RecyclerView (1) - 기본 예제
새 시리즈 포스팅입니다. 기본 에제 - 좌/우 스와이프로 삭제/통화 걸기 구현 - 검색 구현 - AAC MVVM적용 이렇게 총 네편으로 포스팅 할 예정입니다. RecyclerView란? ViewHolder 패턴을 사용해 아이템
greensky0026.tistory.com
2022.02.08 - [IDE & Framework/Android] - [Kotlin] RecyclerView (3) - ItemTouchHelper로 Swipe event 구현
[Kotlin] RecyclerView (3) - ItemTouchHelper로 Swipe event 구현
RecyclerView 마지막 포스팅입니다. 왼쪽에서 오른쪽으로 스와이프를 하면 통화를, 반대로 오른쪽에서 왼쪽으로 스와이프를 하면 메세지 전송이 되도록 해보겠습니다. 필요한 과정 1. item_phonebook.x
greensky0026.tistory.com
전체 코드
https://github.com/gr2nsky/RecyclerViewPractice/tree/Filterable
GitHub - gr2nsky/RecyclerViewPractice
Contribute to gr2nsky/RecyclerViewPractice development by creating an account on GitHub.
github.com
'IDE & Framework > Android' 카테고리의 다른 글
[Kotlin] PhoneStateListener deprecated, TelephonyCallback로 대체하기 (0) | 2022.02.13 |
---|---|
[Kotlin] RecyclerView (3) - ItemTouchHelper로 Swipe event 구현 (0) | 2022.02.08 |
[Kotlin] ViewPager2 사용하기 (0) | 2022.01.31 |
[Kotlin] RecyclerView (1) - 기본 예제 (0) | 2022.01.30 |
Android Background Service 작업 제한 (1) | 2022.01.22 |