Feb 12, 2013

Android Chips EditText , Token EditText and Bubble EditText - [Part 1]

Standard
Chips EditText, Token EditText, Bubble EditText, Spannable EditText and etc.. There are many names of this control. Whatever we call it but this control is not available in Android. I found many questions and solutions for this control. 

Few popular questions & solutions 
1] Roman Nurik : https://plus.google.com/113735310430199015092/posts/WUd7GrfZfiZ 
2] Android EditText Gmail like Field : http://stackoverflow.com/questions/13747809/android-edittext-gmail-like-to-field 
3] Android Labels or Bubbles in Edit Text : http://stackoverflow.com/questions/8090711/android-labels-or-bubbles-in-edittext/8128848#8128848 
4] Contact Bubble Edit Text : http://stackoverflow.com/questions/10812316/contact-bubble-edittext 

While I was reading above post, I thought that, I should develop Chips Edit Text control, which should be easy to understand and integrated in project. Here, My control is named as "Chips Edit Text". Before going to technical details, I would like to show few screenshots.

 

 


Step 1: Prerequisite

Android provides bunch of Span classes, Span classes are used to format text. I suggest to read following articles for helpful detail and in-depth understanding.

1] Introduction to Span
http://blog.stylingandroid.com/archives/177

2] Easy Method for Formatting Android
http://www.androidengineer.com/2010/08/easy-method-for-formatting-android.html

3] API Classes : Spannable, SpannableString, SpannableStringBuilder, ImageSpan ,ClickableSpan 

Step 2 : Logic

Our goal is to display background (border & fill with color) and image at right side of every chip. Android spannable classes allow us to set background color only. It does not support any drawable at background. Another problem is right side image. Android has ImageSpan class, we can display image at right side with ImageSpan but problem is that ImageSpan will consider as separate entity and it will display outside of background.

I have implemented following logic to overcome above two problems

1. Extends MultiAutoCompleteTextView to create chips edit text.
2. Generate TextView dynamically in code and set style & drawable at right side.
3. Capture image/bitmap of newly generated TextView.
4. Set ImageSpan instead-of country name. 

  
Step 3 : Create Android Project

Create Android project named "Chips Edit Text" 

Step 4 : Create class named "ChipsMultiAutoCompleteTextview.java"


package com.kpbird.chipsedittext;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.text.Editable;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextWatcher;
import android.text.style.ImageSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.MultiAutoCompleteTextView;
import android.widget.TextView;

public class ChipsMultiAutoCompleteTextview extends MultiAutoCompleteTextView implements OnItemClickListener {

 private final String TAG = "ChipsMultiAutoCompleteTextview";
 
 // Constructor 
 public ChipsMultiAutoCompleteTextview(Context context) {
  super(context);
  init(context);
 }
 // Constructor 
 public ChipsMultiAutoCompleteTextview(Context context, AttributeSet attrs) {
  super(context, attrs);
  init(context);
 }
 // Constructor 
 public ChipsMultiAutoCompleteTextview(Context context, AttributeSet attrs,
   int defStyle) {
  super(context, attrs, defStyle);
  init(context);
 }
 // set listeners for item click and text change 
 public void init(Context context){
  setOnItemClickListener(this);
  addTextChangedListener(textWather);
 }
 // TextWatcher, If user type any country name and press comma then following code will regenerate chips 
 private TextWatcher textWather = new TextWatcher() {
  
  @Override
  public void onTextChanged(CharSequence s, int start, int before, int count) {
   if(count >=1){
    if(s.charAt(start) == ',')
     setChips(); // generate chips
   }
  }
  @Override
  public void beforeTextChanged(CharSequence s, int start, int count,int after) {}
  @Override
  public void afterTextChanged(Editable s) {}
 };
 //This function has whole logic for chips generate
 public void setChips(){
  if(getText().toString().contains(",")) // check comman in string
  {
   
   SpannableStringBuilder ssb = new SpannableStringBuilder(getText());
   // split string wich comma
   String chips[] = getText().toString().trim().split(",");
   int x =0;
   // loop will generate ImageSpan for every country name separated by comma
   for(String c : chips){
    // inflate chips_edittext layout 
    LayoutInflater lf = (LayoutInflater) getContext().getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
    TextView textView = (TextView) lf.inflate(R.layout.chips_edittext, null);
    textView.setText(c); // set text
    setFlags(textView, c); // set flag image
    // capture bitmapt of genreated textview
    int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
    textView.measure(spec, spec);
    textView.layout(0, 0, textView.getMeasuredWidth(), textView.getMeasuredHeight());
    Bitmap b = Bitmap.createBitmap(textView.getWidth(), textView.getHeight(),Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(b);
    canvas.translate(-textView.getScrollX(), -textView.getScrollY());
    textView.draw(canvas);
    textView.setDrawingCacheEnabled(true);
    Bitmap cacheBmp = textView.getDrawingCache();
    Bitmap viewBmp = cacheBmp.copy(Bitmap.Config.ARGB_8888, true);
    textView.destroyDrawingCache();  // destory drawable
    // create bitmap drawable for imagespan
    BitmapDrawable bmpDrawable = new BitmapDrawable(viewBmp);
    bmpDrawable.setBounds(0, 0,bmpDrawable.getIntrinsicWidth(),bmpDrawable.getIntrinsicHeight());
    // create and set imagespan 
    ssb.setSpan(new ImageSpan(bmpDrawable),x ,x + c.length() , Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    x = x+ c.length() +1;
   }
   // set chips span 
   setText(ssb);
   // move cursor to last 
   setSelection(getText().length());
  }
  
  
 }
 @Override
 public void onItemClick(AdapterView parent, View view, int position, long id) {
  setChips(); // call generate chips when user select any item from auto complete
 }
 
 // this method set country flag image in textview's drawable component, this logic is not optimize, you need to change as per your requirement
 public void setFlags(TextView textView,String country){
  country = country.trim();
  if(country.equals("India")){
   textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.india, 0);
  }
  else if(country.equals("United States")){
   textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.unitedstates, 0);
  }
  else if(country.equals("Canada")){
   textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.canada, 0);
  }
  else if(country.equals("Australia")){
   textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.australia, 0);
  }
  else if(country.equals("United Kingdom")){
   textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.unitedkingdom, 0);
  }
  else if(country.equals("Philippines")){
   textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.philippines, 0);
  }
  else if(country.equals("Japan")){
   textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.japan, 0);
  }
  else if(country.equals("Italy")){
   textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.japan, 0);
  }
  else if(country.equals("Germany")){
   textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.germany, 0);
  }
  else if(country.equals("Russia")){
   textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.russia, 0);
  }
  else if(country.equals("Malaysia")){
   textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.malaysia, 0);
  }
  else if(country.equals("France")){
   textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.france, 0);
  }
  else if(country.equals("Sweden")){
   textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.sweden, 0);
  }
  else if(country.equals("New Zealand")){
   textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.newzealand, 0);
  }
  else if(country.equals("Singapore")){
   textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.singapore, 0);
  }
  
  
 }
 
}

Step 5 : Create drawable shape for chip named "chips_edittext_bg.xml" in drawable folder.


<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >

    <stroke
        android:width="1dp"
        android:color="#A6B0B8" />

    <solid android:color="#E5E5E6" />

    <corners
        android:bottomLeftRadius="3dp"
        android:bottomRightRadius="3dp"
        android:topLeftRadius="3dp"
        android:topRightRadius="3dp" >
    </corners>

</shape>

Step 6 : Create TextView layout for chip named "chips_edittext.xml" in layout folder


 <?xml version="1.0" encoding="UTF-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/edtTxt1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/chips_edittext_gb"
    android:drawablePadding="2dp"
    android:drawableRight="@drawable/android"
    android:padding="8dp"
    android:shadowColor="#FFFFFF"
    android:shadowDy="1"
    android:shadowRadius="0.01"
    android:textColor="#000000"
    android:textSize="18sp"
    android:textStyle="bold" />

Step 7 : Create string array for multi select data named "country" in strings.xml 


    <string-array name="country">
        <item >India</item>
        <item>United States</item>
        <item>Canada</item>
        <item>Australia</item>
        <item>United Kingdom</item>
        <item>Philippines</item>
        <item >Japan</item>
        <item>Italy</item>
        <item>Germany</item>
        <item>Russia</item>
        <item>Malaysia</item>
        <item>France</item>
        <item>Sweden</item>
        <item>New Zealand</item>
        <item>Singapore</item>
    </string-array>

Step 8 : Edit layout of main_activity.xml file


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >
    <com.kpbird.chipsedittext.ChipsMultiAutoCompleteTextview
         android:id="@+id/multiAutoCompleteTextView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:ems="10"
         />
    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:layout_below="@+id/multiAutoCompleteTextView1"
        android:onClick="buttonClicked"
        android:text="@string/getvalue" />
    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:layout_below="@+id/button1"
        android:text="@string/selectedcountries"
        android:textAppearance="?android:attr/textAppearanceLarge" />

</RelativeLayout>

Step 9 : Edit code of mainactivity.java file


package com.kpbird.chipsedittext;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.MultiAutoCompleteTextView;
import android.widget.TextView;

public class MainActivity extends Activity {

 ChipsMultiAutoCompleteTextview mu;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  mu = (ChipsMultiAutoCompleteTextview) findViewById(R.id.multiAutoCompleteTextView1);

  String[] item = getResources().getStringArray(R.array.country);

  Log.i("", "Country Count : " + item.length);
  mu.setAdapter(new ArrayAdapter(this,
    android.R.layout.simple_dropdown_item_1line, item));
  mu.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
 }

 public void buttonClicked(View v) {
  TextView tv = (TextView) findViewById(R.id.textView1);
  tv.setText(mu.getText().toString());
 }

}





Source code : Github Link
    

16 comments:

  1. Ramesh Akula3:43 PM

    Hello sir, i have small doubt that i need to display contact name but while retrieving values from it they should be a contact numbers. Please help me how to solve this ?

    ReplyDelete
  2. kpbird4:03 PM

    Hello Ramesh,


    Chips Edit Text is extension of MultiAutoCompleteTextView. It does not support key value.

    To solve your problem I can suggest two solution

    1. Take separate ArrayList and put numbers in it when user select contact.

    2. You can use setTag(key,value) property of MutliAutoCompleteTextView.

    REF :http://developer.android.com/reference/android/view/View.html#setTag(int, java.lang.Object)



    I hope above reply help you to solve your problem.


    Thank You
    KPBird

    ReplyDelete
  3. Ramesh Akula4:37 PM

    Thank you for your reply.If i want to use this "contact1 and contact2 are now friends." but now i send to server ids instead of names, then how could i solve this ? I tried with following url but i didn't succeed much. https://android.googlesource.com/platform/packages/apps/Mms/+/refs/heads/master/src/com/android/mms/ui/RecipientsEditor.java . And more thing i run your code i got an error when i enter first character is comma,fix it and i hope it will helpful to perfection in your code .

    ReplyDelete
  4. vidao8:56 AM

    Hello, That's a great code, But I have small problem with it. I want to touch to image in chip to delete this chip from TextView but it is hard to catch onChipTouchEvent. Could you please support me sir?
    Thanks && bestRegards,

    ReplyDelete
  5. kpbird10:10 AM

    Hello Vidao,

    To capture click Android provide ClicableSpan.

    Reference link :
    1. http://developer.android.com/reference/android/text/style/ClickableSpan.html
    2. http://thanksmister.com/tag/clickablespan/



    Thank You

    ReplyDelete
  6. Oh, Thanks you very much.
    I solved my problem.
    I think in your lib may be you should write remove chip method, it is very helpful for some person like me.
    Thanks && BestRegards,

    ReplyDelete
  7. Dear, I set more span in the SpannableStringBuilder to get ClickableSpan. It is worked in android 2.3.3 or higher but in android 2.2 it appear duplicate chip in each text insert. Ex: when I edit "France" complete with "," The Image of chip is "'Iamge'France 'Image'France," and the getText() still return "France,". Could you please give me some idea sir.

    Thanks you very much,

    ReplyDelete
  8. Juanu Haedo9:09 PM

    Hey! First of all, thank you for this! It saved my life!
    I only have one problem:
    On the contacts_edittext.xml the property android:drawableRight has an invalid value of @drawable/android (android:drawableRight="@drawable/android")

    Is this something you misstyped or am I missing something?

    Thanks again!

    ReplyDelete
  9. Muhammad Zeshan4:36 PM

    hi kp bird can u tell me please set chips on the basis of keyboard enter key press and set chip

    ReplyDelete
  10. Himanshu Kumar5:06 PM

    Hi Ketan, thanks for providing such a great implemention, It help me, But I am stuck with a usecase with respect to my application. I would like to enable a cross button on "Long press" action on chip, so we can remove his/her name from the edittext.
    Could you please help me in same regard.
    Thanks
    Himanshu
    Email: himanshuqw12@gmail.com

    ReplyDelete
  11. Nambi Narayanan4:34 PM

    hey! thank you for posting this code,can you guide me,how to add clickable span to the every drawable span text in the multiautocompletetextview..

    ReplyDelete
  12. Ameen Maheen12:35 PM

    its works,but still have a few bugs ,,removing and adding new items causes some problems

    ReplyDelete
  13. nikesh pathak11:06 AM

    Hi KpBird,

    Thank you so much for your grate post,
    i've little bit issue on softkey delete button, when i apply softkey delete selected email not deleted,internally it's deleting but chips does not remove from edittext.

    Thank you.

    ReplyDelete