1 package com.android.ex.chips; 2 3 import android.content.Context; 4 import android.graphics.Bitmap; 5 import android.graphics.BitmapFactory; 6 import android.graphics.drawable.StateListDrawable; 7 import android.net.Uri; 8 import android.support.annotation.DrawableRes; 9 import android.support.annotation.IdRes; 10 import android.support.annotation.LayoutRes; 11 import android.text.TextUtils; 12 import android.text.util.Rfc822Tokenizer; 13 import android.view.LayoutInflater; 14 import android.view.View; 15 import android.view.ViewGroup; 16 import android.widget.ImageView; 17 import android.widget.TextView; 18 19 import com.android.ex.chips.Queries.Query; 20 21 /** 22 * A class that inflates and binds the views in the dropdown list from 23 * RecipientEditTextView. 24 */ 25 public class DropdownChipLayouter { 26 /** 27 * The type of adapter that is requesting a chip layout. 28 */ 29 public enum AdapterType { 30 BASE_RECIPIENT, 31 RECIPIENT_ALTERNATES, 32 SINGLE_RECIPIENT 33 } 34 35 public interface ChipDeleteListener { 36 void onChipDelete(); 37 } 38 39 private final LayoutInflater mInflater; 40 private final Context mContext; 41 private ChipDeleteListener mDeleteListener; 42 private Query mQuery; 43 44 public DropdownChipLayouter(LayoutInflater inflater, Context context) { 45 mInflater = inflater; 46 mContext = context; 47 } 48 49 public void setQuery(Query query) { 50 mQuery = query; 51 } 52 53 public void setDeleteListener(ChipDeleteListener listener) { 54 mDeleteListener = listener; 55 } 56 57 58 /** 59 * Layouts and binds recipient information to the view. If convertView is null, inflates a new 60 * view with getItemLaytout(). 61 * 62 * @param convertView The view to bind information to. 63 * @param parent The parent to bind the view to if we inflate a new view. 64 * @param entry The recipient entry to get information from. 65 * @param position The position in the list. 66 * @param type The adapter type that is requesting the bind. 67 * @param constraint The constraint typed in the auto complete view. 68 * 69 * @return A view ready to be shown in the drop down list. 70 */ 71 public View bindView(View convertView, ViewGroup parent, RecipientEntry entry, int position, 72 AdapterType type, String constraint) { 73 return bindView(convertView, parent, entry, position, type, constraint, null); 74 } 75 76 /** 77 * See {@link #bindView(View, ViewGroup, RecipientEntry, int, AdapterType, String)} 78 * @param deleteDrawable 79 */ 80 public View bindView(View convertView, ViewGroup parent, RecipientEntry entry, int position, 81 AdapterType type, String constraint, StateListDrawable deleteDrawable) { 82 // Default to show all the information 83 String displayName = entry.getDisplayName(); 84 String destination = entry.getDestination(); 85 boolean showImage = true; 86 CharSequence destinationType = getDestinationType(entry); 87 88 final View itemView = reuseOrInflateView(convertView, parent, type); 89 90 final ViewHolder viewHolder = new ViewHolder(itemView); 91 92 // Hide some information depending on the entry type and adapter type 93 switch (type) { 94 case BASE_RECIPIENT: 95 if (TextUtils.isEmpty(displayName) || TextUtils.equals(displayName, destination)) { 96 displayName = destination; 97 98 // We only show the destination for secondary entries, so clear it only for the 99 // first level. 100 if (entry.isFirstLevel()) { 101 destination = null; 102 } 103 } 104 105 if (!entry.isFirstLevel()) { 106 displayName = null; 107 showImage = false; 108 } 109 110 // For BASE_RECIPIENT set all top dividers except for the first one to be GONE. 111 if (viewHolder.topDivider != null) { 112 viewHolder.topDivider.setVisibility(position == 0 ? View.VISIBLE : View.GONE); 113 } 114 break; 115 case RECIPIENT_ALTERNATES: 116 if (position != 0) { 117 displayName = null; 118 showImage = false; 119 } 120 break; 121 case SINGLE_RECIPIENT: 122 destination = Rfc822Tokenizer.tokenize(entry.getDestination())[0].getAddress(); 123 destinationType = null; 124 } 125 126 // Bind the information to the view 127 bindTextToView(displayName, viewHolder.displayNameView); 128 bindTextToView(destination, viewHolder.destinationView); 129 bindTextToView(destinationType, viewHolder.destinationTypeView); 130 bindIconToView(showImage, entry, viewHolder.imageView, type); 131 bindDrawableToDeleteView(deleteDrawable, viewHolder.deleteView); 132 133 return itemView; 134 } 135 136 /** 137 * Returns a new view with {@link #getItemLayoutResId(AdapterType)}. 138 */ 139 public View newView(AdapterType type) { 140 return mInflater.inflate(getItemLayoutResId(type), null); 141 } 142 143 /** 144 * Returns the same view, or inflates a new one if the given view was null. 145 */ 146 protected View reuseOrInflateView(View convertView, ViewGroup parent, AdapterType type) { 147 int itemLayout = getItemLayoutResId(type); 148 switch (type) { 149 case BASE_RECIPIENT: 150 case RECIPIENT_ALTERNATES: 151 break; 152 case SINGLE_RECIPIENT: 153 itemLayout = getAlternateItemLayoutResId(type); 154 break; 155 } 156 return convertView != null ? convertView : mInflater.inflate(itemLayout, parent, false); 157 } 158 159 /** 160 * Binds the text to the given text view. If the text was null, hides the text view. 161 */ 162 protected void bindTextToView(CharSequence text, TextView view) { 163 if (view == null) { 164 return; 165 } 166 167 if (text != null) { 168 view.setText(text); 169 view.setVisibility(View.VISIBLE); 170 } else { 171 view.setVisibility(View.GONE); 172 } 173 } 174 175 /** 176 * Binds the avatar icon to the image view. If we don't want to show the image, hides the 177 * image view. 178 */ 179 protected void bindIconToView(boolean showImage, RecipientEntry entry, ImageView view, 180 AdapterType type) { 181 if (view == null) { 182 return; 183 } 184 185 if (showImage) { 186 switch (type) { 187 case BASE_RECIPIENT: 188 byte[] photoBytes = entry.getPhotoBytes(); 189 if (photoBytes != null && photoBytes.length > 0) { 190 final Bitmap photo = BitmapFactory.decodeByteArray(photoBytes, 0, 191 photoBytes.length); 192 view.setImageBitmap(photo); 193 } else { 194 view.setImageResource(getDefaultPhotoResId()); 195 } 196 break; 197 case RECIPIENT_ALTERNATES: 198 Uri thumbnailUri = entry.getPhotoThumbnailUri(); 199 if (thumbnailUri != null) { 200 // TODO: see if this needs to be done outside the main thread 201 // as it may be too slow to get immediately. 202 view.setImageURI(thumbnailUri); 203 } else { 204 view.setImageResource(getDefaultPhotoResId()); 205 } 206 break; 207 case SINGLE_RECIPIENT: 208 default: 209 break; 210 } 211 view.setVisibility(View.VISIBLE); 212 } else { 213 view.setVisibility(View.GONE); 214 } 215 } 216 217 protected void bindDrawableToDeleteView(final StateListDrawable drawable, ImageView view) { 218 if (view == null) { 219 return; 220 } 221 if (drawable == null) { 222 view.setVisibility(View.GONE); 223 } 224 225 view.setImageDrawable(drawable); 226 if (mDeleteListener != null) { 227 view.setOnClickListener(new View.OnClickListener() { 228 @Override 229 public void onClick(View view) { 230 if (drawable.getCurrent() != null) { 231 mDeleteListener.onChipDelete(); 232 } 233 } 234 }); 235 } 236 } 237 238 protected CharSequence getDestinationType(RecipientEntry entry) { 239 return mQuery.getTypeLabel(mContext.getResources(), entry.getDestinationType(), 240 entry.getDestinationLabel()).toString().toUpperCase(); 241 } 242 243 /** 244 * Returns a layout id for each item inside auto-complete list. 245 * 246 * Each View must contain two TextViews (for display name and destination) and one ImageView 247 * (for photo). Ids for those should be available via {@link #getDisplayNameResId()}, 248 * {@link #getDestinationResId()}, and {@link #getPhotoResId()}. 249 */ 250 protected @LayoutRes int getItemLayoutResId(AdapterType type) { 251 switch (type) { 252 case BASE_RECIPIENT: 253 return R.layout.chips_autocomplete_recipient_dropdown_item; 254 case RECIPIENT_ALTERNATES: 255 return R.layout.chips_recipient_dropdown_item; 256 default: 257 return R.layout.chips_recipient_dropdown_item; 258 } 259 } 260 261 /** 262 * Returns a layout id for each item inside alternate auto-complete list. 263 * 264 * Each View must contain two TextViews (for display name and destination) and one ImageView 265 * (for photo). Ids for those should be available via {@link #getDisplayNameResId()}, 266 * {@link #getDestinationResId()}, and {@link #getPhotoResId()}. 267 */ 268 protected @LayoutRes int getAlternateItemLayoutResId(AdapterType type) { 269 switch (type) { 270 case BASE_RECIPIENT: 271 return R.layout.chips_autocomplete_recipient_dropdown_item; 272 case RECIPIENT_ALTERNATES: 273 return R.layout.chips_recipient_dropdown_item; 274 default: 275 return R.layout.chips_recipient_dropdown_item; 276 } 277 } 278 279 /** 280 * Returns a resource ID representing an image which should be shown when ther's no relevant 281 * photo is available. 282 */ 283 protected @DrawableRes int getDefaultPhotoResId() { 284 return R.drawable.ic_contact_picture; 285 } 286 287 /** 288 * Returns an id for TextView in an item View for showing a display name. By default 289 * {@link android.R.id#title} is returned. 290 */ 291 protected @IdRes int getDisplayNameResId() { 292 return android.R.id.title; 293 } 294 295 /** 296 * Returns an id for TextView in an item View for showing a destination 297 * (an email address or a phone number). 298 * By default {@link android.R.id#text1} is returned. 299 */ 300 protected @IdRes int getDestinationResId() { 301 return android.R.id.text1; 302 } 303 304 /** 305 * Returns an id for TextView in an item View for showing the type of the destination. 306 * By default {@link android.R.id#text2} is returned. 307 */ 308 protected @IdRes int getDestinationTypeResId() { 309 return android.R.id.text2; 310 } 311 312 /** 313 * Returns an id for ImageView in an item View for showing photo image for a person. In default 314 * {@link android.R.id#icon} is returned. 315 */ 316 protected @IdRes int getPhotoResId() { 317 return android.R.id.icon; 318 } 319 320 /** 321 * Returns an id for ImageView in an item View for showing the delete button. In default 322 * {@link android.R.id#icon1} is returned. 323 */ 324 protected @IdRes int getDeleteResId() { return android.R.id.icon1; } 325 326 /** 327 * A holder class the view. Uses the getters in DropdownChipLayouter to find the id of the 328 * corresponding views. 329 */ 330 protected class ViewHolder { 331 public final TextView displayNameView; 332 public final TextView destinationView; 333 public final TextView destinationTypeView; 334 public final ImageView imageView; 335 public final ImageView deleteView; 336 public final View topDivider; 337 338 public ViewHolder(View view) { 339 displayNameView = (TextView) view.findViewById(getDisplayNameResId()); 340 destinationView = (TextView) view.findViewById(getDestinationResId()); 341 destinationTypeView = (TextView) view.findViewById(getDestinationTypeResId()); 342 imageView = (ImageView) view.findViewById(getPhotoResId()); 343 deleteView = (ImageView) view.findViewById(getDeleteResId()); 344 topDivider = view.findViewById(R.id.chip_autocomplete_top_divider); 345 } 346 } 347 } 348