'How to add chips from Material Components library to input field in android?

I've seen that in android-P google add new material components library which contains material chips:

Material components for android

Material.io chips usage

Material components on GitHub

So I decided to add material input chips to my project, but unfortunately didn't find any tutorial how to make that. I want to create something like Gmail chips but without image on the start.

Because I'm using appcompat library I tried to use material chips by android.support.design.chip.Chip and android.support.design.chip.ChipGroup. But result was just chips without any input field. Also I tried to create a Standalone ChipDrawable and then add it to EditText using

Editable text = editText.getText();

text.setSpan(span, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

But I got empty EditText without any chips. So how can I create chips input like in Gmail using this material components library? Maybe someone has expreience or knows any tutorials where I could see how to create this?

Thanks in advance!



Solution 1:[1]

All of the previous solutions didn't work for me if you want to achieve a gmail like behaviour with chips on multiple lines. In order to do that I had to avoid using the ChipGroup and instead using a FlexboxLayout.

enter image description here

your_recipient_layout:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/recipient_label_TV"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:layout_gravity="center_vertical" />

    <com.google.android.flexbox.FlexboxLayout
        android:id="@+id/recipient_group_FL"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_gravity="center_vertical"
        app:flexWrap="wrap"
        app:alignItems="stretch"
        app:alignContent="space_around"
        app:showDivider="beginning|middle|end"
        app:dividerDrawable="@drawable/divider">

        <EditText
            android:id="@+id/recipient_input_ET"
            android:layout_width="wrap_content"
            android:layout_height="32dp"
            app:layout_flexGrow="1"
            android:background="@android:color/transparent"
            android:imeOptions="actionDone"
            android:inputType="text"/>

    </com.google.android.flexbox.FlexboxLayout>

</LinearLayout>

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recipients_list_RV"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:visibility="gone" />

The trick now is adding a new chip to the group but as second last position. Something like this:

private fun addNewChip(person: String, chipGroup: FlexboxLayout) {
    val chip = Chip(context)
    chip.text = person
    chip.chipIcon = ContextCompat.getDrawable(requireContext(), R.mipmap.ic_launcher_round)
    chip.isCloseIconEnabled = true
    chip.isClickable = true
    chip.isCheckable = false
    chipGroup.addView(chip as View, chipGroup.childCount - 1)
    chip.setOnCloseIconClickListener { chipGroup.removeView(chip as View) }
}

Solution 2:[2]

We can do this by using material chips design itself without adding any extra styles.

Add it on app gradle For AndroidX

implementation 'com.google.android.material:material:1.0.0-beta01'

For earlier than AndroidX use this

implementation 'com.android.support:design:28.0.0'

enter image description here

Fragment

class EntryChipDemoFragment : Fragment() {
    private lateinit var mView: View

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        mView = inflater.inflate(R.layout.fragment_entry_chip_demo, container, false)

        mView.etValue.setOnEditorActionListener(TextView.OnEditorActionListener { v, actionId, _ ->
            if (actionId == EditorInfo.IME_ACTION_DONE) {
                val txtVal = v.text
                if(!txtVal.isNullOrEmpty()) {
                    addChipToGroup(txtVal.toString(), mView.chipGroup2)
                    mView.etValue.setText("")
                }

                return@OnEditorActionListener true
            }
            false
        })

        return mView
    }


    private fun addChipToGroup(txt: String, chipGroup: ChipGroup) {
        val chip = Chip(context)
        chip.text = txt
//        chip.chipIcon = ContextCompat.getDrawable(requireContext(), baseline_person_black_18)
        chip.isCloseIconEnabled = true
        chip.setChipIconTintResource(R.color.chipIconTint)

        // necessary to get single selection working
        chip.isClickable = false
        chip.isCheckable = false
        chipGroup.addView(chip as View)
        chip.setOnCloseIconClickListener { chipGroup.removeView(chip as View) }
        printChipsValue(chipGroup)
    }

    private fun printChipsValue(chipGroup: ChipGroup) {
        for (i in 0 until chipGroup.childCount) {
            val chipObj = chipGroup.getChildAt(i) as Chip
            Log.d("Chips text :: " , chipObj.text.toString())

        }
    }

    companion object {
        @JvmStatic
        fun newInstance() = EntryChipDemoFragment()
    }
}

XML File:

<HorizontalScrollView
    android:id="@+id/chipGroup2HorizontalView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="8dp"
    android:layout_marginTop="16dp"
    android:scrollbars="none"
    app:layout_constraintVertical_bias="0.62">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:orientation="horizontal">

        <androidx.appcompat.widget.AppCompatTextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Skills: " />

        <com.google.android.material.chip.ChipGroup
            android:id="@+id/chipGroup2"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:duplicateParentState="false">

        </com.google.android.material.chip.ChipGroup>

        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/textInputLayout"
            android:layout_width="wrap_content"
            android:layout_height="43dp"
            android:layout_marginStart="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginBottom="5dp"
            android:minWidth="32dp"
            android:visibility="visible"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@id/chipGroup2HorizontalView"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintWidth_min="32dp">

            <androidx.appcompat.widget.AppCompatEditText
                android:id="@+id/etValue"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@android:color/transparent"
                android:imeOptions="actionDone"
                android:maxLines="1"
                android:singleLine="true" />

        </com.google.android.material.textfield.TextInputLayout>

    </LinearLayout>


</HorizontalScrollView>

For more reference Click Here

Solution 3:[3]

You can use material chip "com.google.android.material.chip.Chip" and "implementation 'com.google.android.material:material:1.0.0' " add in build.gradle

Filter style="@style/Widget.MaterialComponents.Chip.Filter"

Choice Chips style="@style/Widget.MaterialComponents.Chip.Choice"

Entry input: style="@style/Widget.MaterialComponents.Chip.Entry"

Solution 4:[4]

Using TextInputLayout

chip_item.xml

<?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"
    style="@style/Widget.MaterialComponents.Chip.Action"
    app:closeIconEnabled="true"
    android:layout_width="wrap_content" />

tags_input_layout.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="wrap_content">

        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/i_input_v"
            style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:hint="Tags">

            <com.google.android.material.textfield.TextInputEditText
                android:layout_width="match_parent"
                android:gravity="bottom"
                android:paddingBottom="22dp"
                android:layout_height="match_parent" />
        </com.google.android.material.textfield.TextInputLayout>

    <com.google.android.material.chip.ChipGroup
        android:id="@+id/i_flex_box"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:alignContent="space_around"
        app:alignItems="stretch"
        app:flexWrap="wrap"
        android:layout_marginTop="12dp"
        android:layout_marginBottom="50dp"
        android:layout_marginStart="10dp"
        android:layout_marginEnd="10dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:showDivider="beginning|middle|end">


    </com.google.android.material.chip.ChipGroup>

</androidx.constraintlayout.widget.ConstraintLayout>

TagInputView.kt

class TagInputView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null
) : ConstraintLayout(context, attrs) {

    val chipGroup: ChipGroup

    init {
        LayoutInflater.from(context).inflate(R.layout.tags_input_layout, this, true)

        val inputLayout = findViewById<TextInputLayout>(R.id.i_input_v)
        val editText = inputLayout.editText!!
        chipGroup = findViewById(R.id.i_flex_box)

        editText.onFocusChangeListener = OnFocusChangeListener { _, hasFocus ->
            if (hasFocus) {
                if (editText.text.toString() == " "){
                    editText.text.clear()
                }
            } else {
                if (editText.text.isNullOrEmpty() && chipGroup.childCount > 0) {
                    editText.setText(" ")
                }
            }
        }

        editText.setOnKeyListener { _, _, event ->
            if (event.action == KeyEvent.ACTION_DOWN) {
//                onBackspacePressed, also edittext is empty
                if (chipGroup.childCount <= 0) return@setOnKeyListener false
                val lastChip = chipGroup.getChildAt(chipGroup.childCount - 1) as Chip
                editText.append(lastChip.text)
                chipGroup.removeView(lastChip)
            }
            false
        }

        editText.addTextChangedListener(object : TextWatcher {
            override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
            override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}

            override fun afterTextChanged(editable: Editable) {
                val text = editable.toString()
                if (text.isNotEmpty() && text.endsWith(",")) {
                    addNewChip(text.removeSuffix(","))
                    editable.clear()
                }
            }
        })
    }

    private fun addNewChip(text: String) {
        val newChip =
            LayoutInflater.from(context).inflate(R.layout.chip_item, chipGroup, false) as Chip
        newChip.id = ViewCompat.generateViewId()
        newChip.text = text
        newChip.setOnCloseIconClickListener {
            chipGroup.removeView(newChip)
        }
        chipGroup.addView(newChip)
    }
}

Usage

activity_main.xml

...
<com.shubhamgupta16.yourappid.utils.TagInputView
    android:id="@+id/tagInputView"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>
...

You can also access all Chip inside ChipGroup by using tagInputView.chipGroup property.

Output

enter image description here

Solution 5:[5]

Not the ideal solution, but definitely easy

<FrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <com.google.android.material.chip.Chip
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:chipMinTouchTargetSize="0dp" />

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:weightSum="10">

                <EditText
                    android:id="@+id/add_frag_label_et_name"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:layout_marginStart="8dp"
                    android:layout_weight="9"
                    android:background="@null"
                    android:hint="Enter Label"
                    android:inputType="text"
                    android:paddingStart="8dp"
                    android:paddingEnd="0dp"
                    android:textAppearance="@style/TextAppearance.AppCompat.Medium"
                    android:textColor="@color/black"
                    android:textSize="14sp" />

                <ImageView
                    android:id="@+id/add_frag_label_iv_add"
                    android:layout_width="32dp"
                    android:layout_height="32dp"
                    android:layout_gravity="center"
                    app:tint="#6b6a6c"
                    android:layout_weight="1"
                    android:contentDescription="Add Label to note."
                    android:src="@mipmap/ic_plus_foreground" />
            </LinearLayout>
        </FrameLayout>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 br00
Solution 2 Naveen Kumar M
Solution 3 Arul Pandian
Solution 4 Shubham Gupta
Solution 5 shekhar g h