Android

[Android] 안드로이드 - Api 를 사용한 MultiAutoCompleteTextView (태그자동완성 기능)

NukeOlaf 2020. 5. 11. 00:58

안드로이드에서 EditText 에 태그 자동완성 기능을 추가하려고 한다.

사용자가 입력하는 단어와 관련있는 태그를 하단에 리스트로 뿌려줄 것이다.

이럴 때 사용하는 View 가 바로 AutoCompleteTextView 이다.

이때, AutoCompleteTextView 를 extend 하는 EditText 를 사용하면, 사용자가 전체 텍스트를 입력하지 않아도 입력하는 텍스트와 연관된 단어들을 제안할 수 있다.

AutoCompleteTextView 는 한 개의 단어만을 자동으로 완성시키지만, MultiAutoCompleteTextView 는 콤마로 구분된 여러개의 단어들을 자동으로 완성시켜줄 수 있다.

 

0. multiAutoCompleteTextView 기본적인 사용 예시

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    // contries 라는 이름의 Array 초기화
    val countries = resources.getStringArray(R.array.countries_array)
    ArrayAdapter<String>(
        this,
        android.R.layout.simple_list_item_1,
        countries
    ).also { adapter ->        
        autocomplete.setAdapter(adapter) // ArrayAdpater 는 검색어 목록을 보여주는데 사용
        autocomplete.setTokenizer(SpaceTokenizer()) // Tokenizer 는 단어들을 띄어쓰기로 구분
    }    
}

 

1. 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"
    tools:context=".MainActivity">

    <MultiAutoCompleteTextView
    android:id="@+id/autocomplete"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:completionThreshold="2"
    android:completionHint="검색결과"
    android:hint="검색어를 입력해주세요"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toTopOf="@id/autocomplete"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

completionHint 는 검색 시 검색 리스트 맨 하단에 뜨는 문장

compeltionThreshold 는 검색어가 최소 몇자리일때 자동완성 기능을 실행할 것인가 (compeltionThreshold 가 2로 되어있다면, 1글자짜리 문자 상태에서는 검색이 시작되지 않고, 2글자가 되는 순간부터 자동완성 검색이 시작됨)

 

2. Adpater

사용자가 EditText 에 검색어를 입력하기 시작하면, 해당 검색어와 연관된 추천 검색어 목록이 EditText 하단에 리스트뷰로 보여진다. 이때 검색어 목록을 리스트뷰로 보여주도록 하기 위해 Adpater 를 사용한다.

0번의 예시와 같이 기본적인 ArrayAdapter 를 사용할 수도 있고, ArrayAdapter 를 상속받는 커스텀 Adapter 를 작성하여 사용할수도 있다.

class AutoSuggestAdapter(
    context: Context,
    resource: Int
) : ArrayAdapter<String>(context, resource), Filterable {

    private var suggests: MutableList<String> = mutableListOf()

    fun setSuggests(list: List<String>) {
        suggests.clear()
        suggests.addAll(list)
    }

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

    override fun getItem(position: Int): String? {
        return suggests[position]
    }

    override fun getFilter(): Filter {
        return object : Filter() {
            override fun performFiltering(constraint: CharSequence?): FilterResults {
                val filterResults = FilterResults()
                if (constraint != null) {
                    filterResults.apply {
                        values = suggests
                        count = suggests.size
                    }
                }
                return filterResults
            }

            override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
                if (results != null && results.count > 0) {
                    notifyDataSetChanged()
                } else {
                    notifyDataSetInvalidated()
                }
            }
        }
    }
}

 

 

3. Tokenizer

MultiAutoCompleteTextView 는 Tokenizer 를 사용해야 한다.

tokenizer 는 기본적으로 제공하는 commaTokenizer() 를 사용할 수도 있고, Tokenizer 를 상속받아 커스텀하여 사용할 수도 있다. tokenizer 는 단어들을 구별할만한 특정한 기호들을 정해서 단어들을 구분해줄 수 있는 기능을 한다.

나는 콤마가 아닌 공백으로 단어들을 구별하고 싶어 SpaceTokenizer 를 커스텀하여 사용했다.

class SpaceTokenizer : MultiAutoCompleteTextView.Tokenizer {
    override fun findTokenStart(text: CharSequence?, cursor: Int): Int {
        var i = cursor

        while (i > 0 && text!![i - 1] != ' ') {
            i--
        }
        while (i < cursor && text!![i] == ' ') {
            i++
        }

        return i
    }

    override fun findTokenEnd(text: CharSequence?, cursor: Int): Int {
        var i = cursor
        val len = text!!.length

        while (i < len) {
            if (text[i] == ' ') {
                return i
            } else {
                i++
            }
        }

        return len
    }

    override fun terminateToken(text: CharSequence?): CharSequence {
        var i = text!!.length

        while (i > 0 && text[i - 1] == ' ') {
            i--
        }

        return if (i > 0 && text[i - 1] == ' ') {
            text
        } else {
            if (text is Spanned) {
                val sp = SpannableString("$text ")
                TextUtils.copySpansFrom(
                    text, 0, text.length,
                    Any::class.java, sp, 0
                )
                sp
            } else {
                "$text "
            }
        }
    }
}

 

4. AutoSuggestAdapter 를 사용하여 서버에서 검색어 추천목록을 가져오는 MultiAoutoTextView 구현하기

class MainActivity : AppCompatActivity() {

    companion object {
        private const val TAG = "ActivityPostSecond"
        private const val TRIGGER_AUTO_COMPLETE = 200
        private const val AUTO_COMPLETE_DELAY = 300L
    }
    
    private lateinit var adpater: AutoSuggetAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_post_second)

        initMutliAutoCompleteTextView()

    }

    private fun initMutliAutoCompleteTextView() {
        adapter = AutoSuggestAdapter(this, android.R.layout.simple_list_item_1)
        val handler = Handler(Handler.Callback { msg ->
            if (msg.what == TRIGGER_AUTO_COMPLETE) {
                if (autocomplete.text.isNullOrEmpty()) {
                    getSuggestionApi(autocomplete.text.toString())
                }
            }
            false
        })
        autocomplete.apply {
            setAdapter(adapter)
            setTokenizer(SpaceTokenizer())
            addTextChangedListener(object : TextWatcher {
                override fun afterTextChanged(s: Editable?) {}

                override fun beforeTextChanged(
                    s: CharSequence?,
                    start: Int,
                    count: Int,
                    after: Int
                ) {
                }

                override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                    handler.removeMessages(TRIGGER_AUTO_COMPLETE)
                    handler.sendEmptyMessageDelayed(
                        TRIGGER_AUTO_COMPLETE,
                        AUTO_COMPLETE_DELAY
                    )
                }
            })
        }
    }
    
    private fun getSuggestionsApi(query: Text) {
        RetrofitClient.getTagRecommendation(query)
            .enqueue(object :
                Callback<List<String>> {
                override fun onResponse(
                    call: Call<List<String>>,
                    response: Response<List<String>>
                ) {
                    val body = response.body()
                    if (body != null && response.isSuccessful) {
                        adapter.setSuggests(body)
                    } else {
                        // 통신 에러 처리
                    }
                }

                override fun onFailure(call: Call<List<String>>, t: Throwable) {
                    // 통신 에러 처리
                }
            })
    }
}    

 

 

참고 사이트 >>>

https://en.proft.me/2016/10/20/autocompletetextview-and-multiautocompletetextview/

http://www.codeplayon.com/2019/06/android-multiautocompletetextview-with-api-example-tutorial/

https://www.truiton.com/2018/06/android-autocompletetextview-suggestions-from-webservice-call/

https://sharp57dev.tistory.com/12

https://parkho79.tistory.com/83