am 693a1515: am 60e3986c: am 09e73cb2: am 2b080e1e: Main changes to the chips library: Changed attribute styles - Added an avatar position (left, right) inside the chip - Added a disableDelete boolean - Removed chipsAlternateLayout
* commit '693a15154c497b2bf454774b87be0a698cafe15b':
Main changes to the chips library: Changed attribute styles - Added an avatar position (left, right) inside the chip - Added a disableDelete boolean - Removed chipsAlternateLayout
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 44c2500..6b26bcb 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -15,13 +15,17 @@
-->
<resources>
<declare-styleable name="RecipientEditTextView">
- <attr name="invalidChipBackground" format="reference" />
+ <attr name="avatarPosition">
+ <enum name="right" value="0" />
+ <enum name="left" value="1" />
+ </attr>
<attr name="chipBackground" format="reference" />
<attr name="chipBackgroundPressed" format="reference" />
<attr name="chipDelete" format="reference" />
- <attr name="chipAlternatesLayout" format="reference" />
- <attr name="chipPadding" format="reference" />
- <attr name="chipHeight" format="reference" />
<attr name="chipFontSize" format="reference" />
+ <attr name="chipHeight" format="reference" />
+ <attr name="chipPadding" format="reference" />
+ <attr name="disableDelete" format="boolean" />
+ <attr name="invalidChipBackground" format="reference" />
</declare-styleable>
-</resources>
+</resources>
\ No newline at end of file
diff --git a/src/com/android/ex/chips/BaseRecipientAdapter.java b/src/com/android/ex/chips/BaseRecipientAdapter.java
index b779776..d52c7ca 100644
--- a/src/com/android/ex/chips/BaseRecipientAdapter.java
+++ b/src/com/android/ex/chips/BaseRecipientAdapter.java
@@ -23,8 +23,6 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
@@ -43,12 +41,12 @@
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;
-import android.widget.ImageView;
-import android.widget.TextView;
+
+import com.android.ex.chips.DropdownChipLayouter.AdapterType;
import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -460,6 +458,7 @@
private final LayoutInflater mInflater;
private Account mAccount;
private final int mPreferredMaxResultCount;
+ private DropdownChipLayouter mDropdownChipLayouter;
/**
* {@link #mEntries} is responsible for showing every result for this Adapter. To
@@ -570,6 +569,15 @@
return mQueryType;
}
+ public void setDropdownChipLayouter(DropdownChipLayouter dropdownChipLayouter) {
+ mDropdownChipLayouter = dropdownChipLayouter;
+ mDropdownChipLayouter.setQuery(mQuery);
+ }
+
+ public DropdownChipLayouter getDropdownChipLayouter() {
+ return mDropdownChipLayouter;
+ }
+
/**
* Set the account when known. Causes the search to prioritize contacts from that account.
*/
@@ -943,111 +951,9 @@
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final RecipientEntry entry = getEntries().get(position);
- String displayName = entry.getDisplayName();
- String destination = entry.getDestination();
- if (TextUtils.isEmpty(displayName) || TextUtils.equals(displayName, destination)) {
- displayName = destination;
- // We only show the destination for secondary entries, so clear it
- // only for the first level.
- if (entry.isFirstLevel()) {
- destination = null;
- }
- }
-
- final View itemView = convertView != null ? convertView : mInflater.inflate(
- getItemLayout(), parent, false);
- final TextView displayNameView = (TextView) itemView.findViewById(getDisplayNameId());
- final TextView destinationView = (TextView) itemView.findViewById(getDestinationId());
- final TextView destinationTypeView = (TextView) itemView
- .findViewById(getDestinationTypeId());
- final ImageView imageView = (ImageView) itemView.findViewById(getPhotoId());
- displayNameView.setText(displayName);
- if (!TextUtils.isEmpty(destination)) {
- destinationView.setText(destination);
- } else {
- destinationView.setText(null);
- }
- if (destinationTypeView != null) {
- final CharSequence destinationType = mQuery
- .getTypeLabel(mContext.getResources(), entry.getDestinationType(),
- entry.getDestinationLabel()).toString().toUpperCase();
-
- destinationTypeView.setText(destinationType);
- }
-
- if (entry.isFirstLevel()) {
- displayNameView.setVisibility(View.VISIBLE);
- if (imageView != null) {
- imageView.setVisibility(View.VISIBLE);
- final byte[] photoBytes = entry.getPhotoBytes();
- if (photoBytes != null) {
- final Bitmap photo = BitmapFactory.decodeByteArray(photoBytes, 0,
- photoBytes.length);
- imageView.setImageBitmap(photo);
- } else {
- imageView.setImageResource(getDefaultPhotoResource());
- }
- }
- } else {
- displayNameView.setVisibility(View.GONE);
- if (imageView != null) {
- imageView.setVisibility(View.INVISIBLE);
- }
- }
- return itemView;
- }
-
- /**
- * Returns a layout id for each item inside auto-complete list.
- *
- * Each View must contain two TextViews (for display name and destination) and one ImageView
- * (for photo). Ids for those should be available via {@link #getDisplayNameId()},
- * {@link #getDestinationId()}, and {@link #getPhotoId()}.
- */
- protected int getItemLayout() {
- return R.layout.chips_recipient_dropdown_item;
- }
-
- /**
- * Returns a resource ID representing an image which should be shown when ther's no relevant
- * photo is available.
- */
- protected int getDefaultPhotoResource() {
- return R.drawable.ic_contact_picture;
- }
-
- /**
- * Returns an id for TextView in an item View for showing a display name. By default
- * {@link android.R.id#title} is returned.
- */
- protected int getDisplayNameId() {
- return android.R.id.title;
- }
-
- /**
- * Returns an id for TextView in an item View for showing a destination
- * (an email address or a phone number).
- * By default {@link android.R.id#text1} is returned.
- */
- protected int getDestinationId() {
- return android.R.id.text1;
- }
-
- /**
- * Returns an id for TextView in an item View for showing the type of the destination.
- * By default {@link android.R.id#text2} is returned.
- */
- protected int getDestinationTypeId() {
- return android.R.id.text2;
- }
-
- /**
- * Returns an id for ImageView in an item View for showing photo image for a person. In default
- * {@link android.R.id#icon} is returned.
- */
- protected int getPhotoId() {
- return android.R.id.icon;
+ return mDropdownChipLayouter.bindView(convertView, parent, entry, position,
+ AdapterType.BASE_RECIPIENT, mCurrentConstraint.toString());
}
public Account getAccount() {
diff --git a/src/com/android/ex/chips/DropdownChipLayouter.java b/src/com/android/ex/chips/DropdownChipLayouter.java
new file mode 100644
index 0000000..aceefab
--- /dev/null
+++ b/src/com/android/ex/chips/DropdownChipLayouter.java
@@ -0,0 +1,274 @@
+package com.android.ex.chips;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.text.util.Rfc822Tokenizer;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.ex.chips.Queries.Query;
+
+/**
+ * A class that inflates and binds the views in the dropdown list from
+ * RecipientEditTextView.
+ */
+public class DropdownChipLayouter {
+ /**
+ * The type of adapter that is requesting a chip layout.
+ */
+ public enum AdapterType {
+ BASE_RECIPIENT,
+ RECIPIENT_ALTERNATES,
+ SINGLE_RECIPIENT
+ }
+
+ private final LayoutInflater mInflater;
+ private final Context mContext;
+ private Query mQuery;
+
+ public DropdownChipLayouter(LayoutInflater inflater, Context context) {
+ mInflater = inflater;
+ mContext = context;
+ }
+
+ public void setQuery(Query query) {
+ mQuery = query;
+ }
+
+
+ /**
+ * Layouts and binds recipient information to the view. If convertView is null, inflates a new
+ * view with getItemLaytout().
+ *
+ * @param convertView The view to bind information to.
+ * @param parent The parent to bind the view to if we inflate a new view.
+ * @param entry The recipient entry to get information from.
+ * @param position The position in the list.
+ * @param type The adapter type that is requesting the bind.
+ * @param constraint The constraint typed in the auto complete view.
+ *
+ * @return A view ready to be shown in the drop down list.
+ */
+ public View bindView(View convertView, ViewGroup parent, RecipientEntry entry, int position,
+ AdapterType type, String constraint) {
+ // Default to show all the information
+ String displayName = entry.getDisplayName();
+ String destination = entry.getDestination();
+ boolean showImage = true;
+ CharSequence destinationType = getDestinationType(entry);
+
+ final View itemView = reuseOrInflateView(convertView, parent, type);
+
+ final ViewHolder viewHolder = new ViewHolder(itemView);
+
+ // Hide some information depending on the entry type and adapter type
+ switch (type) {
+ case BASE_RECIPIENT:
+ if (TextUtils.isEmpty(displayName) || TextUtils.equals(displayName, destination)) {
+ displayName = destination;
+
+ // We only show the destination for secondary entries, so clear it only for the
+ // first level.
+ if (entry.isFirstLevel()) {
+ destination = null;
+ }
+ }
+
+ if (!entry.isFirstLevel()) {
+ displayName = null;
+ showImage = false;
+ }
+ break;
+ case RECIPIENT_ALTERNATES:
+ if (position != 0) {
+ displayName = null;
+ showImage = false;
+ }
+ break;
+ case SINGLE_RECIPIENT:
+ destination = Rfc822Tokenizer.tokenize(entry.getDestination())[0].getAddress();
+ destinationType = null;
+ }
+
+ // Bind the information to the view
+ bindTextToView(displayName, viewHolder.displayNameView);
+ bindTextToView(destination, viewHolder.destinationView);
+ bindTextToView(destinationType, viewHolder.destinationTypeView);
+ bindIconToView(showImage, entry, viewHolder.imageView, type);
+
+ return itemView;
+ }
+
+ /**
+ * Returns a new view with {@link #getItemLayoutResId()}.
+ */
+ public View newView() {
+ return mInflater.inflate(getItemLayoutResId(), null);
+ }
+
+ /**
+ * Returns the same view, or inflates a new one if the given view was null.
+ */
+ protected View reuseOrInflateView(View convertView, ViewGroup parent, AdapterType type) {
+ int itemLayout = getItemLayoutResId();
+ switch (type) {
+ case BASE_RECIPIENT:
+ case RECIPIENT_ALTERNATES:
+ break;
+ case SINGLE_RECIPIENT:
+ itemLayout = getAlternateItemLayoutResId();
+ break;
+ }
+ return convertView != null ? convertView : mInflater.inflate(itemLayout, parent, false);
+ }
+
+ /**
+ * Binds the text to the given text view. If the text was null, hides the text view.
+ */
+ protected void bindTextToView(CharSequence text, TextView view) {
+ if (view == null) {
+ return;
+ }
+
+ if (text != null) {
+ view.setText(text);
+ view.setVisibility(View.VISIBLE);
+ } else {
+ view.setVisibility(View.GONE);
+ }
+ }
+
+ /**
+ * Binds the avatar icon to the image view. If we don't want to show the image, hides the
+ * image view.
+ */
+ protected void bindIconToView(boolean showImage, RecipientEntry entry, ImageView view,
+ AdapterType type) {
+ if (view == null) {
+ return;
+ }
+
+ if (showImage) {
+ switch (type) {
+ case BASE_RECIPIENT:
+ byte[] photoBytes = entry.getPhotoBytes();
+ if (photoBytes != null && photoBytes.length > 0) {
+ final Bitmap photo = BitmapFactory.decodeByteArray(photoBytes, 0,
+ photoBytes.length);
+ view.setImageBitmap(photo);
+ } else {
+ view.setImageResource(getDefaultPhotoResId());
+ }
+ break;
+ case RECIPIENT_ALTERNATES:
+ Uri thumbnailUri = entry.getPhotoThumbnailUri();
+ if (thumbnailUri != null) {
+ // TODO: see if this needs to be done outside the main thread
+ // as it may be too slow to get immediately.
+ view.setImageURI(entry.getPhotoThumbnailUri());
+ } else {
+ view.setImageResource(getDefaultPhotoResId());
+ }
+ break;
+ case SINGLE_RECIPIENT:
+ default:
+ break;
+ }
+ view.setVisibility(View.VISIBLE);
+ } else {
+ view.setVisibility(View.GONE);
+ }
+ }
+
+ protected CharSequence getDestinationType(RecipientEntry entry) {
+ return mQuery.getTypeLabel(mContext.getResources(), entry.getDestinationType(),
+ entry.getDestinationLabel()).toString().toUpperCase();
+ }
+
+ /**
+ * Returns a layout id for each item inside auto-complete list.
+ *
+ * Each View must contain two TextViews (for display name and destination) and one ImageView
+ * (for photo). Ids for those should be available via {@link #getDisplayNameResId()},
+ * {@link #getDestinationResId()}, and {@link #getPhotoResId()}.
+ */
+ protected int getItemLayoutResId() {
+ return R.layout.chips_recipient_dropdown_item;
+ }
+
+ /**
+ * Returns a layout id for each item inside alternate auto-complete list.
+ *
+ * Each View must contain two TextViews (for display name and destination) and one ImageView
+ * (for photo). Ids for those should be available via {@link #getDisplayNameResId()},
+ * {@link #getDestinationResId()}, and {@link #getPhotoResId()}.
+ */
+ protected int getAlternateItemLayoutResId() {
+ return R.layout.chips_alternate_item;
+ }
+
+ /**
+ * Returns a resource ID representing an image which should be shown when ther's no relevant
+ * photo is available.
+ */
+ protected int getDefaultPhotoResId() {
+ return R.drawable.ic_contact_picture;
+ }
+
+ /**
+ * Returns an id for TextView in an item View for showing a display name. By default
+ * {@link android.R.id#title} is returned.
+ */
+ protected int getDisplayNameResId() {
+ return android.R.id.title;
+ }
+
+ /**
+ * Returns an id for TextView in an item View for showing a destination
+ * (an email address or a phone number).
+ * By default {@link android.R.id#text1} is returned.
+ */
+ protected int getDestinationResId() {
+ return android.R.id.text1;
+ }
+
+ /**
+ * Returns an id for TextView in an item View for showing the type of the destination.
+ * By default {@link android.R.id#text2} is returned.
+ */
+ protected int getDestinationTypeResId() {
+ return android.R.id.text2;
+ }
+
+ /**
+ * Returns an id for ImageView in an item View for showing photo image for a person. In default
+ * {@link android.R.id#icon} is returned.
+ */
+ protected int getPhotoResId() {
+ return android.R.id.icon;
+ }
+
+ /**
+ * A holder class the view. Uses the getters in DropdownChipLayouter to find the id of the
+ * corresponding views.
+ */
+ protected class ViewHolder {
+ public final TextView displayNameView;
+ public final TextView destinationView;
+ public final TextView destinationTypeView;
+ public final ImageView imageView;
+
+ public ViewHolder(View view) {
+ displayNameView = (TextView) view.findViewById(getDisplayNameResId());
+ destinationView = (TextView) view.findViewById(getDestinationResId());
+ destinationTypeView = (TextView) view.findViewById(getDestinationTypeResId());
+ imageView = (ImageView) view.findViewById(getPhotoResId());
+ }
+ }
+}
diff --git a/src/com/android/ex/chips/RecipientAlternatesAdapter.java b/src/com/android/ex/chips/RecipientAlternatesAdapter.java
index b9a7c80..547a76b 100644
--- a/src/com/android/ex/chips/RecipientAlternatesAdapter.java
+++ b/src/com/android/ex/chips/RecipientAlternatesAdapter.java
@@ -27,15 +27,13 @@
import android.text.util.Rfc822Token;
import android.text.util.Rfc822Tokenizer;
import android.util.Log;
-import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
-import android.widget.ImageView;
-import android.widget.TextView;
import com.android.ex.chips.BaseRecipientAdapter.DirectoryListQuery;
import com.android.ex.chips.BaseRecipientAdapter.DirectorySearchParams;
+import com.android.ex.chips.DropdownChipLayouter.AdapterType;
import com.android.ex.chips.Queries.Query;
import java.util.ArrayList;
@@ -51,7 +49,6 @@
*/
public class RecipientAlternatesAdapter extends CursorAdapter {
static final int MAX_LOOKUPS = 50;
- private final LayoutInflater mLayoutInflater;
private final long mCurrentId;
@@ -64,6 +61,7 @@
public static final int QUERY_TYPE_EMAIL = 0;
public static final int QUERY_TYPE_PHONE = 1;
private Query mQuery;
+ private DropdownChipLayouter mDropdownChipLayouter;
public interface RecipientMatchCallback {
public void matchesFound(Map<String, RecipientEntry> results);
@@ -328,14 +326,9 @@
}
public RecipientAlternatesAdapter(Context context, long contactId, long currentId,
- OnCheckedItemChangedListener listener) {
- this(context, contactId, currentId, QUERY_TYPE_EMAIL, listener);
- }
-
- public RecipientAlternatesAdapter(Context context, long contactId, long currentId,
- int queryMode, OnCheckedItemChangedListener listener) {
+ int queryMode, OnCheckedItemChangedListener listener,
+ DropdownChipLayouter dropdownChipLayouter) {
super(context, getCursorForConstruction(context, contactId, queryMode), 0);
- mLayoutInflater = LayoutInflater.from(context);
mCurrentId = currentId;
mCheckedItemChangedListener = listener;
@@ -347,6 +340,8 @@
mQuery = Queries.EMAIL;
Log.e(TAG, "Unsupported query type: " + queryMode);
}
+
+ mDropdownChipLayouter = dropdownChipLayouter;
}
private static Cursor getCursorForConstruction(Context context, long contactId, int queryType) {
@@ -439,7 +434,7 @@
Cursor cursor = getCursor();
cursor.moveToPosition(position);
if (convertView == null) {
- convertView = newView();
+ convertView = mDropdownChipLayouter.newView();
}
if (cursor.getLong(Queries.Query.DATA_ID) == mCurrentId) {
mCheckedItemPosition = position;
@@ -451,44 +446,18 @@
return convertView;
}
- // TODO: this is VERY similar to the BaseRecipientAdapter. Can we combine
- // somehow?
@Override
public void bindView(View view, Context context, Cursor cursor) {
int position = cursor.getPosition();
-
- TextView display = (TextView) view.findViewById(android.R.id.title);
- ImageView imageView = (ImageView) view.findViewById(android.R.id.icon);
RecipientEntry entry = getRecipientEntry(position);
- if (position == 0) {
- display.setText(cursor.getString(Queries.Query.NAME));
- display.setVisibility(View.VISIBLE);
- // TODO: see if this needs to be done outside the main thread
- // as it may be too slow to get immediately.
- imageView.setImageURI(entry.getPhotoThumbnailUri());
- imageView.setVisibility(View.VISIBLE);
- } else {
- display.setVisibility(View.GONE);
- imageView.setVisibility(View.GONE);
- }
- TextView destination = (TextView) view.findViewById(android.R.id.text1);
- destination.setText(cursor.getString(Queries.Query.DESTINATION));
- TextView destinationType = (TextView) view.findViewById(android.R.id.text2);
- if (destinationType != null) {
- destinationType.setText(mQuery.getTypeLabel(context.getResources(),
- cursor.getInt(Queries.Query.DESTINATION_TYPE),
- cursor.getString(Queries.Query.DESTINATION_LABEL)).toString().toUpperCase());
- }
+ mDropdownChipLayouter.bindView(view, null, entry, position,
+ AdapterType.RECIPIENT_ALTERNATES, null);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
- return newView();
- }
-
- private View newView() {
- return mLayoutInflater.inflate(R.layout.chips_recipient_dropdown_item, null);
+ return mDropdownChipLayouter.newView();
}
/*package*/ static interface OnCheckedItemChangedListener {
diff --git a/src/com/android/ex/chips/RecipientEditTextView.java b/src/com/android/ex/chips/RecipientEditTextView.java
index 3cba87b..b355c1b 100644
--- a/src/com/android/ex/chips/RecipientEditTextView.java
+++ b/src/com/android/ex/chips/RecipientEditTextView.java
@@ -30,6 +30,7 @@
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
+import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -149,14 +150,24 @@
private int mChipPadding;
+ /**
+ * Enumerator for avatar position. See attr.xml for more details.
+ * 0 for right, 1 for left.
+ */
+ private int mAvatarPosition;
+
+ private static final int AVATAR_POSITION_RIGHT = 0;
+
+ private static final int AVATAR_POSITION_LEFT = 1;
+
+ private boolean mDisableDelete;
+
private Tokenizer mTokenizer;
private Validator mValidator;
private DrawableRecipientChip mSelectedChip;
- private int mAlternatesLayout;
-
private Bitmap mDefaultContactPhoto;
private ImageSpan mMoreChip;
@@ -254,6 +265,8 @@
private boolean mAttachedToWindow;
+ private DropdownChipLayouter mDropdownChipLayouter;
+
public RecipientEditTextView(Context context, AttributeSet attrs) {
super(context, attrs);
setChipDimensions(context, attrs);
@@ -293,6 +306,12 @@
addTextChangedListener(mTextWatcher);
mGestureDetector = new GestureDetector(context, this);
setOnEditorActionListener(this);
+
+ setDropdownChipLayouter(new DropdownChipLayouter(LayoutInflater.from(context), context));
+ }
+
+ protected void setDropdownChipLayouter(DropdownChipLayouter dropdownChipLayouter) {
+ mDropdownChipLayouter = dropdownChipLayouter;
}
@Override
@@ -433,17 +452,18 @@
@Override
public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
super.setAdapter(adapter);
- ((BaseRecipientAdapter) adapter)
- .registerUpdateObserver(new BaseRecipientAdapter.EntriesUpdatedObserver() {
- @Override
- public void onChanged(List<RecipientEntry> entries) {
- // Scroll the chips field to the top of the screen so
- // that the user can see as many results as possible.
- if (entries != null && entries.size() > 0) {
- scrollBottomIntoView();
- }
- }
- });
+ BaseRecipientAdapter baseAdapter = (BaseRecipientAdapter) adapter;
+ baseAdapter.registerUpdateObserver(new BaseRecipientAdapter.EntriesUpdatedObserver() {
+ @Override
+ public void onChanged(List<RecipientEntry> entries) {
+ // Scroll the chips field to the top of the screen so
+ // that the user can see as many results as possible.
+ if (entries != null && entries.size() > 0) {
+ scrollBottomIntoView();
+ }
+ }
+ });
+ baseAdapter.setDropdownChipLayouter(mDropdownChipLayouter);
}
private void scrollBottomIntoView() {
@@ -543,124 +563,129 @@
TextUtils.TruncateAt.END);
}
+ /**
+ * Creates a bitmap of the given contact on a selected chip.
+ *
+ * @param contact The recipient entry to pull data from.
+ * @param paint The paint to use to draw the bitmap.
+ */
private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint) {
- // Ellipsize the text so that it takes AT MOST the entire width of the
- // autocomplete text entry area. Make sure to leave space for padding
- // on the sides.
- int height = (int) mChipHeight;
- int deleteWidth = height;
- float[] widths = new float[1];
- paint.getTextWidths(" ", widths);
- CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint,
- calculateAvailableWidth() - deleteWidth - widths[0]);
-
- // Make sure there is a minimum chip width so the user can ALWAYS
- // tap a chip without difficulty.
- int width = Math.max(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
- ellipsizedText.length()))
- + (mChipPadding * 2) + deleteWidth);
-
- // Create the background of the chip.
- Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(tmpBitmap);
- if (mChipBackgroundPressed != null) {
- mChipBackgroundPressed.setBounds(0, 0, width, height);
- mChipBackgroundPressed.draw(canvas);
- paint.setColor(sSelectedTextColor);
- // Vertically center the text in the chip.
- canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
- getTextYOffset((String) ellipsizedText, paint, height), paint);
- // Make the delete a square.
- Rect backgroundPadding = new Rect();
- mChipBackgroundPressed.getPadding(backgroundPadding);
- mChipDelete.setBounds(width - deleteWidth + backgroundPadding.left,
- 0 + backgroundPadding.top,
- width - backgroundPadding.right,
- height - backgroundPadding.bottom);
- mChipDelete.draw(canvas);
+ paint.setColor(sSelectedTextColor);
+ Bitmap photo;
+ if (mDisableDelete) {
+ // Show the avatar instead if we don't want to delete
+ photo = getAvatarIcon(contact);
} else {
- Log.w(TAG, "Unable to draw a background for the chips as it was never set");
+ photo = ((BitmapDrawable) mChipDelete).getBitmap();
}
- return tmpBitmap;
+ return createChipBitmap(contact, paint, photo, mChipBackgroundPressed);
}
-
+ /**
+ * Creates a bitmap of the given contact on a selected chip.
+ *
+ * @param contact The recipient entry to pull data from.
+ * @param paint The paint to use to draw the bitmap.
+ */
+ // TODO: Is leaveBlankIconSpacer obsolete now that we have left and right attributes?
private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint,
boolean leaveBlankIconSpacer) {
+ Drawable background = getChipBackground(contact);
+ Bitmap photo = getAvatarIcon(contact);
+ paint.setColor(getContext().getResources().getColor(android.R.color.black));
+ return createChipBitmap(contact, paint, photo, background);
+ }
+
+ private Bitmap createChipBitmap(RecipientEntry contact, TextPaint paint, Bitmap icon,
+ Drawable background) {
+ if (background == null) {
+ Log.w(TAG, "Unable to draw a background for the chips as it was never set");
+ Bitmap.createBitmap((int) mChipHeight * 2, (int) mChipHeight, Bitmap.Config.ARGB_8888);
+ }
+
+ Rect backgroundPadding = new Rect();
+ background.getPadding(backgroundPadding);
+
// Ellipsize the text so that it takes AT MOST the entire width of the
// autocomplete text entry area. Make sure to leave space for padding
// on the sides.
int height = (int) mChipHeight;
- int iconWidth = height;
+ // Since the icon is a square, it's width is equal to the maximum height it can be inside
+ // the chip.
+ int iconWidth = height - backgroundPadding.top - backgroundPadding.bottom;
float[] widths = new float[1];
paint.getTextWidths(" ", widths);
CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint,
calculateAvailableWidth() - iconWidth - widths[0]);
+ int textWidth = (int) paint.measureText(ellipsizedText, 0, ellipsizedText.length());
+
// Make sure there is a minimum chip width so the user can ALWAYS
// tap a chip without difficulty.
- int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
- ellipsizedText.length()))
- + (mChipPadding * 2) + iconWidth);
+ int width = Math.max(iconWidth * 2, textWidth + (mChipPadding * 2) + iconWidth
+ + backgroundPadding.left + backgroundPadding.right);
// Create the background of the chip.
Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(tmpBitmap);
- Drawable background = getChipBackground(contact);
- if (background != null) {
- background.setBounds(0, 0, width, height);
- background.draw(canvas);
- // Don't draw photos for recipients that have been typed in OR generated on the fly.
- long contactId = contact.getContactId();
- boolean drawPhotos = isPhoneQuery() ?
- contactId != RecipientEntry.INVALID_CONTACT
- : (contactId != RecipientEntry.INVALID_CONTACT
- && (contactId != RecipientEntry.GENERATED_CONTACT &&
- !TextUtils.isEmpty(contact.getDisplayName())));
- if (drawPhotos) {
- byte[] photoBytes = contact.getPhotoBytes();
- // There may not be a photo yet if anything but the first contact address
- // was selected.
- if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
- // TODO: cache this in the recipient entry?
- getAdapter().fetchPhoto(contact, contact.getPhotoThumbnailUri());
- photoBytes = contact.getPhotoBytes();
- }
-
- Bitmap photo;
- if (photoBytes != null) {
- photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
- } else {
- // TODO: can the scaled down default photo be cached?
- photo = mDefaultContactPhoto;
- }
- // Draw the photo on the left side.
- if (photo != null) {
- RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight());
- Rect backgroundPadding = new Rect();
- mChipBackground.getPadding(backgroundPadding);
- RectF dst = new RectF(width - iconWidth + backgroundPadding.left,
- 0 + backgroundPadding.top,
- width - backgroundPadding.right,
- height - backgroundPadding.bottom);
- Matrix matrix = new Matrix();
- matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL);
- canvas.drawBitmap(photo, matrix, paint);
- }
- } else if (!leaveBlankIconSpacer || isPhoneQuery()) {
- iconWidth = 0;
- }
- paint.setColor(getContext().getResources().getColor(android.R.color.black));
- // Vertically center the text in the chip.
- canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
- getTextYOffset((String)ellipsizedText, paint, height), paint);
- } else {
- Log.w(TAG, "Unable to draw a background for the chips as it was never set");
+ // Draw the background drawable
+ background.setBounds(0, 0, width, height);
+ background.draw(canvas);
+ // Draw the text vertically aligned
+ int textX = mAvatarPosition == AVATAR_POSITION_RIGHT ?
+ mChipPadding + backgroundPadding.left :
+ width - backgroundPadding.right - mChipPadding - textWidth;
+ canvas.drawText(ellipsizedText, 0, ellipsizedText.length(),
+ textX, getTextYOffset(ellipsizedText.toString(), paint, height), paint);
+ if (icon != null) {
+ // Draw the icon
+ int iconX = mAvatarPosition == AVATAR_POSITION_RIGHT ?
+ width - backgroundPadding.right - iconWidth :
+ backgroundPadding.left;
+ RectF src = new RectF(0, 0, icon.getWidth(), icon.getHeight());
+ RectF dst = new RectF(iconX,
+ 0 + backgroundPadding.top,
+ iconX + iconWidth,
+ height - backgroundPadding.bottom);
+ drawIconOnCanvas(icon, canvas, paint, src, dst);
}
return tmpBitmap;
}
/**
+ * Returns the avatar icon to use for this recipient entry. Returns null if we don't want to
+ * draw an icon for this recipient.
+ */
+ private Bitmap getAvatarIcon(RecipientEntry contact) {
+ // Don't draw photos for recipients that have been typed in OR generated on the fly.
+ long contactId = contact.getContactId();
+ boolean drawPhotos = isPhoneQuery() ?
+ contactId != RecipientEntry.INVALID_CONTACT
+ : (contactId != RecipientEntry.INVALID_CONTACT
+ && (contactId != RecipientEntry.GENERATED_CONTACT &&
+ !TextUtils.isEmpty(contact.getDisplayName())));
+
+ if (drawPhotos) {
+ byte[] photoBytes = contact.getPhotoBytes();
+ // There may not be a photo yet if anything but the first contact address
+ // was selected.
+ if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
+ // TODO: cache this in the recipient entry?
+ getAdapter().fetchPhoto(contact, contact.getPhotoThumbnailUri());
+ photoBytes = contact.getPhotoBytes();
+ }
+ if (photoBytes != null) {
+ return BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
+ } else {
+ // TODO: can the scaled down default photo be cached?
+ return mDefaultContactPhoto;
+ }
+ }
+
+ return null;
+ }
+
+ /**
* Get the background drawable for a RecipientChip.
*/
// Visible for testing.
@@ -668,13 +693,26 @@
return contact.isValid() ? mChipBackground : mInvalidChipBackground;
}
- private static float getTextYOffset(String text, TextPaint paint, int height) {
+ /**
+ * Given a height, returns a Y offset that will draw the text in the middle of the height.
+ */
+ protected float getTextYOffset(String text, TextPaint paint, int height) {
Rect bounds = new Rect();
paint.getTextBounds(text, 0, text.length(), bounds);
int textHeight = bounds.bottom - bounds.top ;
return height - ((height - textHeight) / 2) - (int)paint.descent();
}
+ /**
+ * Draws the icon onto the canvas given the source rectangle of the bitmap and the destination
+ * rectangle of the canvas.
+ */
+ protected void drawIconOnCanvas(Bitmap icon, Canvas canvas, Paint paint, RectF src, RectF dst) {
+ Matrix matrix = new Matrix();
+ matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL);
+ canvas.drawBitmap(icon, matrix, paint);
+ }
+
private DrawableRecipientChip constructChipSpan(RecipientEntry contact, boolean pressed,
boolean leaveIconSpace) throws NullPointerException {
if (mChipBackground == null) {
@@ -749,11 +787,6 @@
if (mChipPadding == -1) {
mChipPadding = (int) r.getDimension(R.dimen.chip_padding);
}
- mAlternatesLayout = a.getResourceId(R.styleable.RecipientEditTextView_chipAlternatesLayout,
- -1);
- if (mAlternatesLayout == -1) {
- mAlternatesLayout = R.layout.chips_alternate_item;
- }
mDefaultContactPhoto = BitmapFactory.decodeResource(r, R.drawable.ic_contact_picture);
@@ -772,6 +805,9 @@
if (mInvalidChipBackground == null) {
mInvalidChipBackground = r.getDrawable(R.drawable.chip_background_invalid);
}
+ mAvatarPosition = a.getInt(R.styleable.RecipientEditTextView_avatarPosition, 0);
+ mDisableDelete = a.getBoolean(R.styleable.RecipientEditTextView_avatarPosition, false);
+
mLineSpacingExtra = r.getDimension(R.dimen.line_spacing_extra);
mMaxLines = r.getInteger(R.integer.chips_max_lines);
TypedValue tv = new TypedValue();
@@ -779,6 +815,7 @@
mActionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, getResources()
.getDisplayMetrics());
}
+
a.recycle();
}
@@ -798,6 +835,10 @@
mChipHeight = height;
}
+ public float getChipHeight() {
+ return mChipHeight;
+ }
+
/**
* Set whether to shrink the recipients field such that at most
* one line of recipients chips are shown when the field loses
@@ -1511,12 +1552,12 @@
private ListAdapter createAlternatesAdapter(DrawableRecipientChip chip) {
return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(),
- getAdapter().getQueryType(), this);
+ getAdapter().getQueryType(), this, mDropdownChipLayouter);
}
private ListAdapter createSingleAddressAdapter(DrawableRecipientChip currentChip) {
- return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip
- .getEntry());
+ return new SingleRecipientArrayAdapter(getContext(), currentChip.getEntry(),
+ mDropdownChipLayouter);
}
@Override
@@ -2121,14 +2162,20 @@
// Figure out the bounds of this chip and whether or not
// the user clicked in the X portion.
// TODO: Should x and y be used, or removed?
- return chip.isSelected() && offset == getChipEnd(chip);
+ if (mDisableDelete) {
+ return false;
+ }
+
+ return chip.isSelected() &&
+ ((mAvatarPosition == 0 && offset == getChipEnd(chip)) ||
+ (mAvatarPosition != 0 && offset == getChipStart(chip)));
}
/**
* Remove the chip and any text associated with it from the RecipientEditTextView.
*/
// Visible for testing.
- /*pacakge*/ void removeChip(DrawableRecipientChip chip) {
+ /* package */void removeChip(DrawableRecipientChip chip) {
Spannable spannable = getSpannable();
int spanStart = spannable.getSpanStart(chip);
int spanEnd = spannable.getSpanEnd(chip);
diff --git a/src/com/android/ex/chips/SingleRecipientArrayAdapter.java b/src/com/android/ex/chips/SingleRecipientArrayAdapter.java
index 0571a4e..985953f 100644
--- a/src/com/android/ex/chips/SingleRecipientArrayAdapter.java
+++ b/src/com/android/ex/chips/SingleRecipientArrayAdapter.java
@@ -17,47 +17,27 @@
package com.android.ex.chips;
import android.content.Context;
-import android.text.util.Rfc822Tokenizer;
-import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
-import android.widget.ImageView;
-import android.widget.TextView;
+
+import com.android.ex.chips.DropdownChipLayouter.AdapterType;
class SingleRecipientArrayAdapter extends ArrayAdapter<RecipientEntry> {
- private int mLayoutId;
+ private final DropdownChipLayouter mDropdownChipLayouter;
- private final LayoutInflater mLayoutInflater;
-
- public SingleRecipientArrayAdapter(Context context, int resourceId, RecipientEntry entry) {
- super(context, resourceId, new RecipientEntry[] {
+ public SingleRecipientArrayAdapter(Context context, RecipientEntry entry,
+ DropdownChipLayouter dropdownChipLayouter) {
+ super(context, dropdownChipLayouter.getAlternateItemLayoutResId(), new RecipientEntry[] {
entry
});
- mLayoutInflater = LayoutInflater.from(context);
- mLayoutId = resourceId;
+
+ mDropdownChipLayouter = dropdownChipLayouter;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = newView();
- }
- bindView(convertView, getItem(position));
- return convertView;
- }
-
- private View newView() {
- return mLayoutInflater.inflate(mLayoutId, null);
- }
-
- private static void bindView(View view, RecipientEntry entry) {
- TextView display = (TextView) view.findViewById(android.R.id.title);
- ImageView imageView = (ImageView) view.findViewById(android.R.id.icon);
- display.setText(entry.getDisplayName());
- display.setVisibility(View.VISIBLE);
- imageView.setVisibility(View.VISIBLE);
- TextView destination = (TextView) view.findViewById(android.R.id.text1);
- destination.setText(Rfc822Tokenizer.tokenize(entry.getDestination())[0].getAddress());
+ return mDropdownChipLayouter.bindView(convertView, parent, getItem(position), position,
+ AdapterType.SINGLE_RECIPIENT, null);
}
}