package com.android.ex.chips; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.drawable.StateListDrawable; import android.net.Uri; import android.support.annotation.DrawableRes; import android.support.annotation.IdRes; import android.support.annotation.LayoutRes; 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 } public interface ChipDeleteListener { void onChipDelete(); } private final LayoutInflater mInflater; private final Context mContext; private ChipDeleteListener mDeleteListener; private Query mQuery; public DropdownChipLayouter(LayoutInflater inflater, Context context) { mInflater = inflater; mContext = context; } public void setQuery(Query query) { mQuery = query; } public void setDeleteListener(ChipDeleteListener listener) { mDeleteListener = listener; } /** * 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) { return bindView(convertView, parent, entry, position, type, constraint, null); } /** * See {@link #bindView(View, ViewGroup, RecipientEntry, int, AdapterType, String)} * @param deleteDrawable */ public View bindView(View convertView, ViewGroup parent, RecipientEntry entry, int position, AdapterType type, String constraint, StateListDrawable deleteDrawable) { // 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; } // For BASE_RECIPIENT set all top dividers except for the first one to be GONE. if (viewHolder.topDivider != null) { viewHolder.topDivider.setVisibility(position == 0 ? View.VISIBLE : View.GONE); } 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); bindDrawableToDeleteView(deleteDrawable, viewHolder.deleteView); return itemView; } /** * Returns a new view with {@link #getItemLayoutResId(AdapterType)}. */ public View newView(AdapterType type) { return mInflater.inflate(getItemLayoutResId(type), 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(type); switch (type) { case BASE_RECIPIENT: case RECIPIENT_ALTERNATES: break; case SINGLE_RECIPIENT: itemLayout = getAlternateItemLayoutResId(type); 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(thumbnailUri); } else { view.setImageResource(getDefaultPhotoResId()); } break; case SINGLE_RECIPIENT: default: break; } view.setVisibility(View.VISIBLE); } else { view.setVisibility(View.GONE); } } protected void bindDrawableToDeleteView(final StateListDrawable drawable, ImageView view) { if (view == null) { return; } if (drawable == null) { view.setVisibility(View.GONE); } view.setImageDrawable(drawable); if (mDeleteListener != null) { view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (drawable.getCurrent() != null) { mDeleteListener.onChipDelete(); } } }); } } 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 @LayoutRes int getItemLayoutResId(AdapterType type) { switch (type) { case BASE_RECIPIENT: return R.layout.chips_autocomplete_recipient_dropdown_item; case RECIPIENT_ALTERNATES: return R.layout.chips_recipient_dropdown_item; default: 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 @LayoutRes int getAlternateItemLayoutResId(AdapterType type) { switch (type) { case BASE_RECIPIENT: return R.layout.chips_autocomplete_recipient_dropdown_item; case RECIPIENT_ALTERNATES: return R.layout.chips_recipient_dropdown_item; default: 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 @DrawableRes 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 @IdRes 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 @IdRes 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 @IdRes 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 @IdRes int getPhotoResId() { return android.R.id.icon; } /** * Returns an id for ImageView in an item View for showing the delete button. In default * {@link android.R.id#icon1} is returned. */ protected @IdRes int getDeleteResId() { return android.R.id.icon1; } /** * 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 final ImageView deleteView; public final View topDivider; public ViewHolder(View view) { displayNameView = (TextView) view.findViewById(getDisplayNameResId()); destinationView = (TextView) view.findViewById(getDestinationResId()); destinationTypeView = (TextView) view.findViewById(getDestinationTypeResId()); imageView = (ImageView) view.findViewById(getPhotoResId()); deleteView = (ImageView) view.findViewById(getDeleteResId()); topDivider = view.findViewById(R.id.chip_autocomplete_top_divider); } } }