(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