Layout
Why use FlexboxLayout? It allows tags/chip to flow to next row, while AutoCompleteTextView
/ EditText
will fill up the remaining space of the row.
Why not use ChipGroup
? Chip
cannot flow on the same row as AutoCompleteTextView
/ EditText
within ChipGroup
.
<FrameLayout 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="wrap_content"
>
<com.google.android.flexbox.FlexboxLayout
android:id="@+id/tagsChipGroup"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:flexWrap="wrap"
app:alignItems="stretch"
app:alignContent="stretch" >
<AutoCompleteTextView
android:hint="Tags"
android:id="@+id/tagsAutoCompleteTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:imeOptions="actionDone"
android:maxLines="1"
android:singleLine="true"
android:inputType="text|textCapWords"
app:layout_flexGrow="1" />
</com.google.android.flexbox.FlexboxLayout>
</FrameLayout>
Code
val editText = tagsAutoCompleteTextView// load autocompleteval allTags = listOf("Love", "Passion", "Peace", "Hello", "Test")val adapter = ArrayAdapter<String>(context, android.R.layout.simple_dropdown_item_1line, allTags)editText.setAdapter(adapter)// load tagsval tags = listOf("Hello", "This Is A Big World", "Test Multiple Row")for (name in tags) { tagsChipGroup.addChip(name) { // TODO: handle when tag removed }}editText.setOnAddTagListener { name -> // TODO: handle when tag is created val tags = tagsChipGroup.getAllChips().map { it.text.toString() } editText.text = null}
Kotlin Extensions
fun AutoCompleteTextView.setOnAddTagListener(callback: (name: String) -> Unit) { // select from adapter list setOnItemClickListener { adapterView, _, position, _ -> val name = adapterView.getItemAtPosition(position) as String callback(name) } // done pressed setOnEditorActionListener { textView, actionId, _ -> if (actionId == EditorInfo.IME_ACTION_DONE) { val name = textView.text.toString() callback(name) true } else false } doAfterTextChanged { text -> if (text != null && text.isEmpty()) { return@doAfterTextChanged } // comma is detected (optional space) if (text?.last() == ',') { // || text?.last() == ' ' val name = text.substring(0, text.length - 1) callback(name) } }}fun FlexboxLayout.getAllChips(): List<Chip> { return (0 until childCount).mapNotNull { index -> getChildAt(index) as? Chip }}fun FlexboxLayout.clearChips() { val chipViews = (0 until childCount).mapNotNull { index -> val view = getChildAt(index) if (view is Chip) view else null } chipViews.forEach { removeView(it) }}private fun ViewGroup.addChip(text: String, removeCallback: (message: String) -> Unit) { // val chip = Chip(context) val inflater = LayoutInflater.from(context) val chip = inflater.inflate(R.layout.view_chip, null) as Chip chip.text = text /* test_chip.isCloseIconVisible = true // necessary to get single selection working test_chip.isClickable = true test_chip.isCheckable = false */ // val layoutParams = test_chip.layoutParams as ViewGroup.MarginLayoutParams val layoutParams = ViewGroup.MarginLayoutParams(ViewGroup.MarginLayoutParams.WRAP_CONTENT, ViewGroup.MarginLayoutParams.WRAP_CONTENT) layoutParams.rightMargin = 4.dpToPx(context.resources.displayMetrics) addView(chip as View, childCount - 1, layoutParams) chip.setOnCloseIconClickListener { removeView(chip as View) removeCallback(chip.text.toString()) }}
R.drawable.view_chip
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.chip.Chip xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
app:closeIconEnabled="true"
/>