Java tutorial
/* * Copyright (C) 2012 Robert Li * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.cnc.mombot.utils; import java.io.FileDescriptor; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.cnc.mombot.R; import org.cnc.mombot.provider.DbContract.TableContact; import org.cnc.mombot.resource.ContactResource; import android.annotation.SuppressLint; import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.net.Uri; import android.os.Build; import android.support.v4.widget.CursorAdapter; import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; import android.text.style.ImageSpan; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnFocusChangeListener; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.MultiAutoCompleteTextView; import android.widget.TextView; public class ContactsEditText extends MultiAutoCompleteTextView implements OnClickListener, OnFocusChangeListener { public static Pattern mPatternTagFriend = Pattern.compile("@\\{name: (.*?), email: (.*?), mobile: (.*?)\\}", Pattern.CASE_INSENSITIVE); private ContactsAdapter mAdapter; private Bitmap mLoadingImage; private int mDropdownItemHeight; public ContactsEditText(Context context) { super(context); init(context); } public ContactsEditText(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public ContactsEditText(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } private void init(Context context) { // Set image height mDropdownItemHeight = 48; // Set default image BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_contact_picture_holo_light, options); options.inSampleSize = Utils.calculateInSampleSize(options, mDropdownItemHeight, mDropdownItemHeight); options.inJustDecodeBounds = false; mLoadingImage = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_contact_picture_holo_light, options); // Set adapter mAdapter = new ContactsAdapter(context); setAdapter(mAdapter); mAdapter.swapCursor(mAdapter.runQueryOnBackgroundThread("")); // Separate entries by commas setTokenizer(new Tokenizer() { @Override public CharSequence terminateToken(CharSequence text) { int i = text.length(); while (i > 0 && text.charAt(i - 1) == ' ') { i--; } if (i > 0 && text.charAt(i - 1) == ' ') { return text; } else { if (text instanceof Spanned) { SpannableString sp = new SpannableString(text + " "); TextUtils.copySpansFrom((Spanned) text, 0, text.length(), Object.class, sp, 0); return sp; } else { return text + " "; } } } @Override public int findTokenStart(CharSequence text, int cursor) { int i = cursor; while (i > 0 && text.charAt(i - 1) != ' ') { i--; } // Check if token really started with ' ', else we don't have a valid token if (i < 1 || text.charAt(i - 1) != ' ') { return cursor; } return i; } @Override public int findTokenEnd(CharSequence text, int cursor) { int i = cursor; int len = text.length(); while (i < len) { if (text.charAt(i) == ' ') { return i; } else { i++; } } return len; } }); // Pop up suggestions after 1 character is typed. setThreshold(1); setOnClickListener(this); setOnFocusChangeListener(this); } @Override protected CharSequence convertSelectionToString(Object selectedItem) { ContactResource contact = (ContactResource) selectedItem; return getContactSpanned(contact); } public void addContact(ContactResource contact) { append(getContactSpanned(contact)); append(" "); } public void setTextFromSpannedString(String spannedString) { setText(addPeopleToGroupChat(new SpannableStringBuilder(spannedString))); } private CharSequence getContactSpanned(ContactResource contact) { String friendName = "@{name: " + contact.name + ", email: " + contact.email + ", mobile: " + contact.phone + " }"; return addPeopleToGroupChat(new SpannableStringBuilder(friendName)); } private SpannableStringBuilder addPeopleToGroupChat(SpannableStringBuilder spannable) { // For tag friends. final Matcher matcherTag = mPatternTagFriend.matcher(spannable); while (matcherTag.find()) { // Set span. group 1 is name View view = createContactTextView(matcherTag.group(1)); BitmapDrawable bd = (BitmapDrawable) convertViewToDrawable(view); spannable.setSpan(new ImageSpan(bd), matcherTag.start(), matcherTag.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } return spannable; } private Object convertViewToDrawable(View view) { BitmapDrawable bd = null; try { int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); view.measure(spec, spec); view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); Bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); canvas.translate(-view.getScrollX(), -view.getScrollY()); view.draw(canvas); bd = new BitmapDrawable(getContext().getResources(), bitmap); bd.setBounds(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); } catch (Exception ex) { } return bd; } private View createContactTextView(String text) { TextView tv = new TextView(getContext()); tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, getContext().getResources().getDimension(R.dimen.common_textsize_larger)); tv.setText(text); tv.setBackgroundResource(R.drawable.img_bg_notify_blue); tv.setTextColor(Color.WHITE); return tv; } /** * get selected contact */ public List<ContactResource> getSelectedContact() { String text = getText().toString(); return getListContact(text); } public static List<ContactResource> getListContact(String text) { final Matcher matcherTag = mPatternTagFriend.matcher(text); ArrayList<ContactResource> result = new ArrayList<ContactResource>(); while (matcherTag.find()) { ContactResource c = new ContactResource(); c.name = matcherTag.group(1); c.email = matcherTag.group(2); c.phone = matcherTag.group(3); result.add(c); } return result; } private class ContactsAdapter extends CursorAdapter { Context mContext; LayoutInflater mInflater; public ContactsAdapter(Context context) { super(context, null, 0); mContext = context; mInflater = LayoutInflater.from(context); } @Override public Object getItem(int position) { Cursor cursor = (Cursor) super.getItem(position); ContactResource contact = new ContactResource(); contact.id = cursor.getInt(ContactsQuery.ID_COLUMN); contact.name = cursor.getString(ContactsQuery.NAME_COLUMN); contact.email = cursor.getString(ContactsQuery.EMAIL_COLUMN); contact.phone = cursor.getString(ContactsQuery.MOBILE_COLUMN); return contact; } @Override public Cursor runQueryOnBackgroundThread(CharSequence constraint) { Cursor c; if (constraint == null || constraint.length() == 0) { c = mContext.getContentResolver().query(ContactsQuery.CONTENT_URI, ContactsQuery.PROJECTION, null, null, ContactsQuery.SORT_ORDER); } else { c = mContext.getContentResolver().query(ContactsQuery.FILTER_URI, ContactsQuery.PROJECTION, ContactsQuery.SELECTION.replaceAll("\\$1", (String) constraint), null, ContactsQuery.SORT_ORDER); } return c; } @Override public View newView(Context context, Cursor cursor, ViewGroup viewGroup) { final View dropdownView = mInflater.inflate(R.layout.contacts_dropdown_item, viewGroup, false); ViewHolder holder = new ViewHolder(); holder.text = (TextView) dropdownView.findViewById(android.R.id.text1); holder.image = (ImageView) dropdownView.findViewById(android.R.id.icon); dropdownView.setTag(holder); return dropdownView; } @Override public void bindView(View view, Context context, Cursor cursor) { final ViewHolder holder = (ViewHolder) view.getTag(); final String displayName = cursor.getString(ContactsQuery.NAME_COLUMN); holder.text.setText(displayName); holder.image.setImageBitmap(mLoadingImage); } } /** * Class to hold the dropdown item's views. Used as a tag to bind the child views to its parent. */ private class ViewHolder { public TextView text; public ImageView image; } public static interface ContactsQuery { // A content URI for the Contacts table final static Uri CONTENT_URI = TableContact.CONTENT_URI; // The search/filter query Uri final static Uri FILTER_URI = TableContact.CONTENT_URI; @SuppressLint("InlinedApi") final static String SELECTION = TableContact.NAME + " like '%$1%' OR " + TableContact.EMAIL + " like '%$1%' OR " + TableContact.MOBILE + " like '%$1%' "; final static String SORT_ORDER = TableContact.NAME; @SuppressLint("InlinedApi") final static String[] PROJECTION = { TableContact._ID, TableContact.NAME, TableContact.EMAIL, TableContact.MOBILE }; // The query column numbers which map to each value in the projection final static int ID_COLUMN = 0; final static int NAME_COLUMN = 1; final static int EMAIL_COLUMN = 2; final static int MOBILE_COLUMN = 3; } private static class Utils { // Prevents instantiation. private Utils() { } /** * Uses static final constants to detect if the device's platform version is Honeycomb or later. */ @SuppressWarnings("unused") public static boolean hasHoneycomb() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB; } @SuppressWarnings("unused") public static Bitmap decodeSampledBitmapFromDescriptor(FileDescriptor fileDescriptor, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); } public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { // Calculate ratios of height and width to requested height and width final int heightRatio = Math.round((float) height / (float) reqHeight); final int widthRatio = Math.round((float) width / (float) reqWidth); // Choose the smallest ratio as inSampleSize value, this will guarantee a final image // with both dimensions larger than or equal to the requested height and width. inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; // This offers some additional logic in case the image has a strange // aspect ratio. For example, a panorama may have a much larger // width than height. In these cases the total pixels might still // end up being too large to fit comfortably in memory, so we should // be more aggressive with sample down the image (=larger inSampleSize). final float totalPixels = width * height; // Anything more than 2x the requested pixels we'll sample down further final float totalReqPixelsCap = reqWidth * reqHeight * 2; while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) { inSampleSize++; } } return inSampleSize; } } @Override public void onClick(View v) { showDropDown(); } @Override public void onFocusChange(View view, boolean focus) { Log.d("zzz", "onFocusChange: " + focus); if (focus) { showDropDown(); } else { dismissDropDown(); AppUtils.hideKeyboard(view); } } }