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 Spanhttp://blog.stylingandroid.com/archives/177
2] Easy Method for Formatting Androidhttp://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 ProjectCreate 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