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

25,760

Solution 1

Answer

No default input field for adding chips in android. They mentioned input chips but i didn't find any layout or viewgroup for input chips. So i do with Chipdrawable method to add chips in edittext. Here am using AppCompatEdittext you can change to anyview which listening the text inputs. Reference.

Step 1

Add chip xml resource. chip.xml

res -> xml -> chip.xml

<?xml version="1.0" encoding="utf-8"?>
<chip xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto"
 android:textAppearance="@style/ChipTextAppearance"
 app:chipBackgroundColor="@color/colorAccent"
 app:chipIcon="@drawable/ic_call_white_24dp"
 app:closeIconEnabled="true"  <!--property for close icon if no need set to false. -->
 app:closeIconTint="@android:color/white" />

Then add textappearance style in style.xml(For change textStyle)

<style name="ChipTextAppearance" parent="TextAppearance.MaterialComponents.Chip">
    <item name="android:textColor">@android:color/white</item>
</style>

Step 2

Add your view here am using AppCompatEdittext

  <android.support.v7.widget.AppCompatEditText
    android:id="@+id/phone"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginEnd="8dp"
    android:layout_marginLeft="8dp"
    android:layout_marginRight="8dp"
    android:layout_marginStart="8dp"
    android:layout_marginTop="8dp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/tvt_Contact" />

Step 3
Add this code to your view to get the desired behaviour.

 private int SpannedLength = 0,chipLength = 4;

 AppCompatEditText Phone = findViewById(R.id.phone);

 Phone.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

        }

        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
            if (charSequence.length() == SpannedLength - chipLength)
            {
                SpannedLength = charSequence.length();
            }
        }

        @Override
        public void afterTextChanged(Editable editable) {

            if(editable.length() - SpannedLength == chipLength) {
                ChipDrawable chip = ChipDrawable.createFromResource(getContext(), R.xml.chip);
                chip.setChipText(editable.subSequence(SpannedLength,editable.length()));
                chip.setBounds(0, 0, chip.getIntrinsicWidth(), chip.getIntrinsicHeight());
                ImageSpan span = new ImageSpan(chip);
                editable.setSpan(span, SpannedLength, editable.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                SpannedLength = editable.length();
            }

        }
    });

Change chipLength according to your need when new chip need to be added in edittext.

OUTPUT

enter image description here

EDITED

You can find more about how to align center the text with span Here.

Here i added some code from the solution will fix for you..

public class VerticalImageSpan extends ImageSpan {

public VerticalImageSpan(Drawable drawable) {
    super(drawable);
}

@Override
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end,
                   Paint.FontMetricsInt fontMetricsInt) {
    Drawable drawable = getDrawable();
    Rect rect = drawable.getBounds();
    if (fontMetricsInt != null) {
        Paint.FontMetricsInt fmPaint = paint.getFontMetricsInt();
        int fontHeight = fmPaint.descent - fmPaint.ascent;
        int drHeight = rect.bottom - rect.top;
        int centerY = fmPaint.ascent + fontHeight / 2;

        fontMetricsInt.ascent = centerY - drHeight / 2;
        fontMetricsInt.top = fontMetricsInt.ascent;
        fontMetricsInt.bottom = centerY + drHeight / 2;
        fontMetricsInt.descent = fontMetricsInt.bottom;
    }
    return rect.right;
}

@Override
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end,
                 float x, int top, int y, int bottom, @NonNull Paint paint) {

    Drawable drawable = getDrawable();
    canvas.save();
    Paint.FontMetricsInt fmPaint = paint.getFontMetricsInt();
    int fontHeight = fmPaint.descent - fmPaint.ascent;
    int centerY = y + fmPaint.descent - fontHeight / 2;
    int transY = centerY - (drawable.getBounds().bottom - drawable.getBounds().top) / 2;
    canvas.translate(x, transY);
    drawable.draw(canvas);
    canvas.restore();
}

}

And change your imagespan class like below

VerticalImageSpan span = new VerticalImageSpan(chip);

Solution 2

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 3

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

Share:
25,760

Related videos on Youtube

IDmikael
Author by

IDmikael

Аndroid and Mobile games with Unity3d (=

Updated on January 30, 2022

Comments

  • IDmikael
    IDmikael over 2 years

    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!

  • Sviat Volkov
    Sviat Volkov over 5 years
    how to make input text center vertical ? so that text inside chip and outside would be on the same level
  • Mohamed Mohaideen AH
    Mohamed Mohaideen AH over 5 years
    @SviatVolkov You need to add custom class in order to get text see my edited answer.
  • Taimur
    Taimur over 5 years
    how can I add chip as background ,if I select contact from contact list using filter.
  • karanatwal.github.io
    karanatwal.github.io about 5 years
    For AndroidX with autocomplete functionality github.com/karanatwal/MaterialChipsInput
  • 4face
    4face over 4 years
    I'm having java.lang.IllegalArgumentException: The style on this component requires your app theme to be Theme.MaterialComponents (or a descendant). during the ChipDrawable instantation of my instrumented test. Any clue about that? Parent of my App theme is Theme.MaterialComponents.Light.NoActionBar.Bridge
  • Nilesh Rathore
    Nilesh Rathore about 4 years
    Hi Naveen, this solution is great. I have one question, is there any way where we can pass a list of strings in ChipGroup, and it will inflate automatically like recycler view?
  • Naveen Kumar M
    Naveen Kumar M about 4 years
    @NileshRathore by uisng addChipToGroup function itself you can inflate. Just you want this by programatically instead of typing. So pass the list to this function and iterate it in each loop add view to chipGroup. Here chipGroup is a just empty container
  • Nilesh Rathore
    Nilesh Rathore about 4 years
    Ya, I'm already doing this right now, but problem is that if I get a newly updated list from somewhere, then I have to remove all previously added views and iterate again for adding it one by one. In the recycler view, we have Diff Util so only changed items get updated.
  • Naveen Kumar M
    Naveen Kumar M about 4 years
    Yes that is the only way. Or else you can directly use recycler view with ChipGroup in list_row
  • Esteban
    Esteban almost 4 years
    Thanks! It worked perfectly, I still need to check on Android versions < 23, but this is the best implementation I have found.
  • Clamorious
    Clamorious almost 4 years
    Great idea! You don't need use the chip view, inflate your own layout and add inside FlexboxLayout, my opinion the Chip is only useful, when you are using material component full. Thanks for helping.
  • cesarsicas
    cesarsicas over 3 years
    Good idea, i just have to keep edittext background as transparent and draw a line after linear layout to fake the guide line from edittext. tks
  • Sadegh Bakhshandeh Sajjad
    Sadegh Bakhshandeh Sajjad about 3 years
    Good idea, is it possible share full code?
  • shekhar g h
    shekhar g h about 3 years
    I am using Chip layout as a background. That's it. On top of that there is a normal edit text and a plus image. You can put on click listener on it to trigger events. It's a quick fix, not a solution.
  • Reejesh PK
    Reejesh PK almost 3 years
    If customization to the chip is needed, can inflate like this val chip = LayoutInflater.from(context).inflate(R.layout.my_chip_layout‌​, chipGroup, false) as Chip where the my_chip_layout can have a <com.google.android.material.chip.Chip as parent
  • freezefry
    freezefry over 2 years
    How did you get the horizontal margin between chips?
  • Shubham Gupta
    Shubham Gupta over 2 years
    close icon is not working.
  • Kartik Agarwal
    Kartik Agarwal about 2 years
    how to get values back at time of submit ?