본문 바로가기
학습(삽질) 노트/Android

AutoCompleteTextView를 활용한 Custom Filtered Adapter

by -MAYDAY- 2022. 8. 20.

AutoCompleteTextView를 활용한 Custom Filtered Adapter

 안드로이드 프로젝트를 진행하면서 검색어에 따른 검색 결과가 리스트로 나타나도록 구현해야 하는 상황이 생긴다. 이러한 경우에  AutoCompleteTextView 라는 안드로이드 기본 지원 Component를 사용하여 구현할 수 있다. 간단한 텍스트 검색은 쉽게 구현할 수 있지만, 특정 검색 조건(ex. Data 클래스를 기반으로 만들어진 객체 내의 변수를 조건)을 충족하는 검색 리스트를 화면에 나타나게 하려면 Filtering 과정이 필요하다. 안드로이드에서 특정 검색 조건을 기준으로 Filtering하는 과정을 어떻게 구현하는지 알아보고 직접 실습하고자 한다.

 

Data class 생성

import java.io.Serializable

data class Post (
    var postId: Int = 0,
    val title: String = "",
    val content: String = "",
    val place: String = "",
    val type: String = ""): Serializable

 AutoCompleteTextView에서 검색 대상인 Data class를 생성한다. 간단하게 Post 라는 Data class를 정의하였다.

 

객체 List 생성

val postList = listOf(
    Post(1, "Post Title 1", "Post Content 1", "Restaurant", "Daily"),
    Post(2, "Post Title 2", "Post Content 2","Cafe", "Daily"),
    Post(3, "Post Title 3", "Post Content 3", "Bank", "Work"),
    Post(4, "Post Title 4", "Post Content 4","Store", "Work"),
    Post(5, "Post Title 5", "Post Content 5", "Station", "Daily")
)

 AutoCompleteTextView의 검색 결과 리스트에 나올 수 있는 객체 List를 생성하였다. 실습 목적으로 생성하였기에 간단하게 구현하였다.

 

화면에 표시할 XML 생성

AutoCompleteTextView (activity_main.xml)

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

    <AutoCompleteTextView
        android:id="@+id/actv_search_title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="24dp"
        android:layout_marginEnd="24dp"
        android:hint="검색어를 입력하세요."
        android:imeOptions="actionSearch"
        android:inputType="text"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:ignore="TouchTargetSizeCheck" />

</androidx.constraintlayout.widget.ConstraintLayout>

 키워드 검색을 하기 위한 목적으로 Activity의 XML에 간단하게 AutoCompleteTextView를 정의하였다.

 

AutoCompleteTextView의 검색 리스트에 나타날 Item View (list_search_title.xml)

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="8dp">


    <TextView
        android:id="@+id/tv_title"
        style="@style/header_text"
        android:text="게시글 제목"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_content"
        style="@style/explain_text_gray"
        android:layout_marginTop="@dimen/activity_margin_8dp"
        android:text="위치"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tv_title" />

    <TextView
        android:id="@+id/tv_place"
        style="@style/explain_text_gray"
        android:text="장소"
        android:textSize="10sp"
        app:layout_constraintBottom_toBottomOf="@+id/tv_content"
        app:layout_constraintEnd_toEndOf="parent" />
    
</androidx.constraintlayout.widget.ConstraintLayout>

 AutoCompleteTextView의 검색 결과 리스트에 나타날 ItemView를 정의하였다. 각각 Post마다 title, content, place를 검색 결과 리스트에 표시할 목적으로 간단하게 구성했다.

 

Filterable Adapter 생성

package com.example.autocompletetexttest

import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*

class SearchTitleAdapter(context: Context, private val layoutResource: Int, private val postList: List<Post>) : ArrayAdapter<Post>(context, layoutResource, postList),
    Filterable {

    private var mList: List<Post> = postList

    override fun getCount(): Int {
        return mList.size
    }

    override fun getItem(position: Int): Post? {
        return mList[position]
    }

    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        var view = LayoutInflater.from(parent.context).inflate(layoutResource, parent, false)
        var tvTitle = view.findViewById<TextView>(R.id.tv_title)
        var tvContent = view.findViewById<TextView>(R.id.tv_content)
        var tvPlace = view.findViewById<TextView>(R.id.tv_place)

        tvTitle.text = mList[position].title
        tvContent.text = mList[position].content
        tvPlace.text = mList[position].place
        return view
    }

    // 여기서 검색어에 대한 조건 정의, 즉 필터링 기준을 정의하는 곳이다.
    override fun getFilter(): Filter {
        return object : Filter() {
            override fun performFiltering(p0: CharSequence?): FilterResults {
                val queryString = p0?.toString()

                Log.d("getFilter", "performFiltering: ${queryString}")

                var filterResults = FilterResults()
                filterResults.values = if (queryString == null || queryString.isEmpty())
                    postList
                else
                    postList.filter {
                        // title, content, place에 포함되는 검색어를 입력하면 필터링이 적용된다.
                        it.title.contains(queryString) || it.content.contains(queryString) || it.place.contains(queryString)
                    }
                return filterResults
            }

            override fun publishResults(p0: CharSequence?, p1: FilterResults?) {
                mList = p1!!.values as MutableList<Post>
                notifyDataSetChanged()
            }

        }
    }

}

 검색하고자 하는 검색어 리스트를 Adapter에 넣어 Filtering을 하기 위한 Filterable Adapter를 정의해주었다. 여기서 포인트는 Fiterable Interface를 implement해서 내부의 함수들을 각각 Overriding해서 postList와 관련된 설정들을 코드로 작성해준다. 또한  랴Filtering 검색 조건을 정의하는 getFilter 함수를 Overriding해서 원하고자 하는 검색 조건을 정의하면 된다. 검색 키워드 조건을 getFilter 함수 내에 지정해주면 AutoCompleteTextView에서 검색 조건에 맞는 키워드 입력 시 의도한 검색 결과가 리스트로 나타나게 된다.

 

MainActivity

package com.example.autocompletetexttest

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.autocompletetexttest.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        var postList = mutableListOf(
            Post(1, "Post Title 1", "Post Content 1", "Restaurant", "Daily"),
            Post(2, "Post Title 2", "Post Content 2","Cafe", "Daily"),
            Post(3, "Post Title 3", "Post Content 3", "Bank", "Work"),
            Post(4, "Post Title 4", "Post Content 4","Store", "Work"),
            Post(5, "Post Title 5", "Post Content 5", "Station", "Daily")
        )

        val searchTitleAdapter = SearchTitleAdapter(baseContext, R.layout.list_search_title, postList)
        binding.actvSearchTitle.setAdapter(searchTitleAdapter)

    }
}

 viewBinding을 사용하여 AutoCompleteTextView에 SearchTitleAdapter를 Setting 해주었다. 키워드에 따른 검색 결과 리스트에 화면에 나타나면, 그 중 하나를 선택해서 화면이 넘어가도록 구성하고 싶으면 setOnItemClickListener를 추가해서 Intent를 활용하여 이동할 Activity를 정의하면 된다.

 

예시 화면 캡처

AutoCompleteTextView 구현 예시 화면

 

 AutoCompleteTextView를 활용하여 검색 결과 리스트에 나타날 검색어들을 Filterable Interface를 활용하여 검색 조건을 설정해보았다. 구글링을 통해 AutoCompleteTextView에 검색 조건을 적용하는 사례가 많지 않아 이를 구현하는데 생각보다 많은 시간을 투자하게 되었다. 특히, Kotlin 기반으로 국내 블로거들이 적용한 경우가 많지 않아 찾기 어려웠지만 해외 블로거가 구현한 코드를 참고하여 다행히 작년 해커톤 프로젝트에서 적용할 수 있었다. 우리가 사용하는 대부분의 안드로이드 앱에서 검색 기능은 기본 탑재되어있기에 AutoCompleteTextView에 적절한 검색 Filtering 조건을 적용시키는 것을 미리 알아두어 어렵지 않게 구현하도록 하자.

 

참고

https://spapas.github.io/2019/04/05/android-custom-filter-adapter/

 

How to create a custom filtered adapter in Android — /var/

Hello! I see that you have an ad blocker. If you find something useful here and want to support me somehow please consider disabling your ad blocker for this site. It won't cost you anything but will convince me that there's actually useful info here! How

spapas.github.io

 

'학습(삽질) 노트 > Android' 카테고리의 다른 글

Clean Architecture  (0) 2022.07.30
Android MVVM 구현하기  (0) 2022.05.01
REST API와 Retrofit  (0) 2022.03.26