(Kotlin) SearchView를 이용한 RecyclerView filter 사용하기

이번 포스팅에서는 SearchView를 이용해서 RecyclerView의 데이터들을 검색하는 기능을 추가할 계획입니다.

우선 결과물부터 보겠습니다. 결과물이 굉장히 만족스럽네요.

 

 

이전 포스팅까지 만든 코드에서는 filter를 사용하려고 하니 굉장히 복잡하네요...

또다시 코드를 전면 수정했습니다. ㅠㅠ

 

코드 중간중간에 설명이 다 있습니다. 이대로 복! 붙! 하시면 됩니다.

 

1. Menu폴더 만들어주기

툴바의 검색 아이콘을 눌러 검색할 수 있도록 menu 폴더를 만들어 주겠습니다.  

res 우클릭 → New 클릭 → Android Resource Directory를 눌러줍니다.

New Resource Directory 창이 뜨면,

Resource type을 menu로 바꿔주고 OK를 눌러주세요.

2. menu (menu_search.xml)

menu폴더 안에 menu_search.xml파일을 하나 만들어줍니다.

<?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">

    <!-- hide share button by default -->
    <item
        android:id="@+id/menu_action_search"
        android:icon="@drawable/ic_search"
        android:title="Search"
        app:showAsAction="ifRoom|collapseActionView"
        app:actionViewClass="android.support.v7.widget.SearchView"/>
</menu>

ic_search는 돋보기 아이콘인데, material icons 홈페이지에서 다운로드하여서 drawable 폴더에 넣어주세요.

링크는 아래 참고하세요.

https://material.io/resources/icons/?style=baseline

 

Resources

Build beautiful, usable products faster. Material Design is an adaptable system—backed by open-source code—that helps teams build high quality digital experiences.

material.io

3. Kotlin (SearchAdapter.kt)

class SearchAdapter(private val context: Context, private val excelList: MutableList<SearchData>, private val listener: ItemClickListener) : RecyclerView.Adapter<SearchAdapter.SearchViewHolder>(), Filterable{

    //검색기능을 위해서 별도의 List를 하나더 만들어주겠습니다.
    private var excelSearchList: List<SearchData>? = null

    //이너 클래스
    inner class SearchViewHolder(view: View) : RecyclerView.ViewHolder(view){
        //TextView 선언
        val info: TextView
        val quiz: TextView

        init{
            info = view.findViewById(R.id.search_info)
            quiz = view.findViewById(R.id.search_quiz)

            //온클릭리스너는 여기에!
            view.setOnClickListener{
                //여기서 !!의 의미는 nullable이면 오류가 발생하게 해줍니다. (?는 null)
                listener.onItemClicked(excelSearchList!![adapterPosition])
            }
        }
    }
    //초기화 구문 init
    init {
        this.excelSearchList = excelList
    }

    //뷰홀더 생성
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchViewHolder {
        //리스트 아이템 인플레이터
        val view = LayoutInflater.from(context).inflate(R.layout.search_list_item, parent, false)
        return SearchViewHolder(view)
    }

    //온바인드뷰홀더 생성
    @SuppressLint("SetTextI18n")
    override fun onBindViewHolder(holder: SearchViewHolder, position: Int){
        val excel = excelSearchList!![position]
        //TextView에 excel 데이터 입력
        holder.info.text = "#"+excel.fullname + "  #"+excel.cate1+"("+excel.cate2+")" + "  #"+excel.source
        holder.quiz.text = excel.quiz.replace(".(?!$)".toRegex(), "$0\u200b")
    }

    //item 사이즈
    override fun getItemCount(): Int = excelSearchList!!.size

    //필터를 위한 코드
    override fun getFilter(): Filter {
        return object : Filter() {
            override fun performFiltering(charSequence: CharSequence): FilterResults {
                val charString = charSequence.toString()
                if (charString.isEmpty()) {
                    excelSearchList = excelList
                } else {
                    val filteredList = ArrayList<SearchData>()
                    //이부분에서 원하는 데이터를 검색할 수 있음
                    for (row in excelList) {
                        if (row.fullname.toLowerCase().contains(charString.toLowerCase()) || row.quiz.toLowerCase().contains(charString.toLowerCase())
                            || row.cate2.toLowerCase().contains(charString.toLowerCase()) || row.source.toLowerCase().contains(charString.toLowerCase())) {
                            filteredList.add(row)
                        }
                    }
                    excelSearchList = filteredList
                }
                val filterResults = FilterResults()
                filterResults.values = excelSearchList
                return filterResults
            }
            override fun publishResults(charSequence: CharSequence, filterResults: FilterResults) {
                excelSearchList = filterResults.values as ArrayList<SearchData>
                notifyDataSetChanged()
            }
        }
    }

    interface ItemClickListener {
        fun onItemClicked(item : SearchData)
    }
}

자세히 확인해야 할 코드들..

 - Filterable

 - excelSearchList (이전에 excelList 부분을 바꿔줘야 합니다.)

 - private var excelsearchList: List? = null

 - override fun getFilter(): Filter {...}

'필터를 위한 코드'에서 빨간 줄 그이는 부분은 List의 이름이 다르거나 하는 경우일 거예요.

본인의 코드에 맞게 바꿔주세요.

2. Kotlin(SearchActivity.kt)

여기도 이대로 복!붙!

class SearchActivity : AppCompatActivity(),SearchAdapter.ItemClickListener {

    private var recyclerView: RecyclerView? = null
    private var itemlist: MutableList<SearchData> = mutableListOf()
    private var mAdapter: SearchAdapter? = null
    private var searchView: SearchView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.search)
        val toolbar = findViewById(R.id.toolbar) as Toolbar
        setSupportActionBar(toolbar)
        val ab = supportActionBar!!
        ab.setDisplayShowTitleEnabled(false)
        ab.setDisplayHomeAsUpEnabled(true)

        //정의
        recyclerView = findViewById(R.id.search_recyclerview)
        itemlist = ArrayList()
        mAdapter = SearchAdapter(this, itemlist as ArrayList<SearchData>, this)

        val mLayoutManager = LinearLayoutManager(applicationContext)
        recyclerView!!.layoutManager = mLayoutManager
        recyclerView!!.itemAnimator = DefaultItemAnimator()
        recyclerView!!.adapter = mAdapter

        //엑셀 불러오기
        readExcelFileFromAssets()
    }

    //엑셀에서 가져온 데이터를 MutableList인 itemList에 추가해주고 Reutrn
    private fun readExcelFileFromAssets(): MutableList<SearchData> {
        try {
            val myInput: InputStream
            // assetManager 초기 설정
            val assetManager = assets
            //  엑셀 시트 열기
            myInput = assetManager.open("hb.xls")
            // POI File System 객체 만들기
            val myFileSystem = POIFSFileSystem(myInput)
            //워크 북
            val myWorkBook = HSSFWorkbook(myFileSystem)
            // 워크북에서 시트 가져오기
            val sheet = myWorkBook.getSheetAt(0)
            //행을 반복할 변수 만들어주기
            val rowIter = sheet.rowIterator()
            //행 넘버 변수 만들기
            var rowno = 0

            //행 반복문
            while (rowIter.hasNext()) {
                val myRow = rowIter.next() as HSSFRow
                if (rowno != 0) {
                    //열을 반복할 변수 만들어주기
                    val cellIter = myRow.cellIterator()
                    //열 넘버 변수 만들기
                    var colno = 0
                    var item_no = ""
                    var year= ""
                    var exam = ""
                    var fullname = ""
                    var cate1 = ""
                    var cate2 = ""
                    var quiz = ""
                    var answer = ""
                    var source = ""
                    var explain = ""
                    var link = ""
                    //열 반복문
                    while (cellIter.hasNext()) {
                        val myCell = cellIter.next() as HSSFCell
                        if (colno === 0) {//no,
                            item_no = myCell.toString()
                        } else if (colno === 1) {//year,
                            year = myCell.toString()
                        } else if (colno === 2) {//exam,
                            exam = myCell.toString()
                        } else if (colno === 3) {//fullname,
                            fullname = myCell.toString()
                        } else if (colno === 4) {//cate1
                            cate1 = myCell.toString()
                        } else if (colno === 5) {//cate2
                            cate2 = myCell.toString()
                        } else if (colno === 6) {//quiz
                            quiz = myCell.toString()
                        } else if (colno === 7) {//answer
                            answer = myCell.toString()
                        } else if (colno === 8) {//source
                            source = myCell.toString()
                        } else if (colno === 9) {//explain
                            explain = myCell.toString()
                        } else if (colno === 10) {//link
                            link = myCell.toString()
                        }
                        colno++
                    }
                    //4,8번째 열을 Mutablelist에 추가
                    itemlist.add(SearchData(item_no, year, exam, fullname, cate1, cate2, quiz, answer, source, explain, link))
                }
                rowno++
            }
        } catch (e: Exception) {
            Toast.makeText(this, "에러 발생", Toast.LENGTH_LONG).show()
        }

        return itemlist
    }

    //아이템 클릭시 TODO
    override fun onItemClicked(item: SearchData) {
        //여기에 원하는 코드 입력!!
        val Intent = Intent(this, Search_Detail::class.java)
        Intent.putExtra("fullname", item.fullname)
        Intent.putExtra("cate1", item.cate1)
        Intent.putExtra("cate2", item.cate2)
        Intent.putExtra("quiz", item.quiz)
        Intent.putExtra("answer", item.answer)
        Intent.putExtra("source", item.source)
        Intent.putExtra("explain", item.explain)
        Intent.putExtra("link", item.link)
        startActivity(Intent)
    }

    //우측상단메뉴
    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        val inflater = menuInflater
        inflater.inflate(R.menu.menu_search, menu)

        var searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
        searchView = menu.findItem(R.id.menu_action_search).actionView as SearchView
        searchView!!.setSearchableInfo(searchManager.getSearchableInfo(componentName))
        searchView!!.maxWidth = Integer.MAX_VALUE
        searchView!!.setOnQueryTextListener(object : SearchView.OnQueryTextListener{
            override fun onQueryTextSubmit(query: String?): Boolean {
                mAdapter!!.filter.filter(query)
                return false
            }

            override fun onQueryTextChange(query: String?): Boolean {
                mAdapter!!.filter.filter(query)
                return false
            }
        })
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        val id = item.itemId

        return if(id== R.id.menu_action_search) {
            true
        } else if(id==android.R.id.home){
            finish()
            true
        } else super.onOptionsItemSelected(item)
    }

    override fun onBackPressed() {
        // close search view on back button pressed
        if (!searchView!!.isIconified) {
            searchView!!.isIconified = true
            return
        }
        super.onBackPressed()
    }
}



 

끝.

 

댓글

Designed by JB FACTORY