Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- AWS EC2
- llm
- video caching
- kotlin list
- 스피너
- ChatGPT
- build with ai
- ListAdapter
- DiffUtil.ItemCallback
- android
- 독서
- exoplayer cache
- android ktor
- 안드로이드
- android exoplayer
- android custom view
- getChangePayload
- ExoPlayer
- doc2vec
- ktor api call
- Python
- kotlin collection
- Zsh
- ktor client
- 시행착오
- ListAdapter DiffUtil
- list map
- FastAPI
- 유튜브
- map
Archives
- Today
- Total
버튼 수집상
[안드로이드] 검색어 자동완성 커스텀 뷰 만들기 Custom Auto Complete View 본문
위처럼 모서리가 둥글고 살짝 그림자가 지는 자동완성 검색어 UI를 그려주기 위해 커스텀 뷰를 만들었다.
뷰바인딩 세팅하기
build.gradle.kts(:app)
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
android {
// 생략..
buildFeatures {
viewBinding = true
}
}
자동완성 커스텀뷰의 베이스 뷰 xml
layout_auto_complete.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- 자동완성 검색어를 나타내는 TextView가 addView 될 것-->
<LinearLayout
android:id="@+id/hint_holder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/background_auto_complete_shadow"
android:orientation="vertical"
tools:layout_height="40dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
자동완성 커스텀뷰의 베이스 뷰 배경 drawable
background_auto_complete_shadow.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Drop Shadow Stack -->
<item>
<shape>
<padding
android:bottom="1dp"
android:left="1dp"
android:right="1dp"
android:top="1dp"/>
<solid android:color="#07bbbbbb" />
<corners android:radius="12dp" />
</shape>
</item>
<item>
<shape>
<padding
android:bottom="1dp"
android:left="1dp"
android:right="1dp"
android:top="1dp"/>
<solid android:color="#0fbbbbbb" />
<corners android:radius="12dp" />
</shape>
</item>
<item>
<shape>
<padding
android:bottom="1dp"
android:left="1dp"
android:right="1dp" />
<solid android:color="#1fbbbbbb" />
<corners android:radius="12dp" />
</shape>
</item>
<item>
<shape>
<padding
android:bottom="1dp"
android:left="1dp"
android:right="1dp" />
<solid android:color="#2fbbbbbb" />
<corners android:radius="12dp" />
</shape>
</item>
<!-- Background -->
<item>
<shape>
<solid android:color="@android:color/white" />
<corners android:radius="12dp" />
</shape>
</item>
</layer-list>
그림자 레이어를 쌓으면서 위 레이어에서부터 순서대로
넓은 영역 -> 좁은 영역
연한 색 -> 진한 색
으로 그려진다.
커스텀뷰 코드
AutoComplete.kt
import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.view.View
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import com.pionnet.accountbook.R
import com.pionnet.accountbook.databinding.LayoutAutoCompleteBinding
import com.pionnet.accountbook.utils.getSpannedColorText
/**
* 자동완성 홀더
*
* TODO
* 1. AutoCompleteListener 인터페이스 구현하기
* 2. [setInput] 검색어 입력
* 3. [setList] 자동완성 결과 리스트 입력
*/
class AutoComplete @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
def: Int = 0,
) : ConstraintLayout(context, attrs, def) {
interface AutoCompleteListener {
/* 선택한 자동완성 단어*/
fun selectAutoComplete(input: String)
}
private lateinit var mListener: AutoCompleteListener
fun setListener(listener: AutoCompleteListener) {
mListener = listener
}
var binding: LayoutAutoCompleteBinding
private var inputString = ""
private var autoCompleteList = listOf<String>()
// 디폴트 검색어 하이라이트 컬러
private var mColor = Color.parseColor("#FF5046")
// 디폴트 텍스트 사이즈
private var mTextSize = 15f
init {
val view = inflate(context, R.layout.layout_auto_complete, this)
binding = LayoutAutoCompleteBinding.bind(view)
}
// 검색어 입력이 실시간으로 감지되는 콜백에서 호출 (ex: TextWatcher 콜백)
fun setInput(input: String) {
inputString = input
}
// 자동완성 단어 리스트
// 검색어 입력시 동시에 입력
fun setList(input: List<String>) {
autoCompleteList = input
if (autoCompleteList.isEmpty()) {
clearAutoComplete()
return
}
binding.root.visibility = View.VISIBLE
val matchingResult: List<String> = autoCompleteList.filter { it.contains(inputString) }
if (matchingResult.isEmpty()) {
clearAutoComplete()
return
}
binding.hintHolder.visibility = View.VISIBLE
matchingResult.forEach { match ->
val matchingTextView = TextView(context, null, 0, R.style.AutoCompleteText).apply {
text = match.getSpannedColorText(inputString, mColor, false)
textSize = mTextSize
setOnClickListener {
mListener.selectAutoComplete(match)
clearAutoComplete()
}
}
binding.hintHolder.addView(matchingTextView)
}
}
fun clearAutoComplete() {
binding.root.visibility = View.GONE
binding.hintHolder.removeAllViews()
}
fun setMatchingTextColor(color: Int) {
mColor = color
}
fun setTextSize(dp: Float) {
mTextSize = dp
}
}
사용처
Activity/Fragment xml 예시
<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">
<!-- 검색어 입력창 -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/search_form"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:backgroundTint="@color/background"
android:elevation="0dp"
app:cardCornerRadius="12dp"
app:cardElevation="0dp"
app:layout_constraintTop_toBottomOf="@id/back"
app:strokeColor="#bbbbbb"
app:strokeWidth="0.7dp">
<!-- childview 생략 -->
</com.google.android.material.card.MaterialCardView>
<!-- 검색어 입력창 바로 아래에 자동완성 커스텀뷰 -->
<com.test.android.views.AutoComplete
android:id="@+id/autoComplete"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="@id/search_form"
app:layout_constraintStart_toStartOf="@id/search_form"
app:layout_constraintTop_toBottomOf="@id/search_form"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
검색어 입력창과 똑같은 너비로 잡고 싶어서 layout_width="0dp"로 설정한 후, Constraint로 start와 end를 잡아줬다.
사용례
Activity/Fragment 예시
/* 사용례 */
// 검색하는 경우
// 1. EditText에서 엔터 입력
// 2. AutoComplete 커스텀 뷰에서 자동완성 검색어 직접 선택
class SearchFragment : Fragment(R.layout.fragment_search), AutoComplete.AutoCompleteListener {
// 프래그먼트 뷰바인딩 init
lateinit var binding: FragmentSearchBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentSearchBinding.inflate(inflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView()
}
@SuppressLint("ClickableViewAccessibility")
private fun initView() = with(binding) {
// AutoComplete 커스텀뷰 리스너 세팅
autoComplete.setListener(this@SearchFragment)
// EditText 검색어 입력창
search.apply {
// 키보드 노출하고 포커스 주는 유틸
showKeyboard()
// 검색어 입력값 실시간 검사 유틸
addTextChangedListener(object : TextInputWatcher() {
override fun onInputTextChanged(input: String) {
clearSearch.isVisible = input.isNotEmpty()
if (input.isNotEmpty()) {
// 입력값이 있을 때마다 AutoComplete 뷰에 세팅
autoComplete.setInput(input)
autoComplete.setList(getAutoCompleteList())
} else {
autoComplete.clearAutoComplete()
}
}
})
// EditText 엔터 리스너 유틸
setOnKeyListener(object : EnterListener() {
override fun onEnter() {
search()
}
})
}
}
// 자동완성 검색어 결과 API
private fun getAutoCompleteList(): List<String> {
val input = binding.search.text
// TODO API 구현
return autoComplete(input)
}
// 자동완성 검색어에서 선택한 단어로 검색
override fun selectAutoComplete(input: String) {
binding.search.setText(input)
search()
}
// 검색 API
private fun search() {
// TODO API 구현
}
}
코드에서 사용한 유틸 함수들
Utils.kt
// 전체중 일부 문자열에 텍스트 컬러 적용하기 유틸
// 예) "강조영역".getSpannedColorText("강조", Color.RED)
fun String.getSpannedColorText(changed: String, color: Int, bold: Boolean = false): Spannable {
val sb = SpannableStringBuilder(this)
val pair = getChangedIndex(this, changed)
sb.setSpan(
ForegroundColorSpan(color),
pair.first,
pair.second,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
if (bold) {
sb.setSpan(
StyleSpan(Typeface.BOLD),
pair.first,
pair.second,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
return sb
}
fun getChangedIndex(origin: String, changed: String): Pair<Int, Int> {
if (origin.isEmpty()) return Pair(0, 0)
var start = origin.indexOf(changed, 0)
var end = start + changed.length
if (start == -1) {
start = 0
end = origin.length
}
return Pair(start, end)
}
// 실시간 텍스트 입력 감지
abstract class TextInputWatcher : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
onInputTextChanged(s.toString())
}
abstract fun onInputTextChanged(input: String)
}
// 엔터 입력감지
abstract class EnterListener : View.OnKeyListener {
override fun onKey(v: View?, keyCode: Int, event: KeyEvent?): Boolean {
if (keyCode == KeyEvent.KEYCODE_ENTER && event?.action == KeyEvent.ACTION_DOWN) {
onEnter()
return true
}
return false
}
abstract fun onEnter()
}
// 시스템 키보드 노출 & 포커스
fun EditText.showKeyboard() {
requestFocus()
val inputManager = context.getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
inputManager.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
}
보완
안드로이드에서 제공하는 AutoCompleteTextView에서 ArrayAdapter를 세팅하는 부분만 커스텀하게 꾸밀 수도 있을 것 같다.
public class CountriesActivity extends Activity {
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.countries);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_dropdown_item_1line, COUNTRIES);
AutoCompleteTextView textView = (AutoCompleteTextView)
findViewById(R.id.countries_list);
textView.setAdapter(adapter);
}
private static final String[] COUNTRIES = new String[] {
"Belgium", "France", "Italy", "Germany", "Spain"
};
}
안드로이드 공식 문서에서 제공하는 샘플코드 (자바)
728x90
'TIL - 안드로이드' 카테고리의 다른 글
[안드로이드] 웹크롬클라이언트로 웹뷰 새 창 처리하기 WebChromeClient onCreateWindow (0) | 2024.04.01 |
---|---|
[안드로이드] 모서리가 둥근 뷰파인더 만들기 rounded square transparent mask for zxing barcode reader UI (0) | 2023.12.14 |
[안드로이드] Retrofit 대신 Ktor로 Api 호출해보기 - 2 (0) | 2023.12.11 |
[안드로이드] Retrofit 대신 Ktor로 Api 호출해보기 - 1 (0) | 2023.12.11 |
[안드로이드] TextView 원하는 글자만 남기고 말줄임표 보여주기 Custom Ellipsis (1) | 2023.12.07 |